/* UI chrome only -- header, buttons, active/hover states, borders. Deliberately
   not used for anything that encodes data: the hazard rating colours, the
   shade-level line ramp, and the canopy tint all live in src/legStyle.js and
   includes/canopy.php and stay exactly as they are. */
/* Design tokens -- including --ink/--charcoal/--tint, which this file uses
   throughout -- live in css/tokens.css, shared with the marketing site so the
   two cannot drift into looking like different products. */

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  margin: 0;
  font-family: var(--mk-sans);
  color: var(--mk-ink);
}

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

/* -- Header ---------------------------------------------------------------- */

/* Kept deliberately thin: every pixel here is one the map doesn't get. What's
   left is identity and navigation; the current route's own name/save/actions
   live in #current-route-bar, just below this. */
/* Deliberately the same object as .marketing-header in css/marketing.css:
   same height token, same paper surface, same hairline, same type sizes. The
   app used to wear a thin near-black strip, which read as a different product
   the moment you crossed over from the site. It costs the map real estate --
   that is the trade, made knowingly. */
#app-header {
  height: var(--mk-header-h);
  background: var(--mk-paper);
  border-bottom: 1px solid var(--mk-line);
  color: var(--mk-ink);
  flex-shrink: 0;
}

/* Mirrors .marketing-header .mk-shell's box model exactly (max-width,
   centering, 32px padding) -- a separate element from #app-header itself,
   same split the marketing header uses, so the header's own paper background
   and bottom hairline still span the full window edge to edge while just the
   content inside is capped and centered. Without this split, capping
   #app-header directly would have capped its background/border too, leaving
   blank margins with no paper colour or hairline showing through them.
   Before this, app-header content ran edge to edge with no cap at all while
   the marketing site's stayed centered at 1120px -- a real mismatch invisible
   at the narrow widths this was first checked at, obvious on any normal
   monitor. */
.app-header-shell {
  display: flex;
  align-items: center;
  gap: 28px;
  height: 100%;
  max-width: var(--mk-shell);
  width: 100%;
  margin: 0 auto;
  padding: 0 32px;
  box-sizing: border-box;
}

/* The mark plus the wordmark, linking back to the marketing site -- the same
   pairing, sizes and spacing as .marketing-logo. */
#app-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--mk-ink);
  flex-shrink: 0;
}

#app-brand svg {
  display: block;
  flex-shrink: 0;
}

#app-header h1 {
  font-size: 16.5px;
  font-weight: 660;
  letter-spacing: -0.02em;
  margin: 0;
  white-space: nowrap;
}

/* Route name + Export GPX, at the top of Route Tools' body -- previously a
   dedicated #current-route-bar above the map (a permanent strip of height
   the map lost even when all it held was a name field), before that the old
   floating "My Routes" panel. #back-to-routes-link, the bar's own link back
   to the library, was dropped rather than moved: app_header()'s own
   "My Routes" CTA (#app-nav-cta) already goes to the same place, so it was a
   second control doing the same job -- its dirty-check-before-leaving guard
   moved onto the CTA instead, see src/main.js. */
/* Sized for a small floating pill -- back to floating over the map, in
   #map-notices, after a stop in a dedicated #current-route-bar above it.
   Still just status text plus a Save button plus the first-save naming
   popover throughout, so the same compact treatment has fit the whole time. */
#save-status {
  font-size: 13px;
}

/* The one Save button -- My Routes' own always-visible one was dropped when
   its panel was, since two buttons calling the same requestSave() side by
   side was just confusing. Only shown while the route is dirty. */
#header-save-btn {
  padding: 5px 14px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: 100px;
  background: var(--mk-accent);
  color: #fff;
  cursor: pointer;
  flex-shrink: 0;
}

#header-save-btn:hover {
  background: var(--mk-accent-hover);
}

#header-save-btn[hidden] {
  display: none;
}

/* Mirrors .marketing-nav exactly, including the margin-left:auto that pushes
   the whole group -- links and button together -- to the right-hand end of the
   header. Without it the links bunch up next to the wordmark and the header
   stops looking like the site's. */
#app-nav {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-left: auto;
  font-size: 14.5px;
}

#app-nav a:not(.mk-btn) {
  color: var(--mk-ink-2);
  text-decoration: none;
}

#app-nav a:not(.mk-btn):hover {
  color: var(--mk-ink);
}

/* "My Routes" and "Map" are different lengths (134px vs 91px, measured) --
   without a shared min-width the header visibly resizes the moment you cross
   between /map and /routes. 135px comfortably fits the longer of the two;
   justify-content centers "Map" inside the extra room rather than leaving it
   hugging one side. */
#app-nav-cta {
  min-width: 135px;
  justify-content: center;
}

/* -- Layout ---------------------------------------------------------------- */

#app-body {
  flex: 1;
  display: flex;
  min-height: 0;
}

#map-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

#map-container {
  flex: 1;
  position: relative;
  min-height: 0;
  /* Nothing may paint outside the map. Found on a 375px screen: the notices
     stack grows upward from the bottom of this box, and with six of them and
     a short map it ran straight over the site header. Everything inside here
     is map furniture; the command bar's popovers are a sibling, so clipping
     costs nothing. */
  overflow: hidden;
}

#map {
  position: absolute;
  inset: 0;
}

/* -- Map notices -------------------------------------------------------------
   Layer status, floating over the map: loading, zoom-too-far-out, failures,
   and the empty-route call to action. Bottom centre keeps the stack clear of
   the panels (top left), the search box (top, beside them) and Leaflet's
   zoom control (top right). Route save state (#save-banner) is back in this
   stack after a stop in a dedicated #current-route-bar above the map -- that
   bar cost the map a permanent strip of height for a name field and a status
   pill that, it turned out, reads fine hiding itself like every other notice
   here when there's nothing to say.

   The layer notices used to live inside the Route Tools panel, where they were
   easy to miss while the thing they described was happening out on the map. */

/* The stack itself is a bare positioning wrapper -- no background, no border.
   Each notice carries its own pill, and hides itself when it has nothing to
   say, so an idle map shows nothing at all and no JS has to manage it. */
#map-notices {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: calc(100% - 32px);
  /* Six "zoom in to see..." pills is a legitimate state (zoomed out, all four
     layers on), and on a short map that is taller than the map. Scroll the
     stack rather than let it disappear under a clip. */
  max-height: calc(100% - 32px);
  overflow-y: auto;
  /* The gaps between pills stay live for panning the map underneath. */
  pointer-events: none;
}

#map-notices > * {
  pointer-events: auto;
}

/* One pill shape, shared by the layer notices and #save-banner, both in
   #map-notices, so a save-status pill reads as the same kind of object as
   every other one here. */
.map-notice,
#save-banner {
  margin: 0;
  padding: 8px 16px;
  border-radius: 100px;
  border: 1px solid var(--mk-line);
  background: var(--mk-paper);
  box-shadow: var(--mk-shadow-pop);
  font-size: 13px;
  line-height: 1.35;
  color: var(--mk-ink-2);
  max-width: 100%;
}

/* Layer status: loading, zoom-too-far-out, and failures. Grey rather than
   green -- same shape, so it reads as the same kind of message, but grey means
   "the map is telling you something" where green means "you have something to
   do". */
.map-notice {
  /* --mk-paper-3 rather than -2: it matches the depth of the green's
     --mk-accent-soft, so the two pills sit at the same visual weight instead
     of the grey one looking like an unstyled white pill. */
  background: var(--mk-paper-3);
  border-color: var(--mk-line-strong);
  color: var(--mk-ink-2);
  font-weight: 550;
  text-align: center;
}

.map-notice:empty {
  display: none;
}

/* The one notice that means "something to do" rather than "the map is
   telling you something" -- accent green instead of the grey every other
   notice gets, per the convention above. */
#empty-route-hint {
  background: var(--mk-accent-soft);
  border-color: var(--mk-accent-border);
  color: var(--mk-accent);
  font-weight: 600;
}

#save-banner {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  /* Less on the right than the left: the Save button brings its own. */
  padding-right: 10px;
}

#save-banner[hidden] {
  display: none;
}

/* Unsaved work is the one state worth actually catching the eye. */
#save-banner.is-dirty {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  font-weight: 550;
}

#save-banner #save-status {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}


/* -- Top-left stack: search, then the route name -----------------------------
   ONE COLUMN, NOT TWO NEIGHBOURS. Side by side these read oddly on desktop
   and stacked badly on a phone (owner, 2026-09-20). As a stack nothing has
   to re-arrange at any width -- the column just narrows.

   The search field is a plain input at its production size. It briefly grew
   into a card with a glyph on one side and a button on the other, which made
   it read as a banner across the top of the map; the furniture is inside the
   field now. */

#map-topleft {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1000;
  width: 320px;
  max-width: calc(100% - 76px); /* clears Leaflet's zoom control */
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

#map-topleft > * {
  pointer-events: auto;
}

#map-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 4px 0 9px;
  background: #fff;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-panel);
}

#map-search:focus-within {
  border-color: var(--mk-accent);
  box-shadow: 0 0 0 3px rgba(27, 94, 32, 0.15), var(--mk-shadow-panel);
}

.map-search-glyph {
  flex-shrink: 0;
  color: var(--mk-ink-3);
}

#search-input {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
  border: 0;
  background: none;
  font-size: 13px;
  font-family: inherit;
  color: var(--mk-ink);
}

#search-input:focus {
  outline: none;
}

/* src/mapInit.js geolocates once on page load and never again, so before
   this there was no way back to where you are after panning away. */
#locate-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper-2);
  color: var(--mk-ink-2);
  cursor: pointer;
}

#locate-btn:hover {
  color: var(--mk-accent);
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
}

#locate-btn.is-busy {
  opacity: 0.5;
  pointer-events: none;
}

#search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 1;
  list-style: none;
  margin: 0;
  padding: 4px;
  max-height: 240px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-pop);
}

#search-results .search-result {
  padding: 7px 9px;
  font-size: 13px;
  border-radius: var(--mk-radius-xs);
  cursor: pointer;
}

#search-results .search-result:hover,
#search-results .search-result.active {
  background: var(--mk-paper-2);
}

/* The route name, under the search. Hidden until there is a route: an empty
   name field floating over the map before anything has been drawn is a box
   asking a question nobody asked. */
#route-name-card {
  padding: 7px 10px 8px;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-panel);
}

#route-name-input {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--mk-ink);
}

#route-name-input:focus {
  outline: none;
  color: var(--mk-accent);
}

#route-name-stats {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
  font-variant-numeric: tabular-nums;
}

