*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
  --bg: #F2F1ED;
  --surface: #FFFFFF;
  --text: #1A2433;
  --muted: #5C6B7A;
  --accent: #2D6A4F;
  --accent-h: #1F4D38;
  --border: #D8DDE3;
  --error: #DC2626;
  --success: #16A34A;
  /* Issue #82: a UI-state color, not a content color — it must never be
     mistaken for one of RUN_COLORS (app.js) or PIN.current/storage below, so
     the "this stop is selected" signal stays unambiguous regardless of
     which run's pin it's ringing. Cyan sits outside every other hue this
     app uses (RUN_COLORS' warm trail palette — brown/orange/green/blue/
     rose/purple — plus PIN's green/red), unlike the amber this replaces,
     which had started reading as "just another run color" once RUN_COLORS
     grew its own orange entry (#60, added after amber was first chosen). */
  --selected: #06B6D4;
  /* Sora (loaded in index.html <head>) — reserved for headers/titles/labels
     across the app (issue #59), never body copy/buttons/inputs, so it reads
     as a deliberate "this is a heading" signal rather than a full re-theme. */
  --font-display: 'Sora', system-ui, sans-serif;
  /* Issue #78: swaps the generic system-ui body stack for Inter (loaded
     alongside Sora in index.html) — a considered, highly-legible typeface
     that reads as more deliberate/professional than the OS default, and is
     a well-established pairing with Sora's display weight. system-ui stays
     as the fallback, so a slow/blocked font load never leaves text unstyled. */
  --font-body: 'Inter', system-ui, -apple-system, sans-serif;
}
html, body { height: 100%; height: 100dvh; overflow: hidden; font-family: var(--font-body); line-height: 1.5; background: var(--bg); color: var(--text); }
#app { display: flex; flex-direction: column; height: 100%; height: 100dvh; }

/* Header */
/* Issue #122: installed as a PWA (apple-mobile-web-app-capable, index.html),
   iOS renders the status bar (time/signal/battery) translucently overlaid
   on top of the page's own content instead of reserving its own space —
   the header's dark-green band used to start right at y=0, directly behind
   those icons, reading as broken/overlapping. env(safe-area-inset-top)
   reports that status-bar height on a notched/Dynamic-Island iPhone in
   standalone mode (0 in an ordinary browser tab, where the browser's own
   chrome already occupies that space) — added on top of the header's
   height (not just as padding alone) so the extra space pushes the
   green band down without squeezing the logo/title/Help row's original
   50px any shorter. */
header {
  background: var(--accent); color: white; padding: 0 20px; height: 50px;
  padding-top: env(safe-area-inset-top, 0px);
  height: calc(50px + env(safe-area-inset-top, 0px));
  display: flex; align-items: center; gap: 10px; flex-shrink: 0;
}
header h1 { font-family: var(--font-display); font-size: 17px; font-weight: 800; letter-spacing: -0.01em; }
/* Issue #77: replaces the old "Nominatim / OpenStreetMap" credit line —
   that info wasn't actionable for the person using the app day-to-day, so
   this slot now hosts a real, re-discoverable way to see the onboarding
   info again (previously only shown automatically on first visit). */
.header-help-btn { margin-left: auto; background: none; border: none; color: white; opacity: 0.75; font-size: 12px; font-weight: 600; cursor: pointer; padding: 4px 6px; font-family: inherit; }
.header-help-btn:hover, .header-help-btn:active { opacity: 1; }

/* Issue #86: the build number used to live behind a hidden "tap the logo"
   alert() — surprising and undiscoverable. Moved here, inside the Setup
   step's "More options" disclosure, so it's still reachable (for support/
   bug-report purposes) but stays out of the way — a <details> only renders
   its content when expanded, so this text isn't in the DOM's visible flow
   until a user deliberately opens that section. */
.build-label { margin-top: 10px; font-size: 10px; color: var(--muted); opacity: 0.6; text-align: right; }

/* Standalone PWA (Add to Home Screen) — status bar draws over content
   (apple-mobile-web-app-status-bar-style: black-translucent), so pad the
   header out to clear the notch/status bar and the home indicator. */
@media (display-mode: standalone) {
  header { height: calc(50px + env(safe-area-inset-top)); padding-top: env(safe-area-inset-top); }
  main { padding-bottom: env(safe-area-inset-bottom); }
}

/* Layout */
main { display: flex; flex: 1; overflow: hidden; }
#panel { width: 390px; background: var(--surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0; }
#panel-body { flex: 1; overflow-y: auto; padding: 16px; }
#map { flex: 1; }
.leaflet-container.map-picker-armed { cursor: pointer !important; }

@media (min-width: 640px) {
   #panel-body { padding: 16px 16px 0;}
}

