/* Crispy Copilot — built to be read at a glance, in a van, in daylight. */

:root {
  --bg: #0b1220;
  --card: #151f33;
  --text: #f2f6ff;
  --dim: #93a4c4;
  --ok: #1f7a4d;
  --over: #a06a10;
  --way: #a01f27;
  --line: #24304a;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Never let the page bounce under a mounted phone's thumb rest. */
  overscroll-behavior: none;
}

/*
 * ⚠️ THE DASHBOARD DOES NOT SCROLL, AND THAT IS A TAP FIX RATHER THAN A TIDINESS ONE.
 *
 * Alan, 2026-09-26: "The main dashboard has a little bit of vertical scroll so when I'm trying to
 * tap a button and it scrolls on me the button doesn't tap."
 *
 * He has it exactly right, and the mechanism is worth writing down. On a page that CAN scroll, a
 * touch that drifts a few pixels between finger-down and finger-up is a DRAG, not a tap — the
 * browser scrolls by those few pixels and never fires the click. In a moving van a thumb always
 * drifts. So a dashboard with "a little bit" of scroll is far worse than one with a lot: there is
 * nowhere to go, nothing visibly moves, and the button simply does not respond.
 *
 * Three diagnostic lines moved into Settings in the same pass, which is what made it fit. This
 * rule is the guarantee — it holds even if something is added back later.
 *
 * ⚠️ ONLY WHILE THE PANEL IS SHUT. Everything else in this app lives in #panel, which is long and
 * MUST scroll; the class goes on <html> only while the dashboard is the thing on screen.
 */
html.dash-only, html.dash-only body {
  height: 100%;
  overflow: hidden;
  touch-action: manipulation;   /* no double-tap zoom delay swallowing the first tap either */
}

.wrap {
  max-width: 560px;
  margin: 0 auto;
  padding: 14px 16px calc(24px + env(safe-area-inset-bottom));
}

/* ---------- the two big numbers ---------- */
.speedBox {
  border-radius: 18px;
  padding: 18px 12px 10px;
  transition: background-color 250ms ease;
  background: var(--ok);
}
.speedBox.over { background: var(--over); }
.speedBox.way-over { background: var(--way); }

.pair { display: flex; }
/* ⚠️ min-width:0, or a wide caption drags the other column narrower.
   The over/under chip is white-space:nowrap, and a flex item's default min-width is its min-content
   size — so a long string RAISES the floor of its own column and steals width from the posted-limit
   sign beside it. Measured at 390px: "at the limit" took its column from 167px to 215px and moved the
   limit sign. With min-width:0 the two columns keep their even split whatever either one says. */
.col { flex: 1; min-width: 0; text-align: center; }

.num {
  font-size: 19vw;
  line-height: 1;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 560px) { .num { font-size: 104px; } }

.num.limit {
  background: #fff;
  color: #111;
  border-radius: 14px;
  padding: 6px 0;
  border: 5px solid #111;
  margin: 0 8px;
}

/* ⚠️ A FIXED HEIGHT, because one of these two captions changes its words, its size AND now its
 * background — it goes from the words "your speed" to a big red-on-white number and back.
 *
 * Without a reserved height the whole box, and every button under it, would change size the moment
 * Crispy started nagging — on the one screen in this app that is only ever read at a glance, in a
 * moving van, and the screen where a button that has just moved is a button you miss. The reserve
 * holds the TALLEST state (the chip), so nothing shifts in either direction.
 *
 * ⚠️ 42px is measured, not chosen: see test_over_under_readout.js, which asserts the box is the same
 * height in every state. Change the chip's font or padding and that test tells you this number is
 * wrong. margin-top stays folded in rather than added on top, or the pixels come straight back. */
.cap {
  font-size: 13px; opacity: .85; margin-top: 0;
  height: 42px; display: flex; align-items: center; justify-content: center;
  letter-spacing: .04em; text-transform: uppercase;
}
/*
 * HOW FAR OVER OR UNDER — the written copy of what she just said.
 *
 * Alan, 2026-09-27: "while it is there show it in as big a font as the box allows and in bold red
 * print… sometimes i cant understand what she said so the print would help alot." So this is not
 * decoration: it is the fallback for the moments the audio does not land, and it has to be readable
 * at a glance, in daylight, at speed.
 *
 * ⚠️ RED ON WHITE, IN EVERY STATE, AND THE WHITE IS NOT OPTIONAL. The box behind this changes color
 * as you go over — green, then amber (#a06a10), then dark red (#a01f27). Bold red text would be
 * nearly invisible on that last one, which is precisely when she nags hardest and when he most needs
 * to read it. Alan, told this: "when the box is amber or red make the alert box white or another
 * color that you think is best with the red bold text."
 * It is the same chip in ALL THREE states rather than only on amber and red, deliberately:
 *   - the posted-limit number beside it is already a white chip with a dark border, so this reads as
 *     the same visual language instead of a fourth new thing to learn;
 *   - one appearance means nothing re-styles itself at the exact moment it is being read, which is
 *     the same reason every block on this screen has a reserved height;
 *   - red on white is the highest-contrast pairing available here, and this screen is specified to be
 *     "read at a glance, in a van, in daylight".
 * The dark border does the work the white alone cannot: it separates the chip from a white-ish
 * background and keeps its edges crisp against the dark red box.
 */
.speedDelta {
  /* ⚠️ SIZED TO THE SCREEN, AND THE MIDDLE VALUE IS MEASURED. The widest thing this can ever say is
     "47 UNDER" (the dawdling rule starts at 15 under, and Alan chose to keep the number up even while
     crawling in traffic, so a big figure is reachable). Its ink is almost exactly 5.04px wide per 1px
     of font size, and the room inside the chip is 106px at a 320px screen and 149px at 390px.
     A flat 26px looked fine at 390px and overflowed a 320px phone by 25px — and it LOOKED fine at
     320px too, until .col got min-width:0, because until then the caption was quietly stealing width
     from the posted-limit sign beside it. Two bugs hiding each other.
     So: 6.8vw, which is 21.8px on a 320 and 26.5px on a 390, clamped at both ends — 18px so it never
     drops back toward caption size on a tiny screen, 34px so it stops growing once .wrap hits its
     560px maximum and the column stops growing with it. */
  font-size: clamp(18px, 6.8vw, 34px);
  font-weight: 800;
  opacity: 1;
  font-variant-numeric: tabular-nums;
  color: #c1121f;                 /* a red that still reads as red against white, not pink */
  background: #fff;
  border: 3px solid #111;
  border-radius: 10px;
  padding: 0 6px;                 /* 6, not 10: the 8px buys the font size back on a narrow phone */
  line-height: 1.05;
  /* Never wider than its own column. With .col at min-width:0 this is what keeps a long string from
     spilling sideways instead of pushing the layout around. */
  max-width: 100%;
  white-space: nowrap;
}
/* When she is not nagging there is nothing to shout about, so it is a plain caption again: no chip,
   no border, no color — just the word it always was. This is the normal state on most of a drive. */