/* Quiet until it has something in it. A route's description is worth having
   -- where it starts, what the surface is like, which bit to skip in the
   rain -- but it is not worth a text box sitting open over the map, so this
   is one line that grows to fit what is typed (src/main.js sizes it). */
#route-description-input {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 0;
  border: 0;
  background: none;
  resize: none;
  overflow: hidden;
  font: inherit;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--mk-ink-2);
}

#route-description-input::placeholder {
  color: var(--mk-ink-3);
}

#route-description-input:focus {
  outline: none;
  color: var(--mk-ink);
}

/* -- Layers panel ------------------------------------------------------------
   "Layers", top right: the four layer toggles, the source feeding each one IN
   THIS VIEW, the sun controls, and the key. Called "Layers" in both states
   since 2026-09-20 -- it read "What the map shows" open and "Layers" stowed,
   so expanding it renamed it. Three things
   that used to be three separate pieces of furniture -- a Route Tools
   accordion, a credit strip along the bottom of the map, and a Legend
   accordion that closed the tools when you opened it -- and all three answer
   the same question.

   Open by default and collapsible, both remembered (owner's choice).
   Collapsing hides the BODY -- the header stays, at full width, with its
   chevron and the icons of whatever is on. */

#layers-panel {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 1000;
  width: 258px;
  max-width: calc(100% - 24px);
  /* Takes only the height it needs and grows when the key is opened -- it
     used to be pinned top AND bottom, so it ran the full height of the map
     whatever was in it. The cap is what stops an opened key running off the
     bottom; the body scrolls past that point. */
  max-height: calc(100% - 24px);
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-panel);
  overflow: hidden;
}

/* THE HEADER IS ALSO THE COLLAPSED PANEL. A separate #layers-stub button
   used to stand in when this was closed, which made the collapsed state a
   different object at a different width with no chevron on it. Collapsing
   hides #layers-panel-body alone now, so the width, the title and the
   chevron all survive it and there is nothing to keep in step. */
#layers-panel-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-bottom: 1px solid var(--mk-line);
  background: var(--mk-paper-2);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mk-ink-2);
  cursor: pointer;
  text-align: left;
}

/* Nothing under it to divide from. */
#layers-panel-header[aria-expanded='false'] {
  border-bottom: 0;
}

#layers-panel-header:hover {
  color: var(--mk-ink);
}

.layers-title-glyph {
  color: var(--mk-accent);
}

/* Pinned to the right edge whichever state the panel is in -- the layer
   icons push up against it from the left rather than displacing it. */
.layers-chev {
  flex-shrink: 0;
  margin-left: auto;
  color: var(--mk-ink-3);
  transform: rotate(180deg);
}

#layers-panel-header[aria-expanded='false'] .layers-chev {
  transform: none;
}

#layers-panel-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  /* THE PANEL MUST NOT MOVE WHEN THE KEY OPENS. The panel is a fixed 258px;
     opening the key makes this body overflow, and a 15px scrollbar appearing
     inside it shrank the content box by 15px -- every toggle, label and
     source credit slid left, then slid back when the key closed. Reserving
     the gutter whether or not it is in use costs those 15px permanently and
     buys an interface that holds still (owner, 2026-09-20). */
  scrollbar-gutter: stable;
}

.layers-group {
  padding: 7px 11px 9px;
  border-bottom: 1px solid var(--mk-line);
}

.layers-group:last-child {
  border-bottom: 0;
}

.layer-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 8px;
}

/* "Show exposed ground", indented under Wind and shown only while Wind is on.
   Its own [hidden] rule because .layer-row's display: flex outranks the
   attribute's (the trap recorded in CLAUDE.md). */
.layer-row-sub {
  padding-left: 30px;
}

.layer-row-sub[hidden] {
  display: none;
}

.layer-row-sub .layer-toggle {
  padding: 2px 0 5px;
  font-size: 12px;
}

.layer-toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  font-size: 13px;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.layer-toggle input[type='checkbox'] {
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  accent-color: var(--mk-accent);
  cursor: pointer;
}

/* Each layer's icon is drawn in that layer's OWN colour on the map, so the
   panel starts explaining the map before anyone opens the key -- and the key
   becomes a confirmation rather than a lookup. The colours are set from the
   layer modules' own constants by src/main.js, never hardcoded here. */
.layer-icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--mk-radius-xs);
  border: 1px solid var(--mk-line);
  background: var(--mk-paper-2);
  color: var(--mk-ink-3);
}

.layer-toggle input:checked ~ .layer-icon {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--layer-colour, var(--mk-accent));
}

/* Sun is not a data layer with a map colour of its own -- it is the gold the
   time slider and the "full sun" end of the ramp already use. */
.layer-toggle input:checked ~ .layer-icon-sun {
  color: var(--mk-sun);
  border-color: #f0d9a8;
  background: #fdf6e6;
}

.layer-toggle input:checked ~ .layer-label {
  color: var(--mk-ink);
}

.layer-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.layer-source {
  flex-shrink: 0;
  max-width: 45%;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted var(--mk-line-strong);
  background: none;
  font: inherit;
  font-size: 10.5px;
  color: var(--mk-ink-3);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.layer-source:hover {
  color: var(--mk-accent);
  border-bottom-color: var(--mk-accent);
}

.layer-source.is-none {
  color: var(--mk-danger);
  border-bottom-color: var(--mk-danger-border);
}

.layer-source-detail {
  flex-basis: 100%;
  margin: 2px 0 6px;
  padding: 8px 9px;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper-2);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--mk-ink-2);
}

.layer-source-block + .layer-source-block {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--mk-line);
}

.layer-source-name {
  margin: 0 0 3px;
  font-weight: 600;
  color: var(--mk-ink);
}

.layer-source-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0 0 3px;
  font-size: 10.5px;
  color: var(--mk-ink-3);
}

.layer-source-badge {
  padding: 1px 5px;
  border-radius: 999px;
  border: 1px solid var(--mk-line-strong);
  background: #fff;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.layer-source-badge.is-surveyed { color: var(--mk-accent); border-color: var(--mk-accent-border); background: var(--mk-accent-soft); }
.layer-source-badge.is-crowd { color: #8a5b00; border-color: #f0d9a8; background: #fdf6e6; }
.layer-source-badge.is-sensed { color: #1565c0; border-color: #c5dcf5; background: #eff5fd; }
.layer-source-badge.is-modelled { color: #5b4b8a; border-color: #d6cfe8; background: #f4f1fa; }

.layer-source-note,
.layer-source-none {
  margin: 0;
}

.layer-source-report {
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11px;
  color: var(--mk-accent);
  text-decoration: underline;
  cursor: pointer;
}

#sun-controls {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
}

#sun-date,
#sun-time-exact {
  padding: 5px 7px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  font: inherit;
  font-size: 12px;
  background: #fff;
  color: var(--mk-ink);
}

#sun-time {
  width: 100%;
  accent-color: var(--mk-sun);
}

#sun-time-precise {
  display: flex;
  align-items: center;
  gap: 6px;
}

#sun-time-exact {
  flex: 1;
  min-width: 0;
}

#sun-time-now-btn {
  padding: 5px 9px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

#sun-time-now-btn:hover {
  border-color: var(--mk-accent);
  color: var(--mk-accent);
}

/* "When you run" -- the date and time are shared by Sun-aware shade and
   wind, so they are labelled as the run's, not the sun's. */
.when-label {
  margin: 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--mk-ink-3);
}

/* -- Wind in the layers panel ---------------------------------------------- */

#wind-controls {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--mk-line);
}

#wind-readout {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--mk-ink);
}

.wind-pace {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}

.wind-pace span:first-child {
  flex: 1;
  min-width: 0;
}

#wind-pace-input {
  width: 58px;
  padding: 4px 6px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  background: #fff;
  color: var(--mk-ink);
}

#wind-pace-input:focus {
  outline: 2px solid var(--mk-accent-border);
  border-color: var(--mk-accent);
}

/* The direction arrows on the route (src/routeArrows.js). The rotation is
   set inline per arrow; this only centres the glyph on its point. */
.route-arrow {
  background: none;
  border: 0;
  pointer-events: none;
}

.route-arrow span {
  display: block;
  width: 12px;
  height: 12px;
  line-height: 0;
  transform-origin: 50% 50%;
}

.wind-streaks-canvas {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

.layer-source-link {
  display: inline-block;
  margin-top: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--mk-accent);
}

/* ONE LINE, ALWAYS THE SAME LINE, AND ALL OF IT THE ANSWER. This reads out
   whatever the cursor is over. It used to hide itself when there was nothing
   to say and wrap when there was a lot, so moving the mouse grew and shrank
   the panel; pinning a "Under cursor" label beside it fixed that and created
   a worse one, because the label took a third of a 221px row and the answer
   was clipped mid-word. No label now: the value owns the row, and at rest it
   says what the row is for rather than sitting blank. */
#canopy-cursor-readout {
  display: flex;
  align-items: center;
  margin: 7px 0 0;
  height: 17px;
  font-size: 11.5px;
  line-height: 17px;
  color: var(--mk-ink-2);
}

.canopy-cursor-value {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.canopy-cursor-value.is-placeholder {
  color: var(--mk-ink-3);
}

/* The key, folded into this panel rather than living as its own accordion:
   the place you go to switch a layer on is the place to learn what it draws.
   Collapsed by default so it is present without being in the way.

   IT WEARS THE PANEL HEADER'S OWN GREY BAR. Inside a .layers-group it was a
   line of small caps indented to the same margin as the sun controls above
   it, which read as the last item of that group rather than the start of a
   section (owner, 2026-09-20). Same background, same full width, same type
   as #layers-panel-header, so the panel has two obvious sections instead of
   one long column. */
.layers-section {
  border-top: 1px solid var(--mk-line);
}

.layers-group + .layers-section {
  border-top: 0; /* the group above already drew this line */
}

#legend-header {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 11px;
  border: 0;
  background: var(--mk-paper-2);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mk-ink-2);
  cursor: pointer;
  text-align: left;
}

#legend-header[aria-expanded='true'] {
  border-bottom: 1px solid var(--mk-line);
}

#legend-header:hover {
  color: var(--mk-ink);
}

/* The key icon IS the shade ramp (includes/mapIcons.php), so unlike the
   panel's tree it brings its own three colours and must not be tinted. */
.legend-title-glyph {
  flex-shrink: 0;
}

.legend-chev {
  flex-shrink: 0;
  margin-left: auto;
  color: var(--mk-ink-3);
  transition: transform 0.15s ease;
}

#legend-header[aria-expanded='true'] .legend-chev {
  transform: rotate(180deg);
}