/* Shared sidebar shell (#138) — a persistent main panel next to a sidebar of
   several simultaneously-visible, independently-collapsible named sections.
   Class-based (not ID-based like #panel/#panel-body above) so it can't
   collide with #panel's own mobile override and stays safe for the planner
   to adopt later without touching #panel at all. */
.shell-sidebar { width: 390px; background: var(--surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0; }
.shell-sidebar-body { flex: 1; overflow-y: auto; padding: 16px; }
.shell-main { flex: 1; display: flex; flex-direction: column; position: relative; overflow: hidden; }
/* Issue #202: the bleed used to live on .shell-section-header alone (a
   negative margin canceling .shell-sidebar-body's 16px padding), while
   .shell-section itself — and therefore both its border-bottom divider and
   (when open) .shell-section-body below the header — stayed inset by that
   same 16px. That mismatch left a 16px strip on each side where an open
   section's body painted nothing, exposing .shell-sidebar's white
   background right at the padding boundary, and made a closed section's
   divider stop 16px short of the edges it otherwise looks flush with.
   Moving the bleed here, once, means every child (header, body, divider)
   is already flush and just adds back its own desired inset via padding,
   instead of each one separately fighting the parent's padding. */
.shell-section { border-bottom: 1px solid var(--border); margin: 0 -16px; }
.shell-section:last-of-type { border-bottom: none; }
/* Issue #168: was 11px uppercase text with no background — same color as
   ordinary body text, just smaller, so section names didn't read as clear
   dividers between areas of the sidebar. The shaded, full-bleed bar reads
   unambiguously as a section boundary at a glance instead of blending in. */
.shell-section-header { cursor: pointer; list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 16px; background: var(--bg); font-family: var(--font-display); font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text); }
.shell-section-header:hover { background: var(--border); }
.shell-section-header::-webkit-details-marker { display: none; }
.shell-section-header::after { content: '▸'; color: var(--muted); transition: transform 0.15s; flex-shrink: 0; }
.shell-section[open] > .shell-section-header::after { transform: rotate(90deg); }
.shell-section-badge { background: var(--bg); color: var(--muted); border-radius: 10px; padding: 1px 8px; font-size: 10px; letter-spacing: normal; text-transform: none; font-weight: 600; }
/* Issue #202: was 0 top padding — a section's content sat flush against its
   own header the instant it opened, with no breathing room. Horizontal
   padding is 16px (restoring .shell-section's own bleed, see above) + the
   original 4px inset, now that .shell-section-body's parent no longer
   contributes any padding of its own. */
.shell-section-body { padding: 10px 20px 14px; }
/* Sticky to the bottom of whichever scroll container it's in (the sidebar
   body or the main panel) — same idiom .verify-actions already established
   for a pinned, always-visible action row. */
.shell-pinned { position: sticky; bottom: 0; background: var(--surface); border-top: 1px solid var(--border); padding: 12px 16px; flex-shrink: 0; }

/* Step bar */
.steps { display: flex; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; gap: 0; }
.step-dot { flex-shrink: 0; }
.step-num { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; background: var(--border); color: var(--muted); transition: all 0.2s; }
.step-dot.active .step-num { background: var(--accent); color: white; }
.step-dot.done .step-num { background: var(--success); color: white; }
.step-label { font-family: var(--font-display); font-size: 10px; text-align: center; margin-top: 2px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.step-dot.active .step-label { color: var(--accent); }
.step-line { flex: 1; height: 2px; background: var(--border); margin: 0 4px; transition: background 0.2s; }

/* Form elements */
.section { margin-bottom: 18px; }
.section-title { font-family: var(--font-display); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin-bottom: 8px; }
label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 3px; }
/* font-size is 16px, not the app's usual 13px — iOS Safari auto-zooms the
   page on focus for any input under 16px, which feels janky/non-native. */
input[type=text], input[type=number], select, textarea { width: 100%; padding: 12px 10px; border: 1px solid var(--border); border-radius: 6px; font-size: 16px; color: var(--text); background: var(--surface); outline: none; transition: border-color 0.15s; font-family: inherit; resize: vertical; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(45,106,79,0.12); }
.field { margin-bottom: 10px; }
.hint { font-size: 11px; color: var(--muted); margin-top: 3px; line-height: 1.4; }