.speedDelta.isLabel {
  font-size: 13px; font-weight: inherit; opacity: .85;
  color: inherit; background: none; border: 0; padding: 0; border-radius: 0;
}

/* 6px, not 8px: the two reclaimed pixels keep the box exactly the height it was before the over/under
   number was added. Measured at 390px wide — 171.09px before, and it must stay 171.09px. */
.mph { text-align: center; font-size: 12px; opacity: .7; margin-top: 6px; }
/* ⚠️ The ODbL credit rides on this line, so its link must be LEGIBLE on the green card — the OSMF
   guidelines ask for attribution that is easily readable, counting color and contrast. The
   default link blue on green is not; white with an underline is. */
.mph a { color: #fff; text-decoration: underline; }

/* ---------- road name and where the number came from ---------- */
/*
 * ⚠️ Every block between the big numbers and the buttons has a reserved height.
 *
 * On the first drive the road name wrapped onto a second line, a GPS message appeared and
 * vanished every couple of seconds, and the whole screen jumped around underneath — unreadable
 * at a glance, which is the only way it ever gets read. Anything here that changes with the
 * words in it must keep its height whatever it says.
 */
.road {
  margin: 14px 0 6px;
  font-size: 22px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 62px;   /* holds a road name that wraps to two lines */
}

.badge {
  font-size: 12px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
}
.badge.posted { background: #1f7a4d; }
.badge.assumed { background: #7a5c14; }
.badge.unknown { background: #46506a; }
/* Marked signs, still working out what their zones are. Its own color rather than reusing
   "assumed", because the two mean opposite things: assumed is the app guessing and getting on with
   it, learning is the app deliberately NOT guessing yet. Blue matches the accent used for the
   active tab, which is the app's existing "this is in progress" color. */
.badge.learning { background: #1d4e89; }

/* ---------- messages ---------- */
.note {
  margin: 10px 0;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--card);
  border-left: 4px solid #3a86ff;
  font-size: 15px;
  line-height: 1.4;
}
.note.bad { border-left-color: #e5484d; }
.note.good { border-left-color: #30a46c; }
.note.quiet { border-left-color: #46506a; color: var(--dim); }

/* What she said, with the hurry switch to the right of it (Alan, 2026-09-27). align-items:center so a
   one-line message and a two-line one both leave the button vertically centered rather than stuck to
   the top of a tall row. */
.speakRow { display: flex; align-items: center; gap: 10px; }

.speakLine {
  margin: 10px 0;
  font-size: 14px;
  color: var(--dim);
  min-height: 40px;   /* two lines: the message is longer than the placeholder */
  line-height: 1.4;
  flex: 1 1 auto;
  min-width: 0;       /* or a long word from her would push the button off the row entirely */
}
.speakLine.fired { color: #ffd166; font-weight: 600; }

/*
 * The hurry toggle on the driving screen. Small, because the dashboard is for driving and only four
 * things had earned a place on it — this is the fifth, and it is here because it is the one setting
 * whose right answer changes DURING a drive, which is exactly when Settings is the wrong place for it.
 *
 * ⚠️ 44px minimum, per the workspace mobile rule, and a fixed width so the row does not reflow every
 * time the label changes between "Hurry off" and "HURRY ON". flex-shrink:0 for the same reason: her
 * message grows and shrinks constantly, and the button must not move when it does — a control that
 * shifts under a thumb in a moving van is a control that gets mis-tapped.
 */
.hurryDash {
  flex: 0 0 auto;
  width: 96px;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--dim);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: .03em;
  cursor: pointer;
}
/* On, and unmistakably so: the same red as the Settings button's on-state (.btn.small.on), so the two
   places this mode can be switched look like the same switch. */
.hurryDash.on { background: #7a2a2a; border-color: #a03a3a; color: var(--text); }
.hurryDash:active { background: #963434; }

/* ---------- Following: the third state (Alan, driving, 2026-10-10) ----------
 * "a button... labeled slow driver or something like that so I can turn it on when I get behind
 * somebody that's driving slow so Crispy doesn't nag me while that's turned on."
 *
 * ⚠️ DELIBERATELY NOT RED. Red is this app's "a mode is making her louder" color — the hurry
 * state and the Settings switch share it on purpose. Following does the opposite: it makes her
 * quieter about one thing. Wearing the same red would say the same thing as hurry at a glance,
 * at exactly the speed a glance happens. Blue reads as "on, and calm", which is what it is.
 *
 * ⚠️ AND IT IS NOT TAPPABLE THROUGH THE CURTAIN, unlike ⚑ Mark this spot — but it IS readable
 * through it; see the carve-out below. */
.hurryDash.following { background: #2b4a66; border-color: #3f6d96; color: var(--text); }
.hurryDash.following:active { background: #365c80; }

/* ---------- the carve-out: which mode she is in stays readable through the curtain ----------
   Alan, after driving with the first version: "that following button you just added needs to show
   when the cover is over it. The curtain, I mean. So you don't have to tap the curtain to make it
   go away to see the button."

   ⚠️ I ARGUED AGAINST A CARVE-OUT AND THAT ARGUMENT WAS ABOUT THE WRONG THING. It was about making
   this TAPPABLE in the dark, where a second live control on a 95%-black screen is a mis-tap
   waiting to happen. Being able to READ it carries none of that risk and answers the question he
   actually has at 2am: is she quiet because I asked her to be, or because something is broken?
   A mode that silences a warning must be legible while it is doing it.

   ⚠️ pointer-events: none IS LOAD-BEARING, and this is the one place in this app where getting it
   wrong is worse than merely annoying. The button now sits ON TOP of the curtain, so without it a
   tap meant to wake the screen would land on the control and CYCLE THE MODE — turning the speed
   nag off, or on, by accident, in the dark, with no way to tell. That is the workspace's
   wake-tap ghost-click bug pointed at a safety feature. Pinned by
   _templates/test_wake_tap_is_spent.js.

   ⚠️ IT IS THE OPPOSITE OF #markBtn BELOW, deliberately. That one is the single carve-out in this
   app that stays live, because marking a run of signs is a thing you do WHILE the screen is dark.
   This is something to read. Copying markBtn's rule onto it — which is what a tidy-up would do,
   since they sit next to each other and look alike — would arm it.

   Dimmer, not brighter, like every other carve-out: the filled card becomes an outline, and the
   mode survives in the text color rather than a lit background. */
html.dash-only body.dimmed .hurryDash {
  position: relative;
  z-index: 10000;
  pointer-events: none;
  background: transparent;
  border-color: #46506b;
  color: #8e99ae;
  transition: none;                 /* nothing to animate once it is transparent */
}
/* The two modes stay distinguishable at a glance, in outline rather than in fill. */
html.dash-only body.dimmed .hurryDash.following { border-color: #3f6d96; color: #7fb4e0; }
html.dash-only body.dimmed .hurryDash.on        { border-color: #a03a3a; color: #e09a9a; }

/* ---------- whether her voice is actually on the phone ----------
   Empty and taking no room whenever everything is saved, which is the normal case. A line that
   is always there is a line that stops being read, and then the drive where 40 clips are missing
   looks exactly like every other drive. */
.voiceCache { font-size: 13px; line-height: 1.4; margin: 0 0 8px; }
.voiceCache:empty { display: none; }
.voiceCache.ok { color: #30a46c; }
.voiceCache.warn { color: #ffd166; }

/* ---------- how far over she starts talking ---------- */
.thresholds {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 6px 12px;
  margin: 12px 0;
}

.thRow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
}
.thRow + .thRow { border-top: 1px solid var(--line); }

.thLabel { flex: 1; font-size: 15px; }

.thValue {
  min-width: 82px;    /* "3 over" and "12 over" must not shuffle the buttons sideways */
  text-align: center;
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.thBtn {
  width: 52px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #1d2942;
  color: var(--text);
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.thBtn:active { background: #2a3a5c; }

/* ---------- her voice ---------- */
.voiceBox {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 12px;
  margin: 12px 0;
}

.voiceRow { display: flex; gap: 10px; margin-top: 10px; }
.voiceRow .btn { margin: 0; }

/* ---------- Mute and ⚑, sized for a thumb in a moving van (2026-10-06) ----------
   Alan: "the mark this spot button needs to be taller so I can hit it… you can make it a, a lot
   bigger button by taking up that space." The space is what "Zones, roads and log" gave back.

   ⚠️ 110px IS MEASURED, NOT CHOSEN, and the measuring matters because THIS DASHBOARD CANNOT
   SCROLL — html.dash-only sets overflow:hidden, deliberately, so that a thumb drifting a few
   pixels still counts as a tap rather than a drag. Anything that grows here does not make the page
   longer; it pushes Settings toward the fold and eventually off it.

   ⚠️ And the fold is ALREADY TIGHT, which this change found rather than caused. At 360×640 the old
   dashboard ran to 720px: "Zones, roads and log" sat 80px below the fold, unreachable, and Settings
   was clipped by 12px. So the ⚑ was NOT simply given the 68px that button freed — at 110px, with
   .controls' top margin trimmed from 16 to 10, the page now ends at 705px against the old 720px.
   It is SHORTER than before on every screen, which is the only way to grow a button here without
   taking Settings further under the fold on a small phone.

   The ⚑ takes two shares of the row to Mute's one: it is the only button in this app pressed while
   the van is moving, and the only one pressed with the screen dark. Mute is still 100 × 110, which
   is a bigger target than either button had before. */
.voiceRow .btn.small { min-height: 110px; }
#markBtn { flex: 2; font-size: 19px; }
#muteBtn { flex: 1; }
/* ⚠️ THE THIRD BUTTON IN THIS ROW, AND IT MUST STAY IN THIS ROW. Given its own .voiceRow it cost
   110px + a 10px gap and pushed Settings clean off a 360×640 screen — Alan, 2026-10-25: "I cannot
   see anything below talk. Not settings button at all." Read the note above before moving it.
   One share, like Mute, so ⚑ keeps its double share. */
#talkBtn { flex: 1; font-size: 15px; }

.volRow { align-items: center; }
.volLabel { font-size: 15px; min-width: 70px; }

#volume {
  flex: 1;
  height: 44px;          /* a thin slider is unusable with a thumb in a moving van */
  accent-color: #3a86ff;
}

/* ---------- buttons: big enough for a thumb in a moving van ---------- */
/* 16px → 10px (2026-10-06): six pixels bought back toward the taller ⚑, so the page ends higher
   than it did before rather than lower. See the note on .voiceRow. */
.controls { display: grid; gap: 10px; margin: 10px 0; }

.btn {
  position: relative;
  display: block;
  width: 100%;
  min-height: 58px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  padding: 14px;
  text-align: center;
  cursor: pointer;
}
.btn:active { background: #1d2942; }

.btn.primary {
  background: #1f7a4d;
  border-color: #2a9c64;
  font-size: 19px;
  min-height: 66px;
}
.btn.primary:active { background: #24925c; }

.btn.small { flex: 1; min-height: 50px; font-size: 15px; padding: 10px; }
.btn.small.on { background: #7a2a2a; border-color: #a03a3a; }

/* An armed "Not interested" — the second tap forgets a road for good, so the button has to stop
   looking like the one he just tapped by accident. Two taps rather than a confirm box, per the
   workspace rule; this is the only thing telling him the next tap is the real one. */
.btn.danger { background: #7a2a2a; border-color: #a03a3a; }
.btn.danger:active { background: #963434; }

/* The "where is that?" link on every doubt, mark, fill and unknown stretch. An <a>, not a button,
   so a popup blocker can never silently swallow it — and so a long press offers "open in new tab"
   and "copy link" for nothing. Styled as a button because it sits in a row of them; the rules
   below undo the bits of .btn that assume a <button> element. */
/* The compass point beside the road name. Readable at a glance without competing with the name
   itself, and it takes no room at all when the phone has no heading to report. */
/* ⚠️ THE SAME SIZE AS THE STREET NAME. Alan, 2026-09-27: "the north, south, east, and west number is
 * too small. Make it the same size as the street name." It was a fixed 13px beside a 22px road name,
 * which is unreadable at a glance on a phone in a truck. `inherit` takes it from .road, so if the road
 * name's size is ever changed this follows it instead of drifting back out of step.
 *
 * It keeps the pill outline on purpose: the road name is a fact about the MAP and the compass point is a
 * fact about the PHONE, and they go stale independently — see the note in app.js render(). The outline
 * is what stops "Hawkins Academy Road NE" reading as one thing. Padding loosened and vertical-align
 * dropped now that it is full size, so it sits on the text baseline rather than floating above it. */
.roadWay {
  display: inline-block; margin-left: 8px; padding: 0 9px;
  border: 1px solid #46506a; border-radius: 10px;
  font-size: inherit; font-weight: 600; color: var(--dim); vertical-align: baseline;
}
.roadWay:empty { display: none; }

a.btn.maplink {
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; white-space: nowrap;
}

.filebtn { overflow: hidden; }
.filebtn input[type=file] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  font-size: 200px; /* keeps the tappable area the whole button on Android */
}

/* ---------- diagnostics, small and out of the way ---------- */
.diag {
  font-size: 12px;
  color: var(--dim);
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  min-height: 36px;   /* reserved: this line grows and shrinks with every fix */
}

/* ⚠️ The links in here are the ODbL attribution (see index.html). They need to stay legible —
   the OSMF guidelines ask for attribution that is "legible and understandable" — so they are
   given the normal link underline and a color that reads against the dark panel, rather than
   inheriting --dim and disappearing into the surrounding sentence. */
.credit a {
  color: var(--dim);
  text-decoration: underline;
}
.credit {
  margin-top: 22px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--dim);
  line-height: 1.5;
}

/* ---------- the panel: roads, zones, log, backup ----------
   Full screen and scrollable on purpose. A short panel on a long list means the Save button ends
   up below the fold with no way to reach it, which is the workspace's oldest phone complaint. */
.panel {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: var(--bg);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 16px calc(32px + env(safe-area-inset-bottom));
}

.panelHead {
  position: sticky;
  top: 0;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0 10px;
  font-size: 19px;
  font-weight: 700;
  border-bottom: 1px solid var(--line);
  /* Sticky, so Close is always reachable however long the list gets. */
}
.panelHead .btn { width: auto; min-width: 96px; margin: 0; }

.panelTabs { display: flex; gap: 6px; margin: 12px 0; flex-wrap: wrap; }

.tab {
  flex: 1 1 auto;
  min-height: 46px;
  min-width: 120px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--dim);
  font-size: 14px;
  font-weight: 600;
  padding: 8px 10px;
  cursor: pointer;
}
.tab.on { background: #1d2942; color: var(--text); border-color: #3a86ff; }

/* A row in Settings: a name (with an optional line of explanation) on the left, the control on
   the right. Wraps rather than squeezing the control off the edge on a narrow phone. */
.setRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}
.setName { font-size: 16px; font-weight: 600; }
.setHint { font-size: 13px; line-height: 1.45; color: var(--dim); margin-top: 4px; max-width: 46ch; }
.setRow input[type=range] { flex: 1; min-width: 140px; }
/* The toggle reads as ON at a glance without needing a word next to it. */
#hurryBtn.on, #wakeBtn.on { background: var(--ok); }

.section { padding-bottom: 20px; }
.secHead { font-size: 14px; text-transform: uppercase; letter-spacing: .05em; color: var(--dim); margin: 18px 0 8px; }

.hint { font-size: 14px; line-height: 1.5; color: var(--dim); margin: 6px 0 14px; }

/* ── A road's signs, as a chain of zones ─────────────────────────────────────────────────────
   One block per road, one row per zone. The zones are indented under the road so the chain reads
   as a single thing rather than a pile of unrelated keypads. */
.signRoad {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 12px;
  margin: 0 0 14px;
  background: var(--card);
}
.signHead { font-weight: 700; margin-bottom: 8px; }
.zoneRow { padding: 10px 0; border-top: 1px solid var(--line); }
.zoneRow:first-of-type { border-top: 0; }
.zoneKeys { margin-top: 8px; }
/*
 * The zone she has nothing to go on for. Alan asked for the screen to "remind which section she
 * needs to know speed limit for on the first time that she's learning", and on a road with three
 * or four zone rows the ONLY thing that makes one row read as the question is something down the
 * side of it — a word in the gray sub-line beside two other gray sub-lines is not a prompt.
 *
 * Deliberately NOT red: nothing is wrong, and this screen already uses red for a limit she thinks
 * is mis-mapped. Amber for "your turn".
 */
.zoneRow.zoneNeeds { border-left: 3px solid #ffd166; padding-left: 10px; }
/* #ffd166, matching .voiceCache.warn — the app's existing "this needs you" yellow, which is light
   enough to read on the dark card. --over (#a06a10) is the speed-box amber and is far too dark for
   text; it is a background color in this stylesheet, never a foreground one. */
.zoneAsk { color: #ffd166; }
/* The Street View button and what it is a view OF, side by side so the caption cannot be mistaken
   for the row's own sub-line. */
.zoneWhere { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.zoneWhere .rowSub { margin-top: 0; }

/* The signs he marked, above the zones they create. Quieter than the zone rows on purpose: the
   zones are the thing he answers, these are just the list he might need to correct. */
.signList { margin: 4px 0 10px; }
/* ⚠️ IT MUST WRAP, and this row was the only one in the app that did not (fixed 2026-10-06).
   Alan: "The remove button was slightly to the right off screen, and then the double tap, you
   couldn't even tell what it said. It worked, but only a sliver of it was showing."

   Measured at 320px before the fix: Remove sat 93px past the right edge, and ARMED —
   "Tap again to remove", 130px against Remove's 80px — it was 202px off. Three things had to be
   true at once and all three were: the row could not wrap, .signActions was `flex: 0 0 auto` so it
   could not shrink, and .rowMain carries a global `min-width: 200px` so the label would not give
   way either. .row and .markWhy both wrap; this one was missed.

   ⚠️ THE ARMED STATE IS WHY THIS MATTERED rather than just looking untidy. The label grows by 50px
   at exactly the moment it has to be readable, so the one tap that throws away days of her watching
   was confirmed by a control he could not read. It still WORKED, which is precisely why it survived
   — nothing failed, it just could not be seen. */
.signRow {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap;
  padding: 8px 0; border-top: 1px dashed var(--line);
}
.signRow .rowSub { margin-top: 2px; }
/* The label gives way before the buttons do: on a narrow phone "Sign 2" wraps its sub-line onto
   another line, which costs nothing, rather than pushing Remove off the edge. Without this the
   inherited min-width:200px holds the line open and the buttons are the only thing left to go. */
.signRow .rowMain { min-width: 0; }
/* wrap here too, as a backstop: with a longer label in some future state the two buttons stack
   instead of the second one leaving the screen. */
.signActions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; }
/* Removing a sign throws away days of her watching, so the button looks like it means it — but
   only once ARMED does it go solid, because the first tap is not the destructive one. */
.btn.danger { border-color: var(--way); color: #ffb3b8; }
.btn.danger.armed { background: var(--way); border-color: var(--way); color: #fff; font-weight: 800; }
/* What an action just did. A sentence, not a pop-up. */
.askMsg {
  margin: 0 0 12px; padding: 10px 12px; border-radius: 8px;
  background: rgba(255, 209, 102, 0.12); border-left: 3px solid #ffd166;
  color: var(--text); font-size: 13px; line-height: 1.5;
}
/* The buttons that belong to that message. They continue the same panel rather than sitting in a
   separate box — the reminder and the thing that acts on it are one thought, and a detached row
   of buttons reads as a toolbar that happens to be nearby.
   ⚠️ The message loses its bottom margin when these are present, which is what joins the two into
   one panel. Driven by a CLASS that noteInAsk() sets, not by a :has() sibling selector — that
   would read more neatly and would also make the layout depend on a feature this app cannot
   check offline, for a join that is pure decoration. A class is deterministic, and a test can
   read it. */
.askMsg.hasActions { margin-bottom: 0; border-radius: 8px 8px 0 0; }
.askMsgActions {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 0 0 12px; padding: 0 12px 12px;
  border-radius: 0 0 8px 8px;
  background: rgba(255, 209, 102, 0.12); border-left: 3px solid #ffd166;
}
/* ⚠️ A REAL TAP TARGET. This one is reached indoors rather than at speed, but it is still a phone
   and it is still the button that decides whether a week of marked signs survives. */
.askMsgActions .btn { min-height: 44px; }
/* Her recommendation, before he has answered. Outlined rather than filled: it is a suggestion, and
   a filled button would read as already chosen. */
.keyBtn.sug { border-color: #3a86ff; color: var(--text); }
/* The chain in words — the thing that makes the whole model obvious at a glance. */
.signSays {
  margin: 10px 0 0;
  padding: 10px 12px;
  background: #11203a;
  border-left: 3px solid #3a86ff;
  border-radius: 0 10px 10px 0;
  color: var(--text);
}

/* The one place Crispy asks you things, marked so it is obvious at a glance which of two houses a
   hundred yards apart she means. Beside his own name for it, never instead of it. */
.homeTag {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text);
  background: var(--ok);
  border-radius: 6px;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: 1px;
}

/* ── Help ────────────────────────────────────────────────────────────────────────────────────
 * Scoped to #sectionHelp rather than styling ul/ol globally: the help is the only place in this
 * app with a list in it today, and a bare `ul {}` rule would silently reshape the next one
 * somebody adds anywhere else.
 *
 * ⚠️ padding-left, not margin-left, and 22px of it. A phone's default list indent puts the
 * bullet in the 16px gutter the panel already has, so the dots sit half off the left edge. */
#sectionHelp .helpList {
  font-size: 14px;
  line-height: 1.55;
  color: var(--dim);
  margin: 6px 0 16px;
  padding-left: 22px;
}
#sectionHelp .helpList li { margin: 0 0 9px; }
/* The one thing in a long page that has to survive skim-reading. Two of these on the sections
   part, and nowhere else — a page where everything is highlighted has nothing highlighted. */
#sectionHelp .helpRule {
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  background: var(--card);
  border-left: 3px solid #3a86ff;
  border-radius: 0 10px 10px 0;
  margin: 12px 0 16px;
  padding: 12px 14px;
}
/* Your own roads, read off the phone at the moment the tab opens — see drawHelp in panel.js.
   Collapses to nothing when there is nothing to say, so the help never shows an empty box. */
#sectionHelp .helpNow { margin: 10px 0 16px; }
#sectionHelp .helpNow:empty { display: none; }
#sectionHelp .helpNow .helpNowBox {
  font-size: 14px;
  line-height: 1.5;
  background: #11203a;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--dim);
}
#sectionHelp .helpNow b { color: var(--text); }

/* ⚠️ The buttons WRAP rather than squeezing the name. A third button was briefly added to a
   places row on 2026-09-20 and the name column collapsed to a sliver, wrapping over about nine
   lines — Alan saw it immediately. flex: 1 on its own does not protect a text column: flex will
   happily shrink it to nothing to fit whatever sits beside it. A min-width on the text and
   flex-wrap on the row mean the buttons drop to their own line instead. */
.row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}
.rowMain { flex: 1 1 60%; font-size: 16px; line-height: 1.4; min-width: 200px; }
.rowSub { display: block; font-size: 12px; color: var(--dim); margin-top: 3px; }
.row .btn { width: auto; min-width: 92px; margin: 0; flex: 0 0 auto; }

/* A collapsed place row: a real <button>, not a div with a click handler, so it gets the same
   tap/focus/keyboard behavior every other interactive thing in this app already gets for free
   (see .btn, .tab, .keyBtn — nothing here is ever a bare clickable div). Reset to look exactly
   like the plain .row it replaces; .row itself supplies the padding and the divider line. */
.placeRow, .folderRow {
  background: none; border: none; border-radius: 0;
  width: 100%; text-align: left; font: inherit; color: inherit;
  box-shadow: none;
  /* ⚠️ Bigger than the rest of the rows in this app, on purpose. Alan, 2026-10-04: "under Places
     the places that I've already set the text is very small and hard to hit." These are the only
     rows here whose whole job is to BE tapped — everything else in a .row is a label with a real
     button beside it — and at 16px with 12px of padding the target was 46px, under the 48px that
     is the floor for a thumb. 18px and 16px of padding puts it at 58px, the same as a .btn. */
  padding: 16px 0;
  min-height: 58px;
}
.placeRow .rowMain, .folderRow .rowMain { font-size: 18px; }
.placeRow .rowSub, .folderRow .rowSub { flex: 0 0 auto; margin-top: 0; font-size: 14px; }
.placeRow:active, .folderRow:active { background: #141f36; }

/* ── Folders ─────────────────────────────────────────────────────────────────────────────────
   Alan, 2026-10-04: "being able to add folders to sort them manually so i can put all library
   places in one collapsable folder." A folder is not a record anywhere — it exists because a place
   names it (see setPlaceGroup) — so there is nothing here for an empty one to look like. */
.folderRow .rowMain { font-weight: 700; }
.folderMark { display: inline-block; width: 14px; color: var(--dim); }
/* Indented so it is obvious at a glance which rows are inside the folder and which are loose
   underneath it. The line down the left is the same idea with less ink than a box. */
.folderBody { padding-left: 14px; border-left: 2px solid var(--line); }
.folderSplit {
  margin: 18px 0 2px;
  font-size: 13px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--dim);
}

/* The filing strip under an open place. flex-basis 100% puts it on its own line beneath the
   buttons rather than squeezing in among them — the same shape as .markWhy, for the same reason. */
.placeFolders {
  flex: 1 1 100%;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 4px;
}
.foldersLabel { font-size: 13px; color: var(--dim); text-transform: uppercase; letter-spacing: .05em; }
.chip {
  min-height: 44px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 22px;
  background: var(--card);
  color: var(--text);
  font-size: 15px;
  cursor: pointer;
}
.chip:active { background: #2a3a5c; }
.chip.on { background: #1d4d33; border-color: #2f7a52; color: #eafff2; }

.placeNewFolder { flex: 1 1 100%; display: flex; gap: 8px; flex-wrap: wrap; }
.newFolderName {
  flex: 1 1 180px;
  min-width: 0;
  min-height: 52px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font-size: 16px;
}

/* The reason box under a mark. flex-basis 100% puts it on its own line beneath the road name
   rather than squeezing in beside it — on a phone there is no room for both, and a text box
   narrow enough to fit would be unusable. Wraps again at the narrowest widths so the Save and
   Delete buttons keep their full tap target instead of being crushed. */
.markWhy { flex: 1 1 100%; display: flex; gap: 8px; flex-wrap: wrap; }
.markWhy input {
  flex: 1 1 180px;
  min-width: 0;
  min-height: 52px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font-size: 16px;               /* 16px or more, or the phone zooms the page when it is focused */
}
.markWhy .btn { width: auto; min-width: 92px; margin: 0; flex: 0 0 auto; }

.zoneAdd { margin-bottom: 18px; }
.zoneAdd input[type=text] {
  width: 100%;
  min-height: 52px;
  padding: 12px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: var(--text);
  font-size: 16px;   /* 16px or more, or the phone zooms in when the keyboard opens */
}

/* the number pad for filling in a limit */
.fillBox { padding-top: 4px; }
.keypad { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 14px 0; }
.keyBtn {
  min-height: 62px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #1d2942;
  color: var(--text);
  font-size: 22px;
  font-weight: 700;
  cursor: pointer;
}
.keyBtn:active { background: #2a3a5c; }
/* The number a road is set to RIGHT NOW, while changing it. Without this the Change screen looks
   identical to a blank one and there is nothing to say what he is changing FROM. */
.keyBtn.on { background: #1d4d33; border-color: #2f7a52; color: #eafff2; }

/* ---------- the limit the law sets where nothing is posted ----------
   Visibly a separate offer, below the keypad and behind a rule, because a number read off a post
   is always the better answer and this must not look like the first thing to reach for. Each row
   leads with the mph so the choice and its consequence are one glance, not two. */
.lawBox { margin-top: 18px; padding-top: 14px; border-top: 1px solid #2a3350; }
.lawBtn {
  width: 100%; display: flex; align-items: center; gap: 12px;
  text-align: left; margin: 0 0 8px; min-height: 58px; padding: 10px 12px;
}
.lawMph {
  flex: 0 0 auto; min-width: 46px; text-align: center;
  font-size: 20px; font-weight: 700; color: #ffd166;
}
.lawWhat { display: flex; flex-direction: column; font-size: 14px; line-height: 1.3; }
.lawNote { font-size: 11px; color: var(--dim); font-weight: 400; margin-top: 2px; }

/* ---------- where you are headed ---------- */
.destBox {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 10px 14px 8px;
  margin: 0 0 6px;
}
/*
 * ⚠️ ONE SIZE FOR ALL THREE, AND STILL BIG — but now ONE LINE, not two (changed 2026-10-07).
 *
 * The history: 2026-10-02, driving — "it shows the two Francis in big, bold print, but the ETA and
 * the mileage is very tiny. Can you make them both the same size as the Francis print?" So name,
 * miles and ETA became one size and one weight, at 26px, allowed to wrap onto two lines.
 *
 * 2026-10-07: those two lines, plus the long "ETA once you get going" sentence, made the box tall
 * enough to push the dashboard buttons off the fold — and the dashboard does NOT scroll (see
 * html.dash-only), so they were unreachable. Alan: "put the town and the mileage on the same line…
 * shrink the font a little bit if that helps. I still want it to be big though." So all three sit
 * on one wrapping line at 21px (still big, still one glance), and the pending ETA is now a short
 * pulsing "ETA…" rather than a sentence (see .destEta.loading). 21px keeps "to Tulsa, OK  12 mi
 * ETA…" on one line at 360px; a very long name still wraps rather than being clipped.
 */
.destLine { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
.destTo { font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; }
.destName { font-size: 21px; font-weight: 800; }
.destMiles { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; }
.destEta { font-size: 21px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
/* The pending ETA (no fix yet, or stopped) pulses to say "a number is coming once you move",
   which is what lets it be two words instead of a sentence. */
.destEta.loading { color: var(--dim); animation: etaPulse 1.3s ease-in-out infinite; }
@keyframes etaPulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) { .destEta.loading { animation: none } }
/* Said plainly, because it IS an estimate — straight line times a detour factor, not a route. */
.destNote { font-size: 11px; color: var(--dim); opacity: .8; margin-top: 2px; }

/* A saved place's name IS its input — tap it and type. It only looks like a text box when it has
   focus, so the list still reads as a list rather than a form. */
.placeRename {
  background: transparent;
  color: var(--text);
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 4px 6px;
  margin: -4px -6px;
  font-size: 16px;               /* 16px or iOS zooms the page when it is focused */
  font-weight: 600;
  width: 100%;
  max-width: 100%;
}
.placeRename:focus {
  background: var(--bg);
  border-color: var(--line);
  outline: none;
}

/* ---------- adding a place ---------- */
.placeAdd { display: grid; gap: 10px; margin-top: 8px; }
.placeWay { display: flex; gap: 8px; }
.placeWay input { flex: 1; }
.placeAdd input[type=text] {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  font-size: 16px;               /* 16px or iOS zooms the page when it is focused */
  width: 100%;
}

/* ---------- what she can be asked, and where you are ----------
   One overlay for both, because both are things you READ rather than answer. It scrolls on its
   own (unlike the dashboard behind it), so a longer list on a short phone is still reachable.

   ⚠️ ABOVE THE CURTAIN AND FULLY TAPPABLE. "Help" is the word somebody says when they are off the
   road in the dark, and a location they cannot read or send is worth nothing. */
#voiceSheet {
  position: fixed;
  left: 0; top: 0; right: 0; bottom: 0;
  z-index: 10001;
  background: rgba(8, 12, 22, 0.97);
  padding: 18px 16px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
#voiceSheet h4 {
  margin: 14px 0 8px;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #8e9cb8;
}
#voiceSheet h4:first-child { margin-top: 0; }
.sayRow {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid #1e2740;
}
.sayRow b { font-size: 18px; color: #e6ecf7; }
.sayRow span { font-size: 13px; color: #8e9cb8; text-align: right; flex: 0 1 auto; }
.sayNote { font-size: 14px; color: #8e9cb8; margin: 12px 0; }
#voiceSheet .btn { margin-top: 14px; min-height: 52px; width: 100%; }
/* ⚠️ THE WAY OUT STAYS ON SCREEN. The command list is eleven rows and scrolls on a 360×640 phone,
   which put Close below the fold — and tapping the backdrop is no help when the content fills it.
   A sticky footer means the way out is always where the thumb already is. */
#voiceSheet #sheetClose {
  position: sticky;
  bottom: 0;
  margin-bottom: 0;
  background: #1a2336;
  box-shadow: 0 -10px 18px rgba(8, 12, 22, 0.97);
}
/* The location, sized to be read off a phone on a dark roadside by somebody who is rattled. */
.whereRoad { font-size: 26px; font-weight: 700; color: #fff; margin: 10px 0 4px; }
.whereNear { font-size: 19px; color: #c3ccdd; margin: 0 0 10px; }
/* ⚠️ THE ONE BUTTON ON THIS SCREEN THAT MUST BE FOUND WITHOUT READING. Red, first, and taller
   than everything around it, because the person looking at it may be hurt, in the dark, holding
   the phone at arm's length. It is an <a href="tel:"> rather than a button: that is what opens the
   dialer, and it works even if every script on the page has stopped running. */
.btn911 {
  background: #8d1f27 !important;
  border-color: #c4323d !important;
  color: #fff !important;
  font-size: 22px !important;
  font-weight: 800 !important;
  min-height: 68px !important;
  letter-spacing: 0.02em;
}
.btn911:active { background: #a82730 !important; }
/* Both call buttons are links, so they need the button's own layout rules applied by hand. */
#voiceSheet a.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
/* She said "tap this one" — so it is marked, without moving or resizing anything under a thumb. */
#voiceSheet .flash { box-shadow: 0 0 0 3px #ffd34d; }
.whereAddr {
  font-size: 23px;
  font-weight: 700;
  color: #fff;
  margin: 10px 0 4px;
  line-height: 1.25;
}
/* Dimmer until the real address lands, so the road name showing through is visibly a placeholder
   rather than something that looks like a confirmed address. */
.whereAddr:not(.found) { color: #c3ccdd; font-weight: 600; }
.iceRow input { width: 100%; }
/* The honest caption under the address. Small, because it qualifies the line above rather than
   competing with it — but present, because that line is an estimate and must not read as a fact. */
.whereCap { font-size: 13px; color: #8e9cb8; margin: 0 0 10px; }

.whereCoords {
  font-size: 22px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  color: #7fe0a8;
  margin: 0;
  user-select: all;          /* one long-press selects the whole thing to copy */
}

/* ---------- she asks at the sign ----------
   Alan, 2026-10-25: reviewing the questions at home meant "opening up the sign" — a Street View tab
   per row — and then "you have to really figure out what happened weeks ago". This is the same
   question put where the answer is.

   ⚠️ FIXED, BECAUSE THIS DASHBOARD CANNOT SCROLL. html.dash-only sets overflow:hidden on purpose,
   so a bar added to the flow pushes the ⚑ button off the bottom edge where nothing can reach it.

   ⚠️ IT SITS ABOVE THE DASHBOARD BUT BELOW THE CURTAIN (9999). A question is not worth more than
   the dark screen, and the carve-out below is what keeps it usable once the curtain is up — the
   same way #markBtn is handled, and for the same reason: this is something you DO while driving. */
#askBar {
  position: fixed;
  left: 10px; right: 10px;
  bottom: 12px;
  z-index: 900;
  padding: 12px 12px 10px;
  border-radius: 14px;
  background: #121a2b;
  box-shadow: 0 0 0 1px #2c3550, 0 10px 30px rgba(0, 0, 0, 0.55);
}
#askQ {
  font-size: 18px;
  line-height: 1.35;
  margin-bottom: 10px;
  color: #e6ecf7;
}
#askQ b { color: #fff; }
.askRow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* ⚠️ MINIMUM 48px TALL, like #markBtn. These are pressed while the van is moving — the standing
   mobile rule in CLAUDE.md — and a row of small buttons at the bottom of a phone in a mount is
   exactly where a thumb lands by accident. */
#askBar .btn {
  min-height: 48px;
  flex: 1 1 auto;
  font-size: 16px;
}
/* ⚠️ #askBar #askMph .btn, NOT .askMphBtn. A bare class loses to "#askBar .btn" on specificity, so
   the numbers inherited flex:1 1 auto, grew to a full row each and stacked TWELVE deep — 745px of
   an 844px screen. The source said flex:0 0 auto the whole time; only a browser showed otherwise.
   Four to a row keeps the whole thing under a third of the screen. */
#askBar #askMph .btn {
  flex: 0 0 auto;
  width: calc(25% - 6px);
  min-width: 0;
  padding-left: 0;
  padding-right: 0;
}
#askMph { margin-top: 8px; }

/* ---------- the dark curtain ---------- */
#dimCurtain {
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(0, 0, 0, 0.95);
  z-index: 9999;
}

/* ---------- the carve-out: both numbers stay readable through the curtain ----------
   Alan, 2026-09-20, after driving with it: "can it have a carve out for the posted speed limit
   and my actual speed so that stays visible always?"

   The panel is lifted ABOVE the curtain rather than a hole being cut in it — a hole would have to
   be positioned to match the panel and would drift the moment the layout changed.

   ⚠️ pointer-events: none is load-bearing. The panel now sits on top of the curtain, so without
   it a tap landing on the numbers — the middle of the screen, the easiest place to hit — would
   be swallowed and the screen would stay dark.

   Everything here gets DIMMER, not brighter. The point of the curtain is battery and OLED
   burn-in, so the solid color panel and the white speed-limit sign (the brightest thing on the
   screen) are stripped back to outlines and digits. The over-speed signal survives by moving from
   the background to the digits themselves, which is less light, not more. */
body.dimmed .speedBox {
  position: relative;
  z-index: 10000;
  pointer-events: none;
  background: transparent;
  transition: none;                 /* no color fade to animate once it is transparent */
}

/* Lighter than --ok/--over/--way, which are chosen to sit behind white text and are too dark to
   BE the text on black. */
body.dimmed .speedBox #speed          { color: #5fcf94; }
body.dimmed .speedBox.over #speed     { color: #e8b356; }
body.dimmed .speedBox.way-over #speed { color: #f2757d; }

/* The posted-limit sign: an unlit outline instead of a glowing white block. */
body.dimmed .num.limit {
  background: transparent;
  color: #d3dae8;
  border-color: #46506b;
}

/* The two labels stay readable. Alan, 2026-09-20, after the first version shipped: "include the
   title's posted limit and your speed as a carve out also" — they were faded almost out, which
   left two bare numbers and nothing saying which was which. Small text, so the light cost is
   negligible; "miles per hour" stays faint because the labels above it already say it. */
body.dimmed .cap { opacity: .62; }
body.dimmed .mph { opacity: .25; }

/* ---------- and the same for where he is headed ----------
   Alan, 2026-10-02: "when the cover comes on, it covers that up. I have to tap the screen every
   time to keep seeing that."

   Exactly the request that produced the speed carve-out in September, about the other half of the
   screen — and the answer is the same one, so this reuses it rather than inventing a second
   mechanism. Lifted above the curtain, never a hole cut in it: a hole has to be positioned to match
   the panel and drifts the moment the layout changes.

   ⚠️ pointer-events: none, for the same load-bearing reason as the speed panel. This sits on top of
   the curtain, so without it a tap landing on the destination — which is most of the lower screen —
   would be swallowed and the screen would stay dark. That is the exact complaint being fixed, so
   re-introducing it here would be a cruel joke.

   Dimmer, not brighter: the card's background and border go, and the text drops to the same muted
   grays the speed digits use. The whole point of the curtain is battery and OLED burn-in, and a
   second lit panel would undo it. */
body.dimmed .destBox {
  position: relative;
  z-index: 10000;
  pointer-events: none;
  background: transparent;
  border-color: transparent;
}
body.dimmed .destName  { color: #c3ccdd; }
body.dimmed .destMiles { color: #d3dae8; }
body.dimmed .destEta   { color: #aab4c6; }
body.dimmed .destTo    { opacity: .45; }
/* The estimate caveat is read once and never again, and it is the lowest-value light on a dark
   screen. The numbers it qualifies are unchanged; only the sentence about them goes. */
body.dimmed .destNote  { display: none; }

/* The street name, same request: "make a carve out for the street that I'm on". Lifted the same
   way as the speed panel, and the same tap rule applies — a tap on the road name must still reach
   the curtain. .road has no background of its own, so only the badge needs unlighting. */
body.dimmed .road {
  position: relative;
  z-index: 10000;
  pointer-events: none;
}

body.dimmed #roadName { color: #c9d3e6; }

/* ---------- and the ⚑ button, which is the one thing you DO while the curtain is up ----------
   Alan, 2026-10-04: "when I'm marking roads that button needs to be visible through the curtain
   also."

   Every other carve-out is something to READ, and is pointer-events:none so a tap on it still
   reaches the curtain. This one is the opposite and is the only one of its kind: it stays
   TAPPABLE, so marking a sign at night is one tap rather than tap-to-wake, find the button, tap
   again — at exactly the moment you are passing the sign and cannot look down twice.

   ⚠️ Tapping it does NOT wake the screen, and that is deliberate. Marking a chain of signs along
   one road would otherwise light the phone up at every sign, which is the whole thing the curtain
   exists to avoid. The button's own label is the confirmation — it reads "Sign marked — tap again
   at the next one" for four seconds — and she says it out loud as well.

   ⚠️ Scoped to html.dash-only, i.e. the panel is shut. The panel is a different screen that happens
   to sit under the same curtain, and a lone ⚑ floating over the trip log would be a stray control
   with nothing behind it.

   Dimmer, not brighter, like the rest: the filled card becomes an outline. */
html.dash-only body.dimmed #markBtn {
  position: relative;
  z-index: 10000;
  background: transparent;
  border-color: #46506b;
  color: #c3ccdd;
  font-weight: 600;
}

/* ---------- and the Talk button, which is the other thing you DO while the curtain is up ----------
   ⚠️ THE THIRD TAP-KEEPING CARVE-OUT, and the last one that should ever be added. The two above it
   (#markBtn, #askBar) are things you DO on a dark screen; so is this, and more so — talking is the
   only way to operate this app without looking at it at all.

   ⚠️ DO NOT ADD pointer-events:none. It sits among rules that all have it, for things that are
   merely READ through the curtain, and a tidy-up that made these consistent would leave the one
   control that works in the dark doing nothing at all.

   ⚠️ Tapping it does NOT wake the screen, exactly like #markBtn — the whole point of answering her
   by voice at night is that the screen stays dark. She speaks the confirmation; the screen is not
   where the answer lands. */
html.dash-only body.dimmed #talkBtn {
  position: relative;
  z-index: 10000;
  background: transparent;
  border-color: #46506b;
  color: #c3ccdd;
  font-weight: 600;
}

/* ---------- and the question she asked, if the curtain came up while it was open ----------
   ⚠️ THE SECOND CARVE-OUT THAT KEEPS ITS TAPS, and only the second. Everything else lifted above
   this curtain is something to READ and is pointer-events:none so the tap falls through and wakes
   the screen. These two are things you DO while the screen is dark: marking a sign, and answering
   the question about the sign you just passed. Both are one touch at the moment you are beside it.

   ⚠️ SHE DOES NOT *START* AN ASK WHILE THE CURTAIN IS UP — askOnTheRoad refuses, because there is
   no recorded 'ask' line yet and a silent question on a black screen is one nobody is being asked.
   This rule is for the ask that was already on screen when the two minutes ran out, which would
   otherwise go dark mid-answer with his thumb on the way to a button.

   ⚠️ DO NOT ADD pointer-events:none TO THIS. It sits next to the read-only carve-outs and looks
   just like them — CLAUDE.md records this exact tidy-up as the thing that would leave #markBtn in
   plain sight doing nothing, and this inherits the same trap. An untappable answer row is worse
   than no row: he taps a number, nothing happens, and the next pass asks him again.

   Dimmed to an outline like the rest, so a dark screen stays dark. */
html.dash-only body.dimmed #askBar {
  z-index: 10000;
  background: transparent;
  box-shadow: 0 0 0 1px #46506b;
}
html.dash-only body.dimmed #askQ,
html.dash-only body.dimmed #askQ b { color: #c3ccdd; }
html.dash-only body.dimmed #askBar .btn {
  background: transparent;
  border-color: #46506b;
  color: #c3ccdd;
}

/* The little source badge (posted / assumed / waiting) is a solid pill by day. It tells you where
   the limit CAME from, which is worth knowing when you are looking, not while you are driving at
   night — so it becomes an outline rather than a lit capsule. */
body.dimmed .badge {
  background: transparent;
  color: #8e9cb8;
  box-shadow: inset 0 0 0 1px #3a4360;
}

/* ---------- the update bar, over a dark screen ----------
   ⚠️⚠️ IT MUST NOT TAKE THE TAP THAT WAKES THE SCREEN (2026-10-11, swept from Daily Delivery's
   audit finding DD-43).

   pwa-update.js draws its bar at z-index 2147483000 — far above this app's 9999 curtain and above
   every carve-out below it — so it sat bright and fully tappable on a screen the driver believes is
   asleep. The curtain's own defences only cover a tap that LANDS ON THE CURTAIN (app.js's wake
   listeners return early while it is up, and the non-passive preventDefault is bound to the curtain
   element), so a tap landing on the bar was delivered to the bar. Low down, where a thumb rests in a
   phone mount, that is "Update now": the app reloads while somebody is driving, and the speed
   readout and the road they were on go with it. Alan's original report about this curtain was that
   a wake tap pressed the button underneath — this is the same bug one layer up, and the one place
   it was still live.

   ⚠️ NOT HIDDEN, and not lowered. Dimmed to an outline and made untappable, the same as every other
   carve-out here: a driver glancing at a dark phone should still be able to see that an update is
   waiting, and the tap falls through to the curtain so the screen comes back. One more tap, with the
   screen lit, reaches the bar normally.

   ⚠️ #markBtn below is the ONE carve-out in this workspace that is deliberately the other way round
   and keeps its taps, because marking a chain of signs is something you do WHILE the screen is dark.
   Do not copy that exception onto this rule — the rules look alike and a tidy-up would — or the bar
   becomes a live control on a screen that is supposed to be off. */
body.dimmed #pwa-update-bar {
  pointer-events: none;
  background: transparent !important;
  border: 1px solid #46506b;
  box-shadow: none !important;
  color: #c3ccdd !important;
}
body.dimmed #pwa-update-bar button {
  background: transparent !important;
  border: 1px solid #46506b !important;
  color: #c3ccdd !important;
}

/* A finished chain, kept on screen until the panel closes so the readback sentence is actually
   seen — see signChainHtml. */
.signDone { color: var(--ok); }

/* ---------- how much work is on this phone and nowhere else ----------
   Alan, 2026-10-02: "Yes, that needs to be added." This app has no cloud, and on the morning of
   that same day clearing the browser's data wiped every road he had filled in — a backup file is
   what brought it back, and only because one happened to exist. Amber when there is something to
   lose, green when there is not; never red, because nothing is broken. */
.backupState {
  margin: 0 0 12px; padding: 10px 12px; border-radius: 8px;
  font-size: 13px; line-height: 1.5; background: var(--card);
}
.backupState.warn { border-left: 3px solid #ffd166; color: var(--text); }
.backupState.ok   { border-left: 3px solid var(--ok); color: var(--dim); }