#map-legend-body {
  padding: 8px 11px 10px;
  /* SIZED TO THE PANEL IT LIVES IN. The key moved inside the layers panel on
     2026-09-20 and brought no font size of its own, so it inherited the
     document's 16px and sat beside 13px layer labels and 10.5px source
     credits -- the largest type on the page, in its smallest panel (owner).
     Set once here, so the .legend-* rules further down only describe shape. */
  font-size: 11.5px;
}

/* WHICH layers are on, not how many. "· 3 on" was a number you had to open
   the panel to decode; these are the same icons the panel itself uses, each
   in its own layer's map colour, so the stowed panel still answers the
   question it exists to answer (owner, 2026-09-20). Filled by src/main.js.

   They sit in the header, immediately LEFT OF THE CHEVRON and vertically
   centred on it, and only while the panel is closed -- open, the rows
   themselves already say this and repeating it in the header would be
   noise. */
#layers-stub-icons {
  display: none;
  align-items: center;
  gap: 5px;
  /* Holds its place when nothing is on, so the header's chevron doesn't
     shift as layers are switched off -- same reasoning as the cursor
     readout below. */
  min-width: 14px;
  margin-left: auto;
  padding-left: 9px;
  border-left: 1px solid var(--mk-line-strong);
}

#layers-panel-header[aria-expanded='false'] #layers-stub-icons {
  display: inline-flex;
}

/* With the icons present they take the free space, so the chevron just
   follows them rather than claiming it too. */
#layers-panel-header[aria-expanded='false'] .layers-chev {
  margin-left: 0;
}

.layers-stub-icon {
  display: flex;
  color: var(--layer-colour, var(--mk-accent));
}

#layers-stub-icons:empty::after {
  content: '\2014'; /* em dash: nothing is on */
  color: var(--mk-ink-3);
  font-size: 11px;
}

/* -- Command bar -------------------------------------------------------------
   A SIBLING OF THE MAP, NOT SOMETHING FLOATING OVER IT, docked directly on
   top of the elevation panel so the two move as one object -- the owner's
   words: "sticky to the top of the elevation profile stats bar area so it
   doesn't look weird when it's expanded and they move together". That is the
   whole difference between this and the floating pill of mixed buttons it
   replaced, which he called "not very professional" and was right about.

   Arrangement is the one he picked: mode on the left, distance and target
   beside it, actions right-aligned with Save separated out, and the profile
   toggle last. */

#command-bar {
  position: relative;
  /* ABOVE THE MAP'S OWN OVERLAYS, not just above the map. This was z-index:2,
     and because `position:relative` + a z-index creates a stacking context,
     every popover inside the bar was CAPPED at 2 however high its own
     z-index -- so the layers panel (z-index 1000, a child of #map-container,
     which has no stacking context of its own) painted straight over the
     More menu, the save-name prompt and the rest. They opened correctly and
     were invisible. Map furniture tops out at 1000; the docked bar and
     everything it opens sits above all of it. */
  z-index: 1200;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: 8px;
  gap: 9px;
  padding: 7px 14px;
  background: #fff;
  border-top: 1px solid var(--mk-line);
}

.cmd-sep {
  flex-shrink: 0;
  width: 1px;
  height: 22px;
  background: var(--mk-line);
}

.cmd-spacer {
  flex: 1 1 auto;
  min-width: 0;
}

#stat-distance {
  flex-shrink: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--mk-ink);
}

/* The target had no label anywhere: nothing said what the number was for,
   and nothing said Generate loop aims at it. Both are on the control now. */
#target-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 4px 9px;
  border: 1px dashed var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper-2);
  font: inherit;
  font-size: 12px;
  color: var(--mk-ink-2);
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}

#target-chip:hover {
  border-color: var(--mk-accent);
  color: var(--mk-accent);
}

#target-chip.is-unset {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
}

#target-chip.is-over {
  border-color: var(--mk-danger-border);
  background: var(--mk-danger-soft);
  color: var(--mk-danger);
}

.cmd-actions {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  /* Must NOT wrap here: the spacer before it takes all the free width, so a
     wrapping action group collapses to min-content and breaks into three
     stacked rows. #command-bar's own wrap handles narrow widths instead. */
  flex-wrap: nowrap;
  flex-shrink: 0;
}

.cmd-actions > button,
#target-chip {
  white-space: nowrap;
}

.cmd-actions > button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  font: inherit;
  font-size: 12.5px;
  color: var(--mk-ink);
  cursor: pointer;
}

.cmd-actions > button:hover:not(:disabled) {
  border-color: var(--mk-accent);
  color: var(--mk-accent);
}

.cmd-actions > button:disabled {
  opacity: 0.45;
  cursor: default;
}

#more-btn {
  padding: 6px 8px;
}

/* ARMED, not "in marker mode". #marker-btn arms the NEXT map click and then
   disarms itself, so this state lasts one click -- but for that click it has
   to be unmistakable, because the map is about to do something other than
   what it normally does. Same accent fill Save uses when it has something to
   save. */
#marker-btn.is-armed {
  background: var(--mk-accent);
  border-color: var(--mk-accent);
  color: #fff;
  font-weight: 600;
}

#marker-btn.is-armed:hover:not(:disabled) {
  background: var(--mk-accent-hover);
  border-color: var(--mk-accent-hover);
  color: #fff;
}

/* Save is a permanent button carrying its own state, not something that
   appears only while the route is dirty -- the owner named it one of the
   four controls he reaches for most, and it used to hide in a banner
   popover. */
.cmd-primary {
  border-color: var(--mk-line-strong);
}

.cmd-primary.is-dirty {
  background: var(--mk-accent);
  border-color: var(--mk-accent);
  color: #fff;
  font-weight: 600;
}

.cmd-primary.is-dirty:hover:not(:disabled) {
  background: var(--mk-accent-hover);
  border-color: var(--mk-accent-hover);
  color: #fff;
}

#elevation-toggle-btn.is-off {
  color: var(--mk-ink-2);
}

/* The overflow MENU, still anchored to its own button and still opening
   upward because the bar sits at the bottom. A menu that leaves its button
   is a menu you have to go looking for, so this one stayed put when the
   three dialogs moved. */
#more-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  z-index: 1200;
  width: 226px;
  padding: 5px;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-pop);
}

/* -- The command bar's dialogs -----------------------------------------------
   ONE PLACE, WHICHEVER BUTTON OPENED IT. Target, Generate loop and the
   save-name prompt used to open upward from their own buttons -- the first
   hard against the left edge of the bar, the last two hard against the right
   -- which put them straight under the layers panel and the key, and meant
   three different corners of the screen for three variations of the same
   "answer this, then carry on" moment. Owner, 2026-09-20: "they get hidden by
   the layers and legend area sometimes and I want to keep a consistent
   experience."

   They are children of #map-container now (app.php), not of the bar, so
   "centred" means centred on the MAP -- clear of the panel, clear of the bar,
   and in the same spot every time. Above every piece of map furniture
   (which tops out at 1000) and above the bar's own 1200. */
.map-dialog {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1300;
  padding: 14px 15px 15px;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  /* Deeper than an anchored popover's: nothing points at this one, so the
     shadow is what lifts it off the map. */
  box-shadow: 0 10px 34px rgba(20, 24, 27, 0.26), 0 2px 8px rgba(20, 24, 27, 0.12);
}

/* A card in the middle of the map has no button pointing at it, so unlike an
   anchored popover it is not obvious that clicking away dismisses it -- and
   the target dialog's only other button says "Clear", which empties the
   field rather than putting the card away (owner, 2026-09-20). Same close
   glyph and hit area as the properties panel's. */
.map-dialog-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--mk-radius-xs);
  background: none;
  color: var(--mk-ink-3);
  cursor: pointer;
}

.map-dialog-close:hover {
  background: var(--mk-paper-2);
  color: var(--mk-ink);
}

/* Clear of the close button, which would otherwise sit on the title. */
.map-dialog .cmd-pop-title {
  padding-right: 24px;
}

#target-popover {
  width: min(300px, calc(100% - 32px));
}

/* THE ONE DIALOG THAT IS NOT CENTRED, because it is the only one you use
   WHILE LOOKING AT THE MAP. Target and the save-name prompt are
   answer-and-dismiss; this one lists three candidate loops that are drawn on
   the map behind it as dashed lines, and centred it landed exactly on top of
   them -- the owner, 2026-09-24: it "makes it un-usable unless you manually
   pan the map away to see the routes".

   Left, not right, because the layers panel and the key are on the right and
   covering this dialog is what moved it off its button in the first place.
   Below the top-left stack rather than beside it: --map-topleft-h is written
   by src/main.js from that stack's measured height, so this clears the
   search box alone before a route exists and the search box plus the route
   card once one does.

   Same position for the prompt and for the results, so it does not jump when
   the candidates land. src/main.js reserves this width in the map's own
   fitBounds, so the loops are drawn in the space beside it rather than
   underneath. */
#loop-popover {
  top: calc(var(--map-topleft-h, 44px) + 20px);
  left: 12px;
  transform: none;
  width: min(330px, calc(100% - 24px));
  max-height: calc(100% - var(--map-topleft-h, 44px) - 32px);
  /* The head stays put and only the body scrolls, so the close button and
     the title never scroll away from a long list of loops. */
  display: flex;
  flex-direction: column;
  padding: 0;
}

#save-name-popover {
  width: min(300px, calc(100% - 32px));
}

.cmd-pop-title {
  margin: 0 0 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--mk-ink);
}

.cmd-pop-note {
  margin: 0 0 9px;
  font-size: 11.8px;
  line-height: 1.5;
  color: var(--mk-ink-2);
}

.cmd-pop-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Clear and Save, on their own row -- four controls in .cmd-pop-row above
   was cramped at 300px. */
.cmd-pop-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 9px;
}

/* .cmd-pop-primary is flex:1 for the loop generator's single full-width
   button; here it shares a row with Clear and must size to its own text. */
.cmd-pop-actions .cmd-pop-primary {
  flex: 0 0 auto;
}

.cmd-pop-row input[type='number'],
#save-name-input {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  font: inherit;
  font-size: 13px;
  background: #fff;
  color: var(--mk-ink);
}

#target-distance-unit {
  font-size: 12px;
  color: var(--mk-ink-3);
}

#target-clear-btn,
.cmd-pop-primary {
  padding: 6px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}

.cmd-pop-primary {
  flex: 1;
  justify-content: center;
  background: var(--mk-accent);
  border-color: var(--mk-accent);
  color: #fff;
  font-weight: 600;
}