/* Mode toggle */
.toggle { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.toggle input[type=radio] { display: none; }
.toggle label { flex: 1; text-align: center; padding: 12px 4px; cursor: pointer; font-size: 12px; font-weight: 600; color: var(--muted); background: var(--surface); margin: 0; transition: all 0.15s; border: none; }
.toggle input[type=radio]:checked + label { background: var(--accent); color: white; }

/* Mobile home screen welcome (wizard step 0, issue #2) */
/* margin-bottom matches the gap below the upload card: its own 16px
   margin-bottom (mobile .wizard-card) + the "Next" button's 12px mt12. */
.home-hero { text-align: center; padding: 4px 8px 0; margin-bottom: 28px; }
/* Issue #81: was a bare 🚩 floating above the title — felt redundant next
   to the header's own flag wordmark right above it. Swapped for the same
   rounded-square-badge mark as the header (public/index.html), just
   larger and color-inverted (accent bg, white icon) to read well against
   the hero's light background — one considered app mark reused at two
   sizes, not two different flag depictions competing. */
.home-hero-flag { display: flex; justify-content: center; }
.home-hero-title { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -0.01em; margin-top: 6px; }
.home-hero-tagline { font-size: 13px; color: var(--muted); margin-top: 4px; }

/* File drop */
#drop-zone { border: 2px dashed var(--border); border-radius: 8px; padding: 18px; text-align: center; cursor: pointer; transition: all 0.15s; background: var(--bg); }
#drop-zone:hover, #drop-zone.dragover { border-color: var(--accent); background: #edf7f2; }
#drop-zone .dz-icon { font-size: 26px; margin-bottom: 4px; }
#drop-zone .dz-main { font-size: 13px; font-weight: 600; color: var(--text); }
#drop-zone .dz-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
/* A long, space-free real-world filename (issue #24) truncates with an
   ellipsis instead of overflowing the dashed box; the row count is a
   separate flex-shrink:0 span so it's never the part that gets cut off. */
.dz-filename-row { display: flex; align-items: baseline; gap: 4px; max-width: 100%; }
.dz-filename { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.dz-rowcount { flex-shrink: 0; color: var(--muted); font-weight: 600; }
#file-input { display: none; }

/* Shared by every collapsible <details> in the app (plan-a-new-route,
   the mobile Verify stop list) for one consistent look. */
.disclosure-summary { cursor: pointer; list-style: none; text-align: center; font-size: 11px; color: var(--muted); font-weight: 600; margin: 14px 0; }
.disclosure-summary::-webkit-details-marker { display: none; }
.disclosure-summary::before { content: '— '; }
.disclosure-summary::after { content: ' —'; }

.howto-details { margin-bottom: 10px; }
.howto-details .hint { text-align: left; padding: 0 4px; }
#verify-list-details > summary { margin-top: 0; }

/* Column map — visibility is controlled by the wrapping #col-map-details
   <details> (issue #38), not this div directly. */
#col-map { background: var(--bg); border-radius: 8px; padding: 10px 12px; margin-top: 10px; border: 1px solid var(--border); }
#col-map .field:last-child { margin-bottom: 0; }

/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 13px 16px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s; width: 100%; }
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover:not(:disabled) { background: var(--accent-h); }
.btn-primary:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-ghost { background: transparent; color: var(--muted); border: 1px solid var(--border); }
.btn-ghost:hover { background: var(--bg); color: var(--text); }
.mt8 { margin-top: 8px; }
.mt12 { margin-top: 12px; }
.mt24 { margin-top: 24px; }
.mb8 { margin-bottom: 8px; }

/* Issue #71: on desktop, Plan mode's "Use This Order"/"Recommend Optimized
   Route"/"Back to Setup" buttons used to sit at the end of #panel-body's
   scrolling content, easy to miss below a long stop list. Sticking them to
   the bottom of #panel-body (which already scrolls, so this needs no DOM
   restructuring) keeps them visible while the stop list scrolls beneath.
   Mobile keeps its original inline placement — its stop list is already
   collapsed behind "View All Stops" by default, so the same problem
   doesn't exist there. */
.verify-actions {  padding-bottom: 16px; margin-top: 4px; }

@media (min-width: 640px) {
   .verify-actions{
      position: sticky; 
      bottom: 0;
      background: var(--surface);
   }
}

/* Progress */
.progress-wrap { margin: 10px 0; }
.progress-bar-bg { background: var(--border); border-radius: 99px; height: 5px; overflow: hidden; }
.progress-bar { background: var(--accent); height: 100%; border-radius: 99px; transition: width 0.3s ease; }
.progress-label { font-size: 11px; color: var(--muted); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Geocoding screen — deliberately more prominent than a plain .section,
   since it's otherwise a static ~1/sec wait with nothing else on screen. */
.geocoding-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 28px 20px; text-align: center; margin-top: 12px;
}
.geocoding-card .section-title { font-size: 13px; margin-bottom: 14px; }
.geocoding-card .progress-bar-bg { height: 8px; }
.geocoding-card .progress-wrap { margin: 18px 0 10px; }
.geo-trivia { margin-top: 20px; min-height: 32px; font-size: 12px; line-height: 1.5; }

/* Stop list (verify step) */
.stop-list { display: flex; flex-direction: column; gap: 4px; transition: transform 0.2s ease-out; }
.stop-list.swiping { transition: none; }
.stop-item { display: flex; align-items: flex-start; gap: 8px; padding: 10px 8px; border-radius: 6px; border: 1px solid var(--border); font-size: 12px; background: white; cursor: pointer; }
.stop-item.err { border-color: #fca5a5; background: #fef2f2; }
.stop-item.sortable-ghost { opacity: 0.4; }
.stop-item.sortable-chosen { box-shadow: 0 2px 8px rgba(0,0,0,.15); }
.drag-handle { flex-shrink: 0; color: var(--muted); cursor: grab; padding: 12px 14px 12px 4px; touch-action: none; font-size: 13px; line-height: 1; user-select: none; }
.drag-handle:active { cursor: grabbing; }
/* Matches the map pin style in makeIcon() — white bold text, white ring,
   drop shadow — so the sidebar badges read the same as the pins they refer to. */
.stop-badge {
  width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 800; color: #fff; flex-shrink: 0; margin-top: 1px; line-height: 1;
  border: 2px solid #fff; box-shadow: 0 2px 6px rgba(0,0,0,.35);
}
/* Map pin number input (Verify step) — sizing/colors are set inline in
   makeEditableIcon() since divIcon HTML is rendered outside this stylesheet's
   normal cascade; only pseudo-elements (not stylable via inline style) live here. */
.map-pin-input { -moz-appearance: textfield; }
.map-pin-input::-webkit-outer-spin-button, .map-pin-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.map-pin-input:focus { box-shadow: 0 0 0 3px rgba(45,106,79,0.5); cursor: text; }
/* Issue #174: the element that actually plays the pop-in animation while a
   pin is still "arriving" — see makeEditableIcon()'s own comment (map.js).
   No layout-relevant styling of its own beyond what's already inline
   (background/size/border/etc, shared with .map-pin-input by design so the
   swap to the real input is visually seamless); this selector exists so
   the reduced-motion override below can reach it too. */
.map-pin-input-decoy { -moz-appearance: textfield; }

/* Issue #73: stop pins pop into place in order on a fresh arrival at Plan —
   animation-delay is set inline per pin (makeEditableIcon(), app.js), this
   keyframe just defines the pop itself. Only referenced when animateIndex
   is non-null, so it never applies on an in-place re-render. */
@keyframes stopPinPop { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .map-pin-input, .map-pin-input-decoy { animation: none !important; opacity: 1 !important; }
}

/* Issue #51: persists after a click/tap — unlike :focus, which only lasts
   while the element has keyboard focus. Tracked as selectedStopIndex
   (app.js) and re-applied after every renderVerify() re-render.
   var(--selected) rather than the app's usual accent green: pins are
   frequently green themselves (PIN.current, RUN_COLORS' green entry), so a
   green ring can nearly disappear against a green pin.
   Originally amber for the same reason — issue #82 moved it to cyan once
   RUN_COLORS grew its own orange entry that amber had started colliding
   with (see --selected's definition above for the full reasoning). The
   pin's ring is a white separator plus a thicker outer ring (vs. a single
   thin ring on the row) since it has to stand out against a busy map
   background, not a plain white list row.
   !important on the pin: makeEditableIcon() sets box-shadow inline (see
   comment above), and an inline style attribute always wins over any
   external stylesheet rule regardless of selector specificity — including
   :focus, which turns out to have this same (pre-existing, unrelated to
   this change) issue. Without !important here, .selected would silently
   never show on the pin at all. */
.stop-item.selected { box-shadow: 0 0 0 3px var(--selected); border-color: var(--selected); }
.map-pin-input.selected { box-shadow: 0 0 0 2px #fff, 0 0 0 5px var(--selected) !important; }
.stop-addr { flex: 1; line-height: 1.35; }
.stop-flags { font-size: 11px; color: var(--muted); flex-shrink: 0; padding-top: 2px; }
.stop-flags-display { cursor: pointer; white-space: nowrap; }
/* Same specificity trick used elsewhere for input[type=number] overrides —
   must fully re-specify sizing/border/text styling or the generic
   input[type=number] rule wins. */
input[type=number].stop-flags-input {
  width: 24px; padding: 0; margin: 0; border: none; outline: none;
  text-align: right; -moz-appearance: textfield; background: transparent;
  color: inherit; font: inherit; font-size: 11px; cursor: pointer;
}
.stop-flags-input::-webkit-outer-spin-button, .stop-flags-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stop-flags-input:focus { cursor: text; color: var(--text); }
.stop-status { font-size: 10px; margin-top: 2px; }
.stop-status.ok { color: var(--success); }
.stop-status.bad { color: var(--error); }
/* Issue #83: a stop whose pin the user moved this session. Amber reads as
   "edited/modified" and is now free (selection moved to cyan in #82). Only
   ever appears on the Plan screen, so it can't be confused with a run color
   on Route Output. */
.stop-status.moved { color: #B45309; font-weight: 700; }
.stop-item.moved { border-left: 3px solid #B45309; }
/* Per-stop location actions ("📍 Move" always, "↺ Reset" when moved) — a
   compact, low-emphasis row so a long stop list doesn't get heavy. Hidden
   until the row is selected (issue #51's .selected), so the default list
   stays clean and the actions surface only for the stop you're actually
   working with. Issue #118: previously also shown on :hover, which meant
   just browsing the list on desktop — or the sticky-hover a tap leaves
   behind on mobile — surfaced the Move button before the user had actually
   selected anything; selection-only is a deliberate, less distracting gate.
   A moved row keeps them always visible, since its Reset needs to stay
   reachable as the signal that it's editable. */
.stop-loc-actions { display: flex; gap: 6px; margin-top: 4px; max-height: 0; overflow: hidden; opacity: 0; transition: max-height 0.15s, opacity 0.15s; }
.stop-item.selected .stop-loc-actions,
.stop-item.moved .stop-loc-actions { max-height: 40px; opacity: 1; }
/* Issue #112: shared base for small inline action buttons — .btn-loc (Plan's
   Move/Reset) and .btn-retry/.btn-place-map/.btn-retry-addr (a failed
   stop's retry actions) were two separate, near-identical rule blocks
   differing only in padding/font-size/color; this list-selector groups the
   properties that were duplicated verbatim, leaving each group's own rule
   below to hold only what's actually different between them. */
.btn-loc, .btn-retry, .btn-place-map, .btn-retry-addr { border: 1px solid var(--border); border-radius: 5px; background: white; font-weight: 600; cursor: pointer; white-space: nowrap; }
.btn-loc:hover, .btn-retry:hover:not(:disabled), .btn-place-map:hover:not(:disabled), .btn-retry-addr:hover:not(:disabled) { background: var(--bg); border-color: var(--accent); }

.btn-loc { padding: 3px 8px; color: var(--muted); font-size: 10px; }
.btn-loc:hover { color: var(--text); }
/* Issue #121: "Edit Stop #" swaps for a same-sized number input in place —
   sized/styled to match the .btn-loc button it replaces. Same specificity
   trick as .stop-badge-input/.stop-flags-input above (element + attribute
   selector) — without the input[type=number] prefix, the generic
   input[type=number] rule (width:100%, 12px padding, 16px font) wins and
   the input balloons to fill the row instead of matching the button. */
input[type=number].stop-num-input {
  width: 34px; padding: 3px 6px; margin: 0; border: 1px solid var(--border); border-radius: 5px;
  font-size: 10px; font-weight: 600; font-family: inherit; text-align: center;
  color: var(--text); background: white; -moz-appearance: textfield; outline: none;
}
.stop-num-input::-webkit-outer-spin-button, .stop-num-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stop-num-input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(45,106,79,0.15); }
.retry-addr-input, .addr-fix-input { width: 100%; padding: 4px 6px; border: 1px solid #fca5a5; border-radius: 4px; font-size: 16px; color: var(--text); background: white; outline: none; }
.retry-addr-input:focus, .addr-fix-input:focus { border-color: var(--error); box-shadow: 0 0 0 2px rgba(220,38,38,0.15); }
/* Retry/Place-on-map live below the address input, not beside it — with
   both buttons competing for horizontal space in the same row as the input
   (narrow desktop sidebar), the input was getting squeezed down to just a
   few visible characters. Wrapping lets both buttons stay full-size while
   the input keeps the row's full width. */
.stop-err-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.btn-retry, .btn-place-map, .btn-retry-addr { flex-shrink: 0; align-self: flex-start; padding: 9px 10px; color: var(--text); font-size: 11px; }
.btn-retry:disabled, .btn-place-map:disabled, .btn-retry-addr:disabled { opacity: 0.6; cursor: default; }
/* Verify's inline start/storage-address fix row — same visual language as a
   failed stop-list row (.stop-item.err) but not a real stop, so it's kept as
   its own class rather than reusing .stop-item and colliding with that
   class's delegated row-click/data-stop-index wiring. */
/* align-items: stretch (the flex default) so .btn-retry-addr matches the
   input's height instead of sitting shorter and top-aligned (issue #30) —
   the input's font-size:16px (prevents iOS auto-zoom-on-focus) makes it
   naturally taller than the button's font-size:11px otherwise. */
.addr-fix-row { display: flex; align-items: stretch; gap: 8px; padding: 10px 8px; border-radius: 6px; border: 1px solid #fca5a5; background: #fef2f2; font-size: 12px; }
/* .btn-retry-addr shares its base look with .btn-retry/.btn-place-map above,
   including align-self: flex-start — right for those two, which sit in a
   flex-wrap row below their input rather than beside it, but wrong here:
   .addr-fix-row wants this button to stretch to match the input (the whole
   point of the rule above), so it needs its own override back to stretch. */
.addr-fix-row .btn-retry-addr { align-self: stretch; }

/* Route output */
/* Issue #50: mobile-only overview map — desktop already shows the shared,
   full-size S.map beside #panel for this step. Taller than #delivery-minimap
   (108px) since this needs to fit an entire route, not one close-up stop. */
#route-overview-minimap { height: 140px; border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }
/* Issue #87 follow-up: the mini-map is too small to actually inspect —
   tapping it opens the same route at a real, pannable size instead. */
#route-overview-minimap-wrap { cursor: pointer; margin-bottom: 12px; }
#route-overview-expanded { height: 60vh; border-radius: 10px; overflow: hidden; margin-top: 8px; }
.run-section { margin-bottom: 16px; }
.run-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 2px solid; }
.run-dot { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; color: white; flex-shrink: 0; }
.run-title { font-family: var(--font-display); font-weight: 700; font-size: 13px; }
.run-meta { font-size: 11px; color: var(--muted); margin-left: auto; }

/* Issue #56: a one-time entrance for the finished-route moment — the
   .route-output-enter class is only added by renderRouteOutput() on a
   genuine arrival at this screen (animate=true), never on the in-place
   re-render a drag-and-drop reorder triggers, so the flourish never
   replays while someone's actively editing. Each .run-section gets its
   own animation-delay (set inline, per run index) so runs stagger in
   rather than all popping at once. */
@keyframes routeTitleSweep { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
@keyframes routeRunIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.route-output-enter > .section-title { animation: routeTitleSweep 0.35s ease-out both; }
.route-output-enter > .run-section { opacity: 0; animation: routeRunIn 0.35s ease-out both; }
@media (prefers-reduced-motion: reduce) {
  .route-output-enter > .section-title,
  .route-output-enter > .run-section { animation: none; opacity: 1; }
}
/* min-height keeps a run that's been dragged empty (issue #35) as a real
   drop target instead of collapsing to 0px and becoming impossible to drag
   a stop back into. */
/* Issue #62: Plan's .stop-item cards (white, bordered, one per row) read as
   an active workspace — still arranging things. Route Output is the
   finished, deliverable artifact that workspace produced, so its stop list
   gets its own warm "printed manifest" surface instead of reusing that
   card look — a parchment tint on the whole list, not per-row cards, plus
   quieter (not gone — dragging between runs still works, issue #35)
   drag handles, since rearranging is a secondary action here rather than
   the primary reading of the screen the way it is on Plan. */
.run-stops-list { min-height: 12px; border: 1px solid #EDE4D3; border-radius: 8px; padding: 2px 10px; }
.route-stop { display: flex; align-items: flex-start; gap: 8px; padding: 7px 0; border-bottom: 1px solid #EDE4D3; font-size: 12px; line-height: 1.35; }
.route-stop:last-child { border-bottom: none; }
.route-stop .drag-handle { opacity: 0.35; transition: opacity 0.15s; }
.route-stop:hover .drag-handle, .route-stop.sortable-chosen .drag-handle { opacity: 1; }
.route-num { font-weight: 700; flex-shrink: 0; width: 20px; padding-top: 1px; }
.storage-row { background: #ecfdf5; border: 1px solid #a7f3d0; padding: 6px 10px; border-radius: 6px; font-size: 11px; font-weight: 700; color: var(--accent); margin: 6px 0; display: flex; align-items: center; gap: 5px; }

/* Alerts */
.alert { padding: 9px 11px; border-radius: 6px; font-size: 12px; margin-bottom: 10px; line-height: 1.4; }
.alert-warn { background: #fffbeb; border: 1px solid #fde68a; color: #78350f; }
.alert-error { background: #fef2f2; border: 1px solid #fca5a5; color: #7f1d1d; }
.alert-success { background: #f0fdf4; border: 1px solid #86efac; color: #14532d; }
.alert-info { background: #f0f9ff; border: 1px solid #bae6fd; color: #0c4a6e; }

/* Spinner */
.spinner { width: 32px; height: 32px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; margin: 24px auto 12px; }
@keyframes spin { to { transform: rotate(360deg); } }
.loading-msg { text-align: center; font-size: 13px; color: var(--muted); }
.loading-sub { text-align: center; font-size: 11px; color: var(--muted); margin-top: 4px; }

.copy-ok { color: var(--success); font-size: 12px; text-align: center; margin-top: 4px; display: none; }
.reasoning-box { font-size: 11px; color: var(--muted); font-style: italic; background: var(--bg); border-radius: 6px; padding: 8px 10px; margin-bottom: 14px; line-height: 1.5; }

/* Delivery Mode — full-screen, mobile-first layout used during the actual
   flag route, as opposed to the desktop-oriented planning steps. */
#app.delivery-mode #panel { width: 100%; border-right: none; }
#app.delivery-mode #map { display: none; }
#app.delivery-mode .steps { display: none; }

/* Fixed height (not min-height) so the card doesn't jump around between a
   short street address and a long entrance description — the rest of the
   screen (nav button, map, notes) then sits at a consistent Y position
   regardless of which stop is showing. */
.delivery-card {
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 16px; text-align: center; margin-bottom: 4px;
  height: 108px; display: flex; flex-direction: column; justify-content: center; overflow: hidden;
}
.delivery-card.is-storage { background: #ecfdf5; border-color: #a7f3d0; }
.delivery-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 6px; }
.delivery-card.is-storage .delivery-label { color: var(--accent); }
.delivery-flags-inline { font-size: 13px; color: var(--accent); letter-spacing: 0; text-transform: none; }
/* Clamped to 2 lines with an ellipsis — the standard multi-line-truncation
   pattern — so a long address can't push past the card's fixed height above. */
.delivery-address {
  font-size: 18px; font-weight: 700; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; text-overflow: ellipsis;
}
.delivery-notes { font-size: 13px; font-weight: 700; color: var(--text); text-align: center; margin-top: 10px; padding: 24px 8px; line-height: 1.4; }

.flag-photo-preview { display: block; width: 100%; max-height: 60vh; object-fit: contain; border-radius: 8px; border: 1px solid var(--border); }

.map-marker-popup-photo { margin: 6px 0; }
.map-marker-popup-photo-img { display: block; width: 100%; max-width: 220px; max-height: 160px; object-fit: cover; border-radius: 6px; margin-bottom: 4px; }

.btn-lg { padding: 22px 16px; font-size: 16px; text-decoration: none; }

.delivery-title-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.mp-select-inline {
  width: auto; flex-shrink: 0; padding: 3px 6px; font-size: 10px; border-radius: 5px;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
}
.delivery-autoadvance-row { display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 11px; color: var(--muted); cursor: pointer; }

/* Margin matches .btn-lg's own vertical padding (22px) rather than the
   generic .mt24 utility, so the gap above the button reads the same as the
   gap already built into the button itself. */
.delivery-nav-row { display: flex; gap: 8px; margin-top: 22px; }
.delivery-nav-row .btn-lg { flex: 1; min-width: 0; }
.btn-disabled { opacity: 0.4; cursor: default; pointer-events: none; }

/* Matches .delivery-card's fixed height so the info block and map read as a
   matched pair instead of two arbitrarily-sized boxes stacked together. */
#delivery-minimap { height: 108px; border-radius: 10px; margin-top: 12px; overflow: hidden; border: 1px solid var(--border); }

.delivery-row { display: flex; gap: 8px; }
.delivery-row .btn { flex: 1; }
.btn-tap { padding: 13px 16px; font-size: 13px; }

/* Full-screen list view (renderDeliveryList()) — no max-height/overflow of
   its own since #panel-body already scrolls the whole panel, same as every
   other screen in the app. */
.delivery-list-full { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; }
.delivery-list-item { display: flex; align-items: center; gap: 8px; padding: 12px 8px; border-radius: 6px; font-size: 12px; cursor: pointer; }
.delivery-list-item:hover { background: var(--bg); }
.delivery-list-item.current { background: #edf7f2; font-weight: 700; }
.delivery-list-num { width: 18px; flex-shrink: 0; color: var(--muted); font-weight: 700; font-size: 10px; }

/* Reusable modal dialog (onboarding, save/resume route name entry).
   z-index set well above Leaflet's own pane/control stacking (typically
   caps under 1000) since this overlay lives outside #map's DOM subtree. */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 2000; padding: 20px; }
.modal-card { background: var(--surface); border-radius: 10px; padding: 20px; max-width: 360px; width: 100%; box-shadow: 0 10px 30px rgba(0,0,0,.25); }
.modal-card p { font-size: 13px; line-height: 1.5; margin-bottom: 14px; }
.modal-checkbox-row { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; color: var(--muted); cursor: pointer; margin-top: 4px; }
.modal-intro-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.modal-intro-list div { font-size: 12.5px; line-height: 1.4; }

/* Issue #64: pure-FYI modals (onboarding, how-to-reorder, delivery intro)
   back off visually from real-decision modals (column-mapping warning,
   capacity warning) — lighter backdrop dim and card elevation, paired with
   a ghost-styled (not primary) dismiss button at each call site, so a
   "just tap through" modal doesn't compete for attention the way a modal
   that actually needs a decision should. */
.modal-overlay--info { background: rgba(0,0,0,0.25); }
.modal-card--info { box-shadow: 0 4px 16px rgba(0,0,0,.12); }

/* Mobile Setup wizard — one question per screen. Only ever rendered on
   mobile widths, so no media query needed here. */
.wizard-progress { display: flex; justify-content: center; gap: 6px; margin: 8px 0 16px; }
.wizard-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
.wizard-dot.active { background: var(--accent); width: 20px; border-radius: 4px; }
.wizard-dot.done { background: var(--success); }

.chip-toggle {
  display: block; width: auto; margin: 12px auto 0; padding: 8px 16px;
  border-radius: 99px; border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font-size: 12px; font-weight: 600; cursor: pointer;
}
.chip-toggle:hover { border-color: var(--accent); }
.chip-toggle[aria-expanded="true"] { background: var(--accent); color: white; border-color: var(--accent); }

/* Pinned to #map's own top-right corner — Leaflet sets .leaflet-container to
   position:relative and doesn't clear pre-existing children on init, so
   this static child anchors correctly whether #map is a flex sibling of
   #panel (desktop) or full-screen (mobile map-first). Top-right avoids both
   Leaflet's own zoom control (top-left) and, on notched/Dynamic-Island
   phones, the status-bar cutout that sits centered at the very top. z-index
   comfortably above Leaflet's own panes/controls (cap under ~1000, see
   .modal-overlay's note), but this overlay lives inside #map so it doesn't
   need the modal's z-index of 2000. */
#tap-status-overlay {
  position: absolute; top: 8px; right: 8px; z-index: 1000;
  display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 99px;
  padding: 6px 10px; font-size: 12px; font-weight: 600; color: var(--text);
  box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
#btn-tap-undo, #btn-tap-done {
  border: none; background: var(--bg); color: var(--accent); border-radius: 99px;
  padding: 3px 8px; font-size: 11px; font-weight: 700; cursor: pointer;
}
#btn-tap-undo:hover, #btn-tap-done:hover { background: var(--border); }
/* Issue #74: drag-handle serves no purpose while tap-to-reorder mode is
   active (Sortable isn't even initialized then, see wireVerifyHandlers()) —
   hiding it removes a dead, misleading affordance and signals the row
   itself is now the tap target. */
.stop-list.tap-reorder-active .drag-handle { display: none; }
.stop-list.tap-reorder-active .stop-item:not(.err) { cursor: pointer; }

/* Mobile: the planning wizard (Setup/Geocode/Verify/Route) is desktop-
   oriented — on-site usage is almost always Delivery Mode instead, so the
   step bar and full planning form collapse out of the way by default.
   Placed at the end of the file so it reliably wins the cascade over the
   equal-specificity base rules (.steps, #panel) defined earlier. */
@media (max-width: 640px) {
  .steps { display: none; }
  /* Desktop's #panel is a white sidebar next to the map, so var(--surface)
     is right there — but on mobile #panel *is* the whole screen (no map
     beside it), so it needs the page's cream background instead, or the
     new --bg color from issue #3 never actually shows. */
  #panel { width: 100%; background: var(--bg); }

  /* App-like card treatment for the mobile wizard's step content — more
     breathing room than the compact desktop form, since mobile only ever
     shows one step (and one card) at a time. */
  .wizard-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 18px 16px;
    margin-bottom: 16px;
  }
  .wizard-card .section-title { font-size: 12px; margin-bottom: 12px; }
  .wizard-card .field { margin-bottom: 14px; }

  /* Resume-route picker rows, mobile only — desktop keeps the compact
     .delivery-list-item look shared with Delivery Mode's own stop list. */
  #resume-route-list .delivery-list-item {
    padding: 16px 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    margin-bottom: 6px;
    background: var(--surface);
  }
  #resume-route-list .delivery-list-item strong { font-size: 14px; }

  /* Verify step, mobile only: map fills the screen behind a bottom-sheet
     panel instead of the map being squeezed off-screen by a full-width
     sidebar — see renderVerify()'s isMobile branch. */
  #app.verify-mapfirst main { position: relative; }
  #app.verify-mapfirst #map { position: absolute; inset: 0; z-index: 1; height: 100%; }
  #app.verify-mapfirst #panel {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
    max-height: 65vh; border-radius: 14px 14px 0 0;
    box-shadow: 0 -6px 24px rgba(0,0,0,.18);
  }
}