.cmd-pop-primary:hover {
  background: var(--mk-accent-hover);
}

#more-menu button,
.more-menu-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 9px;
  border: 0;
  border-radius: var(--mk-radius-xs);
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--mk-ink);
  text-align: left;
  cursor: pointer;
}

#more-menu button:hover,
.more-menu-toggle:hover {
  background: var(--mk-paper-2);
}

.more-menu-toggle input[type='checkbox'] {
  width: 15px;
  height: 15px;
  accent-color: var(--mk-accent);
}

#more-menu hr {
  margin: 4px 6px;
  border: 0;
  border-top: 1px solid var(--mk-line);
}

.more-menu-danger {
  color: var(--mk-danger);
}

#save-name-popover label {
  display: block;
  margin-bottom: 5px;
  font-size: 12.5px;
  font-weight: 600;
}

#save-name-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 8px;
}

#save-name-actions button {
  padding: 6px 11px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}

/* WHITE TEXT ON A WHITE BUTTON. `#save-name-confirm-btn` is ONE id; the rule
   above it is an id plus an element, which is more specific, so its
   background:#fff won and this rule's colour:#fff was all that applied --
   the confirm button in the save prompt rendered as an empty outline
   (owner, 2026-09-20). Scoped to the same parent so it outranks it, rather
   than moved above it, because source order would not have helped. */
#save-name-actions #save-name-confirm-btn {
  background: var(--mk-accent);
  border-color: var(--mk-accent);
  color: #fff;
  font-weight: 600;
}

#save-name-actions #save-name-confirm-btn:hover {
  background: var(--mk-accent-hover);
  border-color: var(--mk-accent-hover);
}



/* -- Loop generator (src/loopGenerator.js) ----------------------------- */

.loop-generator {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.loop-generator-hint {
  margin: 0;
  font-size: 11px;
  color: var(--mk-ink-3);
}

/* #generate-loop-btn had its own block here, left over from the Route Tools
   panel it used to sit in: width:100%, its own padding, its own font size,
   its own hover and disabled states. It has been a .cmd-actions button since
   2026-09-20, which supplies all of that -- but an id beats a class, so the
   old rule was still winning: on desktop it made Generate loop 2px taller
   and a half-point smaller than every button beside it, and on a phone,
   where .cmd-actions buttons are told not to shrink, width:100% took the
   whole row and pushed Save, More and the profile toggle off the screen
   with no way to scroll to them. Deleted, not overridden. */

#loop-results:empty {
  display: none;
}

/* -- What matters most: the runner's loop priorities (src/loopPriorities.js).
   Presets as pills, like the app's other one-tap choices; the fine-tune
   sliders folded away for the runners who want them. */

.loop-priorities {
  margin: 0;
  padding: 0;
}

.loop-priorities[hidden] {
  display: none;
}

/* Step 3's label, so it wears the step labels' look (.loop-step-label). */
.loop-priorities-title {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--mk-ink-3);
}

/* -- The loop generator's process (src/loopFlow.js) -------------------------
   How far, where to start, what matters most: numbered, in that order, all
   visible at once, then the loops. */

.loop-flow-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 12px 10px 8px 15px;
  border-bottom: 1px solid var(--mk-line);
}

.loop-flow-head .cmd-pop-title {
  flex: 1 1 auto;
  margin: 0;
  padding-right: 0;
}

/* In the head's own row rather than pinned to the corner, so the phone's
   fold button can sit beside it. */
.loop-flow-head .map-dialog-close {
  position: static;
  flex: 0 0 auto;
}

/* Phone only (see the max-width block): the one-line "what is happening"
   for a folded sheet, the button that folds it, and Use for a loop tapped
   while it is folded. */
.loop-flow-summary,
.loop-flow-collapse,
.loop-flow-use {
  display: none;
}

.loop-flow-body {
  min-height: 0;
  overflow-y: auto;
  padding: 10px 15px 14px;
}

.loop-steps {
  margin: 0 0 4px;
  padding: 0;
  list-style: none;
}

.loop-step {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 8px;
  padding: 0 0 10px;
}

.loop-step + .loop-step {
  padding-top: 10px;
  border-top: 1px solid var(--mk-line);
}

.loop-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--mk-paper-3);
  color: var(--mk-ink-2);
  font-size: 11px;
  font-weight: 700;
}

/* The step waiting on you -- a click on the map -- is the one lit up. */
.loop-step.is-waiting .loop-step-num {
  background: var(--mk-accent);
  color: #fff;
}

.loop-step-label {
  display: block;
  margin: 2px 0 6px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--mk-ink-3);
}

.loop-length-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.loop-length-row input[type='number'] {
  width: 76px;
  padding: 5px 7px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  font: inherit;
  font-size: 13px;
  background: #fff;
  color: var(--mk-ink);
}

#loop-length-unit {
  font-size: 12px;
  color: var(--mk-ink-2);
}

.loop-length-note {
  margin-left: auto;
  font-size: 11px;
  color: var(--mk-ink-3);
}

.loop-step-text {
  margin: 0 0 6px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--mk-ink-2);
}

.loop-step.is-waiting .loop-step-text {
  color: var(--mk-accent);
  font-weight: 600;
}

.loop-step-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.loop-step-actions button,
#loop-try-again {
  padding: 4px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 999px;
  background: var(--mk-paper);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.loop-step-actions button:hover,
#loop-try-again:hover {
  border-color: var(--mk-accent-border);
  color: var(--mk-accent);
}

.loop-step-actions button[hidden],
#loop-try-again[hidden],
.loop-link[hidden],
#loop-find-btn[hidden],
#loop-use-length[hidden],
.loop-status[hidden],
.loop-more[hidden],
.loop-step[hidden],
.loop-place-results[hidden],
.loop-my-places[hidden],
.loop-step-text[hidden] {
  display: none;
}

.loop-step-actions button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.loop-step-actions button.is-open {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
}

/* Places to pass (src/loopFlow.js initPlaces()). */
.loop-step-optional {
  margin-left: 4px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--mk-ink-3);
}

.loop-places {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 0 0 6px;
  padding: 0;
  list-style: none;
}

.loop-places:empty {
  display: none;
}

.loop-place {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 3px 2px 3px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 999px;
  background: var(--mk-paper);
  font-size: 12px;
}

.loop-place-letter {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
}

.loop-place-name {
  overflow: hidden;
  max-width: 190px;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--mk-ink);
}

.loop-place-save,
.loop-place-remove {
  flex: none;
  padding: 0 4px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--mk-ink-3);
  cursor: pointer;
}

.loop-place-save[aria-pressed="true"] {
  color: #c79100;
}

.loop-place-save:hover,
.loop-place-remove:hover {
  color: var(--mk-accent);
}

.loop-place-search {
  margin-top: 6px;
}

.loop-place-search input {
  width: 100%;
  padding: 5px 8px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  font: inherit;
  font-size: 12.5px;
  background: #fff;
  color: var(--mk-ink);
}

.loop-place-search input:disabled {
  opacity: 0.5;
}

.loop-place-results {
  margin: 4px 0 0;
  padding: 3px;
  list-style: none;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper);
  box-shadow: 0 4px 14px -6px rgba(20, 24, 27, 0.3);
}

.loop-place-results button {
  display: block;
  width: 100%;
  padding: 5px 7px;
  border: 0;
  border-radius: 4px;
  background: none;
  font: inherit;
  font-size: 12px;
  line-height: 1.35;
  text-align: left;
  color: var(--mk-ink);
  cursor: pointer;
}

.loop-place-results button:hover,
.loop-place-results button:focus-visible {
  background: var(--mk-paper-3);
}

.loop-my-places {
  margin-top: 6px;
  padding: 6px;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper-2);
}

.loop-my-places ul {
  display: grid;
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.loop-my-place {
  display: flex;
  align-items: center;
  gap: 4px;
}

.loop-my-place-add {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  padding: 4px 6px;
  border: 0;
  border-radius: 4px;
  background: none;
  font: inherit;
  font-size: 12px;
  text-align: left;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--mk-ink);
  cursor: pointer;
}

.loop-my-place-add:hover:not(:disabled) {
  background: var(--mk-paper);
}

.loop-my-place-add:disabled {
  color: var(--mk-ink-3);
  cursor: default;
}

.loop-my-place-tool {
  flex: none;
  padding: 2px 6px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--mk-ink-3);
  cursor: pointer;
}

.loop-my-place-tool:hover {
  border-color: var(--mk-line-strong);
  color: var(--mk-ink);
}

.loop-my-place-tool.is-confirm {
  border-color: var(--mk-danger-border);
  background: var(--mk-danger-soft);
  color: var(--mk-danger);
  font-weight: 600;
}

.loop-my-place input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 3px 6px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 4px;
  font: inherit;
  font-size: 12px;
}

.loop-my-places-empty {
  margin: 0;
  font-size: 12px;
  color: var(--mk-ink-2);
}

.loop-candidate-places {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}

/* A place every loop must pass: a lettered pin, so it never reads as the Start pill. */
.loop-place-pin span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 2px solid #fff;
  border-radius: 50% 50% 50% 0;
  box-shadow: 0 1px 5px rgba(20, 24, 27, 0.4);
  transform: rotate(-45deg);
  cursor: grab;
}

.loop-place-pin b {
  transform: rotate(45deg);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

.loop-status {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 2px 0 8px;
}

.loop-status p {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--mk-ink-2);
}

.loop-status p:empty {
  display: none;
}

/* Searching: a small spinner in front of what it is doing. */
.loop-status.is-busy p::before {
  content: '';
  display: inline-block;
  width: 11px;
  height: 11px;
  margin: 0 7px -1px 0;
  border: 2px solid var(--mk-accent-border);
  border-top-color: var(--mk-accent);
  border-radius: 50%;
  animation: loop-spin 0.8s linear infinite;
}

@keyframes loop-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .loop-status.is-busy p::before { animation: none; }
}

.loop-more {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-top: 8px;
  padding-top: 10px;
  border-top: 1px solid var(--mk-line);
}

.loop-more-note {
  font-size: 11px;
  color: var(--mk-ink-3);
}

.loop-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11.5px;
  color: var(--mk-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* On the map: each loop's rank, so which is which never rests on colour
   alone, and the start, which you can drag. */
.loop-badge span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(20, 24, 27, 0.35);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}

.loop-badge.is-dim {
  opacity: 0.4;
}

.loop-badge.is-focus span {
  transform: scale(1.15);
}

.loop-start-pin span {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  padding: 0 8px;
  border: 2px solid #fff;
  border-radius: 999px;
  background: var(--mk-ink);
  box-shadow: 0 1px 5px rgba(20, 24, 27, 0.4);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: grab;
}

/* While the next click sets a start. The route builder's own crosshair is
   off while a loop is being chosen (it is in the background). */
.leaflet-container.loop-picking {
  cursor: crosshair;
}

/* The drawn route in the background (src/routeBuilder.js setBackground()).
   Leaflet gives every interactive path and marker its own pointer-events,
   `.leaflet-pane > svg path.leaflet-interactive` (0,2,2) and
   `.leaflet-marker-icon.leaflet-interactive` (0,2,0), which a style on the
   pane cannot reach -- so these out-rank them by name rather than by order. */
.leaflet-pane.runmapapp-route-background path.leaflet-interactive,
.leaflet-pane.runmapapp-route-background .leaflet-marker-icon.leaflet-interactive {
  pointer-events: none;
}

/* WHILE A LOOP IS BEING CHOSEN the notices say nothing but what bears on it.
   "Click your starting point to create a new route" and "Your route hasn't
   been saved" were both on screen while choosing between loops, both about a
   route rather than a loop, and both wrong at that moment (owner,
   2026-09-24; reproduced 2026-09-27). They come back when it closes. The
   route card steps out of the top-left stack for the same reason, which
   also lets the panel sit higher. */
#map-container.loop-mode #map-notices > :not(#loop-generator-hint),
#map-container.loop-mode #route-name-card {
  display: none;
}

/* The search box is part of choosing where to start, so its suggestions open
   OVER the panel. Reproduced 2026-09-27: the panel (1300) covered the list
   (inside #map-topleft, 1000), and a click on a suggestion landed on the
   panel instead. They only overlap by that dropdown -- the panel sits below
   the stack -- so lifting the stack costs nothing else. */
#map-container.loop-mode #map-topleft {
  z-index: 1350;
}

/* The route tools, set aside (inert) while a loop is being chosen. */
#command-bar [inert] {
  opacity: 0.4;
}

/* Generate loop, lit while its process is open: it is also how to close it. */
#generate-loop-btn.is-active {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
}

.loop-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.loop-preset {
  padding: 4px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 999px;
  background: var(--mk-paper);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.loop-preset:hover {
  border-color: var(--mk-ink-3);
  color: var(--mk-ink);
}

.loop-preset[aria-checked="true"] {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  font-weight: 600;
}

.loop-priorities-note {
  margin: 6px 0 4px;
  font-size: 11.5px;
  color: var(--mk-ink-3);
}

.loop-fine-tune > summary {
  cursor: pointer;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--mk-ink-2);
  list-style: none;
}

.loop-fine-tune > summary::-webkit-details-marker { display: none; }
.loop-fine-tune > summary::before { content: '▸ '; }
.loop-fine-tune[open] > summary::before { content: '▾ '; }

.loop-weights {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0 4px;
}

.loop-weight-row {
  display: grid;
  grid-template-columns: 108px 1fr 34px;
  align-items: center;
  gap: 6px;
}

.loop-weight-row label {
  font-size: 11px;
  line-height: 1.25;
  color: var(--mk-ink-2);
}

.loop-weight-row input[type="range"] {
  width: 100%;
  accent-color: var(--mk-accent);
}

.loop-weight-row output {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--mk-ink-3);
}

.loop-weights-note {
  margin: 4px 0 0;
  font-size: 11px;
  line-height: 1.45;
  color: var(--mk-ink-3);
}

.loop-weights-reset {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  color: var(--mk-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.loop-candidate {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 0;
  border-top: 1px solid var(--mk-line);
}

.loop-candidate-stats {
  margin: 0;
  font-size: 12px;
  color: var(--mk-ink-2);
  line-height: 1.5;
}

/* Colour comes inline (matches src/sidewalksLayer.js's own MISSING_COLOR) --
   the weight here is what makes it "shout" relative to .loop-candidate-stats,
   same as a confirmed-missing sidewalk line already does on the map itself. */
.loop-candidate-warning {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
}

/* Was `.loop-candidate button`, which styled every button in a card and, as
   element-plus-class, would outrank the pick button's own class below. Each
   button names its own class now. */
.loop-candidate-use {
  align-self: flex-start;
  padding: 6px 10px;
  border: 1px solid var(--mk-accent-border);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  cursor: pointer;
  font-size: 12px;
  font-family: inherit;
  font-weight: 550;
}

.loop-candidate-use:hover,
.loop-candidate.is-selected .loop-candidate-use {
  background: var(--mk-accent);
  border-color: var(--mk-accent);
  color: #fff;
}

.loop-candidate-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* On the chosen card while the bottom panel is shut: the way to its hills
   and shade (src/loopFlow.js applyEmphasis()). */
.loop-candidate-profile {
  padding: 6px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  color: var(--mk-ink-2);
  cursor: pointer;
  font-size: 12px;
  font-family: inherit;
  font-weight: 550;
}

.loop-candidate-profile:hover {
  border-color: var(--mk-accent);
  color: var(--mk-accent);
}

/* The card's first line is a button: the keyboard's way to show a loop on
   the map (a mouse can click anywhere on the card). */
.loop-candidate-pick {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
}

.loop-candidate-badge {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 50%;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

.loop-candidate {
  cursor: pointer;
  margin: 0 -8px;
  padding: 8px;
  border-radius: var(--mk-radius-xs);
}

.loop-candidate.is-hovered {
  background: var(--mk-paper-2);
}

.loop-candidate.is-selected {
  background: var(--mk-accent-soft);
  box-shadow: inset 0 0 0 1px var(--mk-accent-border);
}

/* Results for a length that has since been changed: still readable, clearly
   not current until "Find loops" is pressed. */
#loop-results.is-stale {
  opacity: 0.5;
}

/* -- Map legend ------------------------------------------------------------
   Shape only. The key's type size and padding are set on #map-legend-body up
   in the layers panel section, where the rest of that panel's layout lives. */

.legend-heading {
  margin: 9px 0 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--mk-ink);
}

.legend-heading:first-child {
  margin-top: 0;
}

.legend-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.legend-list li {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 3px;
  color: var(--mk-ink-2);
  line-height: 1.35;
}

.legend-swatch {
  flex-shrink: 0;
}

.legend-endpoint,
.legend-waypoint,
.legend-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: 9px;
  font-weight: 700;
  color: #fff;
}

.legend-endpoint {
  width: 16px;
  height: 16px;
  background: var(--mk-map-marker);
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px #bbb;
  margin: 0 5px;
}

.legend-waypoint {
  width: 12px;
  height: 12px;
  background: #fff;
  border: 3px solid var(--mk-map-marker);
  margin: 0 7px;
}

.legend-marker {
  width: 17px;
  height: 17px;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px #bbb;
  margin: 0 4px;
}

/* Matches the crossing dots' actual marker styling: coloured fill, walking-
   figure glyph -- the same icon drawn on the map, just fixed-size here. */
.legend-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px #bbb;
  margin: 0 8px;
}

.legend-dot svg {
  width: 68%;
  height: 68%;
}

/* The tint is semi-transparent on the map, so the key shows it over a checker
   rather than flat -- a solid swatch would imply it hides the streets. */
.legend-tint {
  width: 34px;
  height: 12px;
  border: 1px solid var(--mk-line-strong);
  background-image:
    linear-gradient(
      color-mix(in srgb, var(--tint-color) calc(var(--tint-alpha) * 100%), transparent),
      color-mix(in srgb, var(--tint-color) calc(var(--tint-alpha) * 100%), transparent)
    ),
    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%),
    linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%);
  background-size: 100% 100%, 8px 8px, 8px 8px;
  background-position: 0 0, 0 0, 4px 4px;
  background-color: #fff;
}

/* -- Map cursors and route handles ----------------------------------------- */

.leaflet-container.runmapapp-adding {
  cursor: crosshair;
}

body.runmapapp-busy .leaflet-container {
  cursor: progress;
}

.runmapapp-waypoint-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--mk-map-marker);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  font-size: 10px;
  font-weight: 700;
  color: #fff;
}

.runmapapp-waypoint-dot.runmapapp-waypoint-end {
  width: 20px;
  height: 20px;
  background: var(--mk-map-marker);
  border-color: #fff;
}

.runmapapp-marker-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
}

/* A raw OSM/Wilmington crossing dot -- smaller and plainer than the
   hand-placed marker above (no border, lighter shadow), since there can be
   hundreds on screen at once and each one is informational, not something
   the runner placed. The walking-figure glyph inside is what makes it read
   as "a crossing" rather than just a coloured dot at a glance. */
/* Three shapes, chosen in admin.runmapapp.com/map-settings.php. The default
   halo has no drop shadow on purpose: nothing else on this map casts one, and
   the hard shadow the original disc carried is most of what made the dots look
   like clip art next to the soft path bands. */
.runmapapp-crossing-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  width: 100%;
  height: 100%;
}

/* Solid core inside a translucent halo -- colour as a wash, one crisp element
   for precision, the same idea as the path lines. */
.runmapapp-crossing-halo > i,
.runmapapp-crossing-ring > i {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  flex-shrink: 0;
}

.runmapapp-crossing-halo > i > b {
  display: block;
  border-radius: 50%;
}

.runmapapp-crossing-ring {
  border: 2.4px solid currentColor;
}

/* The original look, kept as an option. */
.runmapapp-crossing-disc {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Only drawn when the glyph is switched on; it sits over whichever shape is
   underneath, so it is absolutely positioned rather than in the flex flow.
   The positioning context is the dot, NOT .runmapapp-crossing-marker -- that
   outer element is the one Leaflet positions with `position: absolute` plus a
   transform, and main.css loads after leaflet.css, so overriding it to
   `relative` silently drops every marker into normal flow and scatters them
   across the map. Do not put position on the marker element. */
.runmapapp-crossing-dot {
  position: relative;
}

.runmapapp-crossing-dot svg {
  position: absolute;
  width: 60%;
  height: 60%;
}

/* -- Elevation panel ------------------------------------------------------- */

/* -- Elevation panel resizer ------------------------------------------------ */

#elevation-resizer {
  flex-shrink: 0;
  height: 7px;
  cursor: ns-resize;
  background: var(--mk-line);
  border-top: 1px solid var(--mk-line-strong);
  position: relative;
}

#elevation-resizer:hover,
#elevation-resizer:focus-visible,
#elevation-resizer.dragging {
  background: var(--charcoal);
}

/* A grip, so it reads as draggable rather than as a divider. */
#elevation-resizer::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 50%;
  transform: translateX(-50%);
  width: 34px;
  height: 2px;
  border-radius: 1px;
  background: rgba(0, 0, 0, 0.28);
}

#elevation-resizer:hover::after,
#elevation-resizer.dragging::after {
  background: rgba(255, 255, 255, 0.75);
}

/* While dragging, keep the pointer's resize cursor everywhere and stop the
   drag from selecting text across the page. */
body.resizing-elevation {
  cursor: ns-resize;
  user-select: none;
}

#elevation-panel {
  /* Tall enough for the chart, the shade strip, the sun controls row, and the
     sun-aware caption wrapping to two lines at narrow widths. */
  height: 258px;
  flex-shrink: 0;
  border-top: 1px solid var(--mk-line);
  background: #fff;
  display: flex;
  flex-direction: column;
}

/* The wind strip, its summary and its advice need about 70px of their own;
   without this they came out of the chart's height, which squashed the
   profile flat. A height the runner has dragged to (an inline style from
   src/elevationResizer.js) still wins. */
#elevation-panel:has(#wind-strip-wrap:not([hidden])) {
  height: 330px;
}

#elevation-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px 4px;
}

#route-stats {
  display: flex;
  align-items: baseline;
  gap: 16px;
}

#stat-distance {
  font-size: 20px;
  font-weight: 650;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

#stat-gain,
#stat-loss {
  font-size: 13px;
  color: var(--mk-ink-2);
  font-variant-numeric: tabular-nums;
}

/* "Loop 2 · 4.02 mi": which loop the panel is describing while one is chosen
   in the loop generator (src/loopPreview.js). Its badge is the one on the map
   and on the card. */
#elevation-subject {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--mk-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#elevation-subject[hidden] {
  display: none;
}

.elevation-subject-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

#target-remaining {
  font-variant-numeric: tabular-nums;
}

#target-remaining:empty {
  display: none;
}

/* Neutral while under target -- red (the same colour toolbar-actions uses for
   destructive buttons) only once the runner has actually gone over, since
   that's the one state that's worth a signal rather than plain data. */
#target-remaining.over {
  color: var(--mk-danger);
}

#elevation-status {
  font-size: 12px;
  color: var(--mk-ink-3);
}

#elevation-header-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

#elevation-hover-readout {
  font-size: 12px;
  color: var(--mk-ink-2);
  font-variant-numeric: tabular-nums;
  font-variant-numeric: tabular-nums;
  min-width: 120px;
  text-align: right;
}

#units-toggle {
  padding: 4px 10px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
}

#units-toggle:hover {
  border-color: var(--mk-accent);
}

/* Where the heights came from (src/elevationProfile.js's setCredits()). Quiet
   on purpose: a credit, not a control anyone needs mid-drawing, but it names
   the source that actually answered for this route and opens the licence
   notices. The panel is the positioning context for the detail below. */
#elevation-panel {
  position: relative;
}

/* The credit is the one thing in this header allowed to give way. Measured at
   375px when it could not: the climb figures wrapped onto three lines each
   ("↑ / 67 / ft") and "mi / ft" stacked vertically. So the stats and the
   toggle keep their size, and the credit truncates -- it opens the full text
   on click either way. */
#elevation-header-right {
  min-width: 0;
}

#route-stats,
#units-toggle {
  flex-shrink: 0;
}

#units-toggle {
  white-space: nowrap;
}

#elevation-source {
  display: inline-block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 2px 4px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11.5px;
  color: var(--mk-ink-3);
  text-decoration: underline;
  text-decoration-color: var(--mk-line-strong);
  text-underline-offset: 3px;
  cursor: pointer;
  white-space: nowrap;
}

#elevation-source:hover,
#elevation-source[aria-expanded="true"] {
  color: var(--mk-ink);
  text-decoration-color: currentColor;
}

#elevation-source-detail {
  position: absolute;
  top: 38px;
  right: 12px;
  z-index: 5;
  width: min(380px, calc(100% - 24px));
  display: grid;
  gap: 10px;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-pop);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--mk-ink-2);
}

.elevation-source-block {
  display: grid;
  gap: 6px;
}

.elevation-source-block + .elevation-source-block {
  border-top: 1px solid var(--mk-line);
  padding-top: 10px;
}

.elevation-source-what {
  margin: 0;
  color: var(--mk-ink);
}

.elevation-source-notice {
  margin: 0;
  font-size: 11.5px;
  color: var(--mk-ink-3);
}

.elevation-source-block a {
  color: var(--mk-accent);
  font-weight: 600;
  justify-self: start;
}

@media (max-width: 767px) {
  /* The source name is what matters; "Elevation:" is implied by where it sits. */
  .elevation-source-prefix {
    display: none;
  }
}

#elevation-chart {
  flex: 1;
  min-height: 0;
  position: relative;
}

.elevation-svg {
  display: block;
}

.elevation-area {
  fill: rgba(20, 24, 27, 0.10); /* neutral ink -- deliberately not the accent:
                                    a green wash here would read as shade */
}

.elevation-line {
  fill: none;
  /* A loop being looked at in the loop generator draws in its own colour
     (src/elevationProfile.js sets --elevation-stroke), so the chart and the
     line on the map read as one thing. */
  stroke: var(--elevation-stroke, var(--mk-map-marker));
  stroke-width: 1.75;
}

.elevation-grid {
  stroke: var(--mk-line);
  stroke-width: 1;
}

.elevation-axis-label {
  font-size: 10px;
  fill: #999;
}

.elevation-crosshair {
  stroke: #c62828;
  stroke-width: 1;
  stroke-dasharray: 3, 3;
}

.elevation-cursor {
  fill: #c62828;
  stroke: #fff;
  stroke-width: 1.5;
}

/* -- Shade strip ----------------------------------------------------------- */

#shade-strip-wrap {
  flex-shrink: 0;
  padding: 0 0 6px;
}

/* Used by the "Colour route by" group in Route Tools. */
.segmented {
  display: flex;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  overflow: hidden;
  flex-shrink: 0;
}

.segmented-btn {
  padding: 4px 9px;
  border: 0;
  background: #fff;
  cursor: pointer;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  color: var(--mk-ink-2);
}

.segmented-btn + .segmented-btn {
  border-left: 1px solid var(--mk-line-strong);
}

.segmented-btn:hover:not(.active) {
  background: var(--mk-paper-2);
}

.segmented-btn.active {
  background: var(--charcoal);
  color: #fff;
}

#shade-strip {
  height: 14px;
}

.shade-svg {
  display: block;
}

#shade-strip-label {
  margin: 3px 14px 0;
  font-size: 11px;
  color: var(--mk-ink-3);
}

#shade-strip-label:empty {
  display: none;
}

/* -- Wind strip ------------------------------------------------------------
   Same x-axis as the profile above it (PADDING in src/elevationProfile.js):
   headwind above the centre line, wind behind you below. */

#wind-strip-wrap {
  flex-shrink: 0;
  padding: 2px 0 6px;
  border-top: 1px solid var(--mk-line);
}

#wind-strip {
  height: 34px;
}

.wind-svg {
  display: block;
  cursor: crosshair;
}

.wind-strip-axis {
  stroke: var(--mk-line-strong);
  stroke-width: 1;
}

.wind-strip-scale {
  font-size: 9px;
  fill: #999;
}

.wind-strip-cursor {
  stroke: var(--mk-ink);
  stroke-width: 1;
  stroke-dasharray: 3, 3;
}

#wind-strip-label {
  margin: 2px 14px 0;
  font-size: 11px;
  color: var(--mk-ink-3);
}

#wind-strip-label:empty {
  display: none;
}

#wind-strip-advice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 3px 14px 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}

#wind-strip-advice strong {
  font-weight: 600;
  color: var(--mk-ink);
}

.wind-reverse-btn {
  padding: 3px 9px;
  border: 1px solid var(--mk-accent-border);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-accent-soft);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--mk-accent);
  cursor: pointer;
}

.wind-reverse-btn:hover {
  border-color: var(--mk-accent);
}

.legend-arrow,
.legend-streaks {
  display: inline-flex;
  align-items: center;
}

/* ============================================================================
   Mobile layout (<= 767px, matches src/viewport.js's isMobileLayout())
   ----------------------------------------------------------------------------
   Everything above this point assumes a wide map: the top-left stack and the
   layers panel at opposite corners, the command bar in one row, and a
   fixed-height elevation panel under it. A phone has room for none of that at
   once -- see "Mobile is unusable in the app" in TODO.md for the original
   measurements.

   ONE RULE HOLDS THE WHOLE THING TOGETHER: the bottom edge belongs to exactly
   one thing. The layers panel, the command bar and the elevation profile were
   three claims on the same strip, which is the stacking the owner caught from
   a screenshot on 2026-09-20. So layers becomes a sheet that sits ABOVE the
   command bar and is capped at 62% height, the bar wraps rather than scrolls
   sideways, and the profile is a normal collapsible panel driven by the one
   button on the bar -- the same control desktop uses, not a parallel mobile
   state. The header's nav still moves behind the menu button it always did.
   ========================================================================== */

@media (max-width: 767px) {

  /* Scoped to .app-page throughout (set on app.php's <body> only):
     route-share.php's much simpler layout reuses several of the same ids
     -- #app-header, #app-nav, #elevation-panel and friends -- for its own
     public shared-route view, and must not inherit a bottom sheet or a
     collapsible elevation panel it has no controls for. */

  /* -- Header: nav collapses behind a menu button ------------------------- */

  .app-page #app-header {
    position: relative;
  }

  .app-page .app-header-shell {
    padding: 0 14px;
  }

  .app-page #app-nav-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    margin-left: auto;
    padding: 0;
    border: 1px solid var(--mk-line-strong);
    border-radius: var(--mk-radius-xs);
    background: #fff;
    color: var(--mk-ink);
    cursor: pointer;
  }

  .app-page #app-nav-toggle:hover {
    border-color: var(--mk-ink-3);
  }

  .app-page #app-nav {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    right: 14px;
    left: auto;
    z-index: 1200;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    width: 200px;
    margin-left: 0;
    padding: 6px;
    background: #fff;
    border: 1px solid var(--mk-line);
    border-radius: var(--mk-radius-sm);
    box-shadow: var(--mk-shadow-pop);
  }

  .app-page #app-nav.open {
    display: flex;
  }

  .app-page #app-nav a {
    padding: 9px 10px;
    border-radius: var(--mk-radius-xs);
  }

  .app-page #app-nav a:hover {
    background: var(--mk-paper-2);
  }

  .app-page #app-nav .mk-btn {
    margin-top: 4px;
    justify-content: center;
  }

  /* .mk-account-btn's negative margin-right (css/tokens.css) is a desktop
     row-gap trick -- in this column layout it would just push the button
     past its stretched width instead of closing a gap, so it's cancelled
     here. */
  .app-page #app-nav .mk-account-btn {
    margin-right: 0;
  }

  /* -- The top-left stack -------------------------------------------------
     Just narrows. Nothing re-arranges, which is the whole reason the route
     name went UNDER the search rather than beside it. */

  .app-page #map-topleft {
    left: 10px;
    /* Plain gutter. This was `right: 62px`, reserving room for Leaflet's
       zoom control, which used to sit at top-right -- src/mapInit.js moved
       it to bottomleft in the 2026-09-20 rebuild and the inset stayed, so
       the search box gave up 62px of a 375px screen to nothing. Measured
       before changing it: the control renders at x=10, y=451 (bottom left)
       and #map-topleft stopped 62px short of the map's right edge. */
    right: 10px;
    width: auto;
    max-width: none;
  }

  .app-page #map-notices {
    bottom: 16px;
  }

  /* -- Layers: a bottom sheet, and NOT open by default ---------------------
     "Always open" is a desktop answer. At 372px an always-open panel is most
     of the screen, so on a phone the panel starts stowed and opens as a
     sheet over the map (src/main.js picks the default by viewport).

     THE BOTTOM EDGE HOLDS EXACTLY ONE THING. The sheet, the command bar and
     the elevation panel were three claims on the same strip of screen, which
     is the stacking the owner caught on 2026-09-20. The sheet sits above the
     command bar rather than on top of it, and is capped so the map is never
     entirely covered. */

  .app-page #layers-panel {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: none;
    max-height: 62%;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -6px 24px rgba(20, 24, 27, 0.18);
  }

  .app-page #layers-panel-header {
    padding: 12px 14px;
  }

  /* -- Command bar: wraps instead of scrolling sideways ------------------ */

  .app-page #command-bar {
    flex-wrap: wrap;
    gap: 7px;
    padding: 8px 10px;
    row-gap: 8px;
  }

  .app-page .cmd-spacer,
  .app-page .cmd-sep {
    display: none;
  }

  .app-page .cmd-actions {
    flex-basis: 100%;
    gap: 6px;
  }

  .app-page .cmd-actions > button {
    flex: 1 1 auto;
    justify-content: center;
  }

  /* Labels go, icons stay, on everything but Save -- seven labelled buttons
     will not fit one row at 375px and two rows of controls pushes the map up
     too far. Measured, not guessed: four icon buttons plus "Generate loop"
     and "Save" already overflowed the row by ~37px before the Marker button
     was added, clipping the overflow menu and the profile toggle off the
     right edge with no way to scroll to them. Save keeps its label because
     it is the one button whose glyph is not self-evident and the one whose
     state (dirty / not) has to be readable at a glance. Each of the others
     keeps its `title`, which is what names it once the label is out of the
     accessibility tree. */
  .app-page #marker-btn span,
  .app-page #undo-btn span,
  .app-page #loop-btn span,
  .app-page #generate-loop-btn span,
  .app-page #elevation-toggle-btn span {
    display: none;
  }

  .app-page #marker-btn,
  .app-page #undo-btn,
  .app-page #loop-btn,
  .app-page #generate-loop-btn,
  .app-page #elevation-toggle-btn {
    flex: 0 0 auto;
    padding: 8px 11px;
  }

  /* The three dialogs size themselves against the map (min(...) on their own
     rules), so only the anchored menu needs a narrow-screen width. */
  .app-page #more-menu {
    right: 0;
    width: min(260px, calc(100vw - 20px));
  }

  /* -- Elevation panel --------------------------------------------------- */

  .app-page #elevation-resizer {
    display: none;
  }

  .app-page #elevation-panel {
    height: auto;
    max-height: 46vh;
  }

  .app-page #elevation-header {
    padding: 8px 10px 2px;
  }

  .app-page #elevation-hover-readout {
    display: none;
  }

  /* "③ 2.56 mi" rather than "③ Loop 3 · 2.56 mi": at 375px the name
     squeezed the elevation credit down to its first letter. */
  .app-page .elevation-subject-badge + .elevation-subject-name {
    display: none;
  }
}

@media (min-width: 768px) {
  /* The toggle only exists for the mobile header; on a wide viewport #app-nav
     is always visible inline, same as before this feature. */
  .app-page #app-nav-toggle {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   Why a generated loop ranked where it did -- the per-term contributions to
   its score. Collapsed by default: a runner picking a route wants the route,
   and this is for the person deciding whether the weights in Map settings
   are right. See src/loopGenerator.js's scoreParts.
   --------------------------------------------------------------------------- */
.loop-score-why { margin: 4px 0 8px; }

.loop-score-why > summary {
  cursor: pointer;
  font-size: 11.5px;
  color: var(--mk-ink-3);
  list-style: none;
}
.loop-score-why > summary::-webkit-details-marker { display: none; }
.loop-score-why > summary::before { content: '▸ '; }
.loop-score-why[open] > summary::before { content: '▾ '; }
.loop-score-why > summary:hover { color: var(--mk-ink-2); }

.loop-score-bars { display: flex; flex-direction: column; gap: 3px; margin: 6px 0 4px; }

.loop-score-row { display: grid; grid-template-columns: 62px 1fr auto; align-items: center; gap: 6px; }

.loop-score-label { font-size: 10.5px; color: var(--mk-ink-3); }

.loop-score-track {
  height: 5px;
  border-radius: 3px;
  background: var(--mk-paper-3);
  overflow: hidden;
}

.loop-score-bar { display: block; height: 100%; background: var(--mk-accent); border-radius: 3px; }

.loop-score-value {
  font-size: 10.5px;
  color: var(--mk-ink-2);
  font-variant-numeric: tabular-nums;
  min-width: 62px;
  text-align: right;
}

.loop-score-total { margin: 2px 0 0; font-size: 10.5px; color: var(--mk-ink-3); }

/* A generated loop that runs ground it has already covered. Uses the same
   red-orange as a confirmed-absent sidewalk, because both are the case the
   runner most needs to notice before choosing -- see src/main.js's
   retraceNote(). */
.loop-candidate-retrace {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: #c2410c;
}

/* Wind on a candidate: information, not a score (owner, 2026-09-26). */
.loop-candidate-wind {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}

/* An action offered inside a map notice -- today, "ask us to add it" when a
   layer has nothing to draw for this area (src/feedbackEntry.js). Inline and
   small on purpose: the pill is already the message, and this is the thing to
   do about it, not a second sentence. */
.map-notice-action {
  font: inherit;
  font-size: inherit;
  font-weight: 600;
  color: var(--mk-accent);
  background: none;
  border: none;
  border-bottom: 1px solid var(--mk-accent-border);
  padding: 0;
  cursor: pointer;
}

.map-notice-action:hover {
  border-bottom-color: var(--mk-accent);
}

/* The post-save prompt ("How did that go?").

   THE TWO ACTIONS COST DIFFERENT AMOUNTS, so they are shaped differently.
   Opening the form by mistake costs one click to close. Dismissing by mistake
   costs the prompt for a fortnight. The first version made both inline text
   links of the same size, side by side, and the expensive mistake was the easy
   one to make. Now the whole pill is the ask -- a big forgiving target for the
   cheap action -- and dismissal is a small x with real space and its own hit
   area. */
#post-save-hint.is-prompt {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px 4px 14px;
}

.post-save-ask {
  font: inherit;
  font-size: 13px;
  color: var(--mk-ink-2);
  background: none;
  border: none;
  border-radius: 100px;
  padding: 5px 8px;
  cursor: pointer;
  text-align: left;
}

.post-save-ask:hover {
  background: var(--mk-accent-soft);
}

.post-save-cta {
  font-weight: 600;
  color: var(--mk-accent);
  border-bottom: 1px solid var(--mk-accent-border);
}

.post-save-ask:hover .post-save-cta {
  border-bottom-color: var(--mk-accent);
}

/* Its own hit area, and separated -- the gap is the point. Grey, because
   declining should never look like the thing to do. */
.post-save-dismiss {
  font: inherit;
  font-size: 17px;
  line-height: 1;
  color: var(--mk-ink-3);
  background: none;
  border: none;
  border-radius: 100px;
  width: 28px;
  height: 28px;
  margin-left: 4px;
  flex: none;
  cursor: pointer;
}

.post-save-dismiss:hover {
  background: var(--mk-paper-3);
  color: var(--mk-ink);
}


/* -- hidden-attribute overrides ----------------------------------------------
   `hidden` only sets display:none at USER-AGENT specificity, so any id or
   class rule above that sets a display wins over it and the element stays on
   screen after JS "hides" it. Every element this app toggles with .hidden
   needs naming here. Silent when wrong, which is why they are in one list
   rather than scattered next to each rule. */
#map-topleft[hidden],
#route-name-card[hidden],
#layers-panel[hidden],
#layers-panel-body[hidden],
#map-legend-body[hidden],
#sun-controls[hidden],
#command-bar[hidden],
#elevation-panel[hidden],
#elevation-resizer[hidden],
#target-popover[hidden],
#loop-popover[hidden],
#more-menu[hidden],
#save-name-popover[hidden],
#save-banner[hidden],
#search-results[hidden],
.layer-source[hidden],
.layer-source-detail[hidden],
#elevation-source[hidden],
#elevation-source-detail[hidden],
#wind-controls[hidden],
#wind-strip-wrap[hidden],
#wind-strip-advice[hidden] {
  display: none !important;
}

/* Feedback, as a nav item on the app's own pages. The floating .fb-fab is
   position:fixed bottom-right, which on /map is exactly where the command
   bar sits -- it was covering Save. */
.app-nav-feedback {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 12px;
  border: 1px solid var(--mk-accent-border);
  border-radius: var(--mk-radius-xs);
  /* COLOURED, AND NOT ONE OF THE TWO DARK CTAs. It was white on a white
     header, which made the one button on the page that invites you to say
     something the least visible thing on it (owner, 2026-09-20). This was
     briefly amber, on the reasoning that a third green button would compete
     with Account and My Routes -- he'd rather it matched the site ("i dont
     really like yellow"), and the soft accent tint keeps it subordinate to
     those two without leaving the palette. */
  background: var(--mk-accent-soft);
  font: inherit;
  font-size: 14.5px;
  color: var(--mk-accent);
  cursor: pointer;
}

.app-nav-feedback:hover {
  border-color: var(--mk-accent);
  background: var(--mk-accent);
  color: #fff;
}

/* In the marketing header it stands in a row with .mk-btn, which is a taller
   box (10px/18px padding, 8px radius, 550 weight) than the app nav's own
   buttons. Matched to it rather than left at the app's size, where it
   rendered 33px beside two 39px buttons and sat 3px low. */
.marketing-nav .app-nav-feedback {
  padding: 10px 14px;
  border-radius: var(--mk-radius-sm);
  font-weight: 550;
  line-height: 17.4px;
}

/* -- Marker editor pickers ---------------------------------------------------
   Icon and colour are chosen from fixed vocabularies, not typed -- see
   includes/markerCategories.php for why a free colour was the wrong answer on
   a green basemap under a canopy tint. */
.panel-pick {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.panel-pick-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: #fff;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.panel-pick-icon:hover {
  color: var(--mk-ink);
}

.panel-pick-icon.is-on {
  border-color: var(--mk-accent);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  box-shadow: 0 0 0 2px var(--mk-accent-soft);
}

.panel-pick-colour {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--mk-radius-xs);
  background: none;
  cursor: pointer;
  position: relative;
}

.panel-pick-colour::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--pick);
}

.panel-pick-colour.is-on {
  border-color: var(--mk-ink);
}

/* The pin's glyph, drawn from the shared sprite so the picker, the map and
   the legend cannot disagree about what an icon looks like. */
.runmapapp-marker-dot svg {
  display: block;
  color: #fff;
}


/* -- Properties card ---------------------------------------------------------
   What you clicked: a marker, a route point, or a stretch of the line. It
   floats over the map ANCHORED TO THAT POINT (positioned by
   src/propertiesPanel.js), in the same card language as the layers panel.

   It used to be a 290px column sliding in from the right edge of the window
   -- a different shape from everything else on the page, with no obvious way
   to shut it, and no visible relationship to the thing it was describing.
   The owner: "I REALLY do not like how the add/edit marker stuff slides open
   on the right - its totally inconsistent with the design, cant be easily
   closed or collapsed, and makes no sense. The form itself is great, just not
   how it displays." So the form is unchanged and only its container moved. */
#properties-panel {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1050;
  width: 264px;
  max-width: calc(100% - 24px);
  max-height: calc(100% - 24px);
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  box-shadow: var(--mk-shadow-pop);
  overflow: hidden;
}

#properties-panel-head {
  flex-shrink: 0;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 9px 9px 12px;
  border-bottom: 1px solid var(--mk-line);
  background: var(--mk-paper-2);
}

#properties-panel-title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mk-ink-2);
}

#properties-panel-subtitle {
  margin: 3px 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--mk-ink-3);
  text-transform: none;
  letter-spacing: 0;
}

#properties-panel-subtitle:empty {
  display: none;
}

#properties-panel-close {
  flex-shrink: 0;
  margin-left: auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--mk-radius-xs);
  background: none;
  color: var(--mk-ink-3);
  cursor: pointer;
}

#properties-panel-close:hover {
  border-color: var(--mk-line-strong);
  color: var(--mk-ink);
  background: #fff;
}

#properties-panel-content {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 11px 12px 13px;
}

#properties-panel[hidden] {
  display: none !important;
}

@media (max-width: 767px) {
  /* At 375px an anchored card would spend most of its life clamped against
     an edge anyway, so it becomes a sheet -- and sits ABOVE the command bar
     rather than over it, the same rule everything else on this edge follows. */
  .app-page #properties-panel {
    top: auto !important;
    left: 8px !important;
    right: 8px;
    bottom: 12px;
    width: auto;
    max-width: none;
    max-height: 60%;
  }
}

@media (max-width: 767px) {
  /* THE LOOP GENERATOR ON A PHONE is a sheet along the bottom of the map,
     not a card down its left side: at 375px a 330px card is most of the
     width, and the loops it lists would be drawn underneath it. As a sheet
     it leaves the top of the map for the loops (src/loopFlow.js fits them
     there), and it folds down to its head -- while you pick a start, and
     whenever you want the whole map -- with a one-line summary of what is
     going on, so folding it away never hides that something is happening. */
  .app-page #loop-popover {
    top: auto;
    left: 8px;
    right: 8px;
    bottom: 8px;
    width: auto;
    max-height: 58%;
  }

  .app-page .loop-flow-collapse {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: var(--mk-radius-xs);
    background: none;
    color: var(--mk-ink-3);
    cursor: pointer;
  }

  /* Down to fold the sheet away; up to bring it back. */
  .app-page .loop-flow-collapse[aria-expanded='false'] svg {
    transform: rotate(180deg);
  }

  .app-page #loop-popover.is-collapsed .loop-flow-body {
    display: none;
  }

  .app-page #loop-popover.is-collapsed .loop-flow-head {
    border-bottom: 0;
    padding-bottom: 10px;
  }

  .app-page #loop-popover.is-collapsed .loop-flow-head .cmd-pop-title {
    flex: 0 0 auto;
  }

  .app-page #loop-popover.is-collapsed .loop-flow-summary {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    color: var(--mk-ink-2);
  }

  .app-page #loop-popover.is-collapsed .loop-flow-use:not([hidden]) {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 5px 12px;
    border: 1px solid var(--mk-accent);
    border-radius: var(--mk-radius-xs);
    background: var(--mk-accent);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    color: #fff;
    cursor: pointer;
  }

  /* The one notice left in loop mode ("Click the map where you'd like to
     start") sits above the folded sheet rather than on it. */
  .app-page #map-container.loop-mode #map-notices {
    bottom: 64px;
  }

  /* ONE SHEET AT A TIME. On a phone the layers panel is a sheet along the
     same bottom edge, with its header always showing; while a loop is being
     chosen, that edge is the loop generator's (reproduced 2026-09-27: a
     layers sheet remembered open left a sliver of map to pick a start in,
     and the folded loop sheet sat on top of it). The layers you had on stay
     on; the panel comes back when the loop generator closes. */
  .app-page #map-container.loop-mode #layers-panel {
    display: none;
  }
}

/* MY AREAS: the loop generator's highlighter (src/loopAreas.js, 2026-09-27).
   Each rule that sets a display is matched by a [hidden] rule, because a
   class that sets display beats the bare hidden attribute (CLAUDE.md). */
.loop-weight-row[hidden],
.loop-preset[hidden],
.loop-use-areas[hidden],
.areas-toolbar[hidden],
.areas-toolbar [hidden] {
  display: none;
}

.loop-use-areas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-top: 6px;
}

.loop-use-areas-check {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.loop-use-areas-check input {
  margin: 0;
  accent-color: var(--mk-accent);
}

.loop-area-modes {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 999px;
  background: var(--mk-paper);
}

.loop-area-modes button {
  padding: 3px 9px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.loop-area-modes button[aria-pressed="true"] {
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  font-weight: 600;
}

.loop-area-modes button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.loop-candidate-areas {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}

.loop-candidate-areas span {
  display: inline-block;
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--area-colour) 24%, #fff);
  color: var(--mk-ink);
  font-weight: 600;
}

/* The painting toolbar: bottom right of the map on a computer, clear of the
   loop panel on the left; along the top on a phone, where the panel is a
   folded sheet at the bottom. */
.areas-toolbar {
  position: absolute;
  z-index: 1360;
  right: 12px;
  bottom: 28px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  max-width: min(640px, calc(100% - 400px));
  padding: 8px 10px;
  border: 1px solid var(--mk-line);
  border-radius: 12px;
  background: var(--mk-paper);
  box-shadow: 0 6px 20px -8px rgba(20, 24, 27, 0.35);
  font-size: 12.5px;
}

.areas-tools,
.areas-pens,
.areas-actions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}

.areas-toolbar button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--mk-line-strong);
  border-radius: 999px;
  background: var(--mk-paper);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.areas-toolbar button:hover {
  border-color: var(--mk-accent-border);
  color: var(--mk-ink);
}

.areas-toolbar button[aria-pressed="true"] {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  font-weight: 600;
}

.areas-toolbar button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.areas-toolbar .areas-icon-like {
  color: var(--area-like, #8bc34a);
}

.areas-toolbar .areas-icon-avoid {
  color: var(--area-avoid, #d32f2f);
}

/* Scoped by id: .areas-toolbar button (0,1,1) would otherwise beat the class. */
.areas-toolbar #areas-done {
  border-color: var(--mk-accent);
  background: var(--mk-accent);
  color: #fff;
  font-weight: 600;
}

.areas-toolbar #areas-clear.is-confirm {
  border-color: var(--mk-danger-border);
  background: var(--mk-danger-soft);
  color: var(--mk-danger);
  font-weight: 600;
}

.areas-fill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.areas-fill input {
  margin: 0;
  accent-color: var(--mk-accent);
}

.areas-toolbar-note {
  flex-basis: 100%;
  margin: 0;
  font-size: 11.5px;
  color: var(--mk-ink-3);
}

.areas-toolbar-note:empty {
  display: none;
}

.areas-toolbar-note.is-error {
  color: var(--mk-danger);
}

/* While painting, one finger paints and the page never scrolls. Leaflet sets
   touch-action: pan-x pan-y on .leaflet-touch-zoom once dragging is off,
   which would let a finger scroll the page instead; this outranks it. */
.leaflet-container.areas-drawing.leaflet-touch-zoom,
.leaflet-container.areas-drawing {
  touch-action: none;
}

.leaflet-container.areas-drawing:not([data-area-tool="move"]) {
  cursor: crosshair;
}

@media (max-width: 767px) {
  .areas-toolbar {
    top: 62px;
    right: 8px;
    bottom: auto;
    left: 8px;
    max-width: none;
  }
}

/* THE SHAPE OF THE RUN (src/loopFlow.js, 2026-09-28): four buttons, each a
   small drawing of the shape over its name, the chosen one in the accent. A
   rule that sets display is matched by a [hidden] rule (CLAUDE.md). */
.loop-shapes {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px;
}

.loop-shapes button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 2px 5px;
  border: 1px solid var(--mk-line-strong);
  border-radius: var(--mk-radius-xs);
  background: var(--mk-paper);
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--mk-ink-2);
  cursor: pointer;
}

.loop-shapes button[hidden] {
  display: none;
}

.loop-shapes button:hover:not(:disabled) {
  border-color: var(--mk-accent);
  color: var(--mk-ink);
}

.loop-shapes button[aria-pressed="true"] {
  border-color: var(--mk-accent-border);
  background: var(--mk-accent-soft);
  color: var(--mk-accent);
  font-weight: 600;
}

.loop-shapes button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.loop-candidate-shape {
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--mk-ink-2);
}
