/* base.css — reset, layout, UI chrome.
 *
 * Colour and type tokens live in css/tokens.css + css/type.css, which index.html loads
 * FIRST (both generated from packages/brand — see that package's README).
 *
 * Two families of token, and the difference is load-bearing:
 *
 *   --color-primary / --color-accent  TENANT-owned. Overwritten at runtime by
 *     applyTheme() in js/main.js from GET /api/config, so a white-labelled campus
 *     renders in its own identity. The values below are only the seed for a tenant
 *     that never chose a colour; they match BRANDING_DEFAULTS in @wayfinder/shared-types.
 *
 *   --wf-*  PRODUCT-owned and unreachable from tenant branding. Navigation affordances
 *     have to read the same on every campus, so the route line, the user puck, the AR
 *     arrow, the focus ring and all map furniture sit here — never on --color-primary.
 *
 * `--color-accent`'s real role throughout this file is "success / arrived" (the arrival
 * card, the ETA, the on-route chip), which is why its default is the brand's Success
 * green rather than a second brand colour.
 */
:root {
  --color-primary: #ff5a3c;
  --color-accent: #16b364;
  /* The label to place ON those fills. applyTheme() recomputes both per tenant (white or
     Ink 950, whichever has more contrast) — these seeds are the answer for the defaults
     above, and are what renders if the config fetch has not landed yet. White on Coral 500
     is only 3.10:1; Ink 950 on it is 6.12:1. */
  --color-on-primary: #0c1117;
  --color-on-accent: #0c1117;
  font-family: var(--wf-font-body);
}

* {
  box-sizing: border-box;
}

/* A single visible focus indicator for the whole app: 2px Route Blue, offset so it clears
   the control (Brand Guidelines v2.0 §10). Route Blue rather than the tenant colour on
   purpose — a campus is free to pick a pale brand colour, and keyboard users would then
   have no usable indicator. :focus-visible keeps it off mouse/touch interactions. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--wf-focus-width) solid var(--wf-focus);
  outline-offset: var(--wf-focus-offset);
}

/* Honor the `hidden` attribute/property even on elements we give a `display` to
   (e.g. #nav-search and #route-label are display:flex). Without this, toggling
   `.hidden` in JS leaves them visible — which showed the empty route chip on boot
   and left the from/to form on screen next to its collapsed "From → To" chip. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  height: 100%;
}

/* Full-bleed map with the navigation UI floating over it (demo-style). */
#app {
  position: relative;
  block-size: 100dvh;
  inline-size: 100vw;
  overflow: hidden;
}

#map {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Google-Maps-style layout: the map is the major view, with a COMPACT top bar (brand +
   language + from/to search) and the route info in a bottom sheet. */

/* Top search bar — full-width on phones, a left card on wide screens. */
#nav-panel {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 1000;
  max-block-size: 70dvh;
  overflow-y: auto;
  padding: 6px 10px calc(6px + env(safe-area-inset-top, 0px));
  background: color-mix(in srgb, var(--wf-paper) 96%, transparent);
  backdrop-filter: blur(6px);
  box-shadow: 0 2px 10px rgb(0 0 0 / 16%);
}
@media (min-width: 641px) {
  #nav-panel {
    inset-inline-end: auto;
    inset-block-start: 12px;
    inset-inline-start: 12px;
    inline-size: 380px;
    border-radius: 12px;
  }
}

/* Bottom route sheet — slides up when a route is previewed; drag the handle to resize
   between peek and expanded. Its block-size is set inline by makeBottomSheet (snap
   states); the rest is styling. */
#route-sheet,
#info-sheet {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 1000;
  block-size: 22dvh; /* fallback until JS sets the snapped height */
  max-block-size: 88dvh;
  overflow-y: auto;
  /* Reserve the scrollbar's width whether or not it is showing. Without this, on a
     platform with classic (space-taking) scrollbars the sheet can oscillate: content
     overflows by a hair → scrollbar appears → content column narrows → text rewraps
     one line taller → the height re-fit grows the sheet → scrollbar disappears → the
     column widens → it rewraps shorter → … which reads as the panel bouncing. On
     overlay-scrollbar platforms (most phones) this is a no-op. */
  scrollbar-gutter: stable;
  padding: 0 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--wf-paper);
  border-start-start-radius: 16px;
  border-start-end-radius: 16px;
  box-shadow: 0 -2px 16px rgb(0 0 0 / 18%);
  transform: translateY(110%);
  transition: transform 0.25s ease, height 0.2s ease;
}
/* The info sheet sits above the route sheet when both are open (tap-to-inspect flow). */
#info-sheet {
  z-index: 1001;
}
#route-sheet.wf-sheet--open,
#info-sheet.wf-sheet--open {
  transform: translateY(0);
}
/* No height transition while actively dragging — follow the finger. */
#route-sheet.wf-sheet--dragging,
#info-sheet.wf-sheet--dragging {
  transition: none;
}

/* The grab handle: a centered pill, sticky so it's always reachable while content scrolls. */
.wf-sheet-handle {
  position: sticky;
  inset-block-start: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-inline: -16px;
  padding: 5px 0 4px;
  background: var(--wf-paper);
  cursor: grab;
  touch-action: none; /* the handle owns the drag gesture, not the scroller */
}
/* Compact peek: distance/ETA sit close together above the Start button. */
#route-summary {
  margin: 0;
}
#route-summary p {
  margin-block: 2px;
}
.wf-sheet-handle:active {
  cursor: grabbing;
}
.wf-sheet-handle::before {
  content: "";
  inline-size: 40px;
  block-size: 5px;
  border-radius: 3px;
  background: var(--wf-ink-300);
}

#route-sheet #start-nav {
  inline-size: 100%;
  margin-block-start: 4px;
}
@media (min-width: 641px) {
  #route-sheet {
    inset-inline-end: auto;
    inset-inline-start: 12px;
    inset-block-end: 12px;
    inline-size: 380px;
    border-radius: 12px;
  }
}

/* Brand header — tenant logo + title + language picker. */
#nav-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-block-end: 5px;
}
.nav-brand__logo {
  block-size: 22px;
  inline-size: auto;
  border-radius: 4px;
}
.nav-brand__titles {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.nav-brand__title {
  min-inline-size: 0;
  /* The wordmark — Space Grotesk Medium, per the type scale. */
  font-family: var(--wf-font-display);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--wf-ink-900);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The WayFinder credit under the campus name. Hidden entirely on white-label plans
   (main.js gates it on branding.hideWayfinderBranding). */
.nav-brand__tagline {
  font-size: 11px;
  line-height: 1.1;
  color: var(--wf-ink-500);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-lang {
  flex: 0 0 auto;
  padding: 4px 6px;
  border-radius: 6px;
  border: 1px solid var(--wf-ink-300);
  background: var(--wf-paper);
  font-size: 13px;
}

/* From/To search, stacked. */
#nav-search {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* The explicit `display:flex` above beats the [hidden] attribute's display:none, so make
   hiding work explicitly — otherwise the inputs AND the collapsed chip both show. */
#nav-search[hidden] {
  display: none;
}

/* Collapsed "From → To" chip — REPLACES the search form once a route is previewed (so the
   inputs fold away and only this compact row shows). Tap it to edit the endpoints again. */
#route-label {
  display: flex;
  align-items: center;
  gap: 8px;
  inline-size: 100%;
  padding: 6px 10px;
  min-block-size: var(--wf-tap-min);
  border: 1px solid var(--wf-ink-300);
  border-radius: 8px;
  background: var(--wf-paper);
  text-align: start;
  cursor: pointer;
}
.route-label__text {
  flex: 1;
  min-inline-size: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--wf-ink-900);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.route-label__edit {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-primary);
}

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

/* A small leading dot marks each row (start = hollow, destination = filled primary). */
.nav-row::before {
  content: "";
  flex: 0 0 auto;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  border: 2px solid var(--color-primary);
}
.nav-row:nth-of-type(2)::before {
  background: var(--color-primary);
}

#from-search,
#dest-search {
  flex: 1;
  min-inline-size: 0;
  padding: 8px 38px 8px 10px; /* right pad leaves room for the custom clear (✕) button */
  font-size: 16px; /* >=16px stops iOS Safari auto-zooming the input on focus */
  border: 1px solid var(--wf-ink-300);
  border-radius: 6px;
  min-block-size: var(--wf-tap-min); /* comfortable touch target */
}

/* Custom clear (✕) button for the search inputs. iOS Safari does not render the native
   type=search cancel button, so we draw our own; hide the WebKit one where it DOES exist
   (macOS Safari / Chrome) to avoid a double ✕. */
#from-search::-webkit-search-cancel-button,
#dest-search::-webkit-search-cancel-button {
  appearance: none;
  -webkit-appearance: none;
}
.search-field {
  position: relative;
  flex: 1;
  min-inline-size: 0;
  display: flex;
}
.search-clear {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 7px;
  transform: translateY(-50%);
  inline-size: 26px;
  block-size: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-primary) 10%, var(--wf-ink-100));
  color: var(--wf-ink-700);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
/* Shown only when the field has text (empty → placeholder visible → hide the button). */
.search-field input:placeholder-shown ~ .search-clear {
  display: none;
}

/* Compact icon squares (GPS "use my location", Scan QR) sitting next to an input —
   sized/bordered to match it, NOT a full-width primary bar. */
.icon-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--wf-tap-min);
  min-block-size: var(--wf-tap-min);
  border: 1px solid var(--wf-ink-300);
  border-radius: 6px;
  background: var(--wf-paper);
  color: var(--color-primary);
  cursor: pointer;
}
.icon-btn svg {
  display: block;
}

/* Start (in the sheet) is the one primary call-to-action: full-width, icon + label. */
#start-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--color-primary);
  color: var(--color-on-primary);
  border: 0;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 15px;
  font-weight: 600;
  min-block-size: 46px;
  cursor: pointer;
  box-shadow: 0 1px 6px color-mix(in srgb, var(--color-primary) 40%, transparent);
}
#start-nav svg {
  flex: 0 0 auto;
}
#start-nav:disabled {
  background: color-mix(in srgb, var(--color-primary) 35%, var(--wf-ink-300));
  cursor: default;
}

#search-results {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  max-block-size: 38dvh;
  overflow-y: auto;
}

#search-results li {
  padding: 8px;
  cursor: pointer;
  border-radius: 6px;
}

#search-results li:hover {
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

/* Room results render two lines: the room name and a muted "Floor · Building" line. */
#search-results li .result-name {
  display: block;
}

#search-results li .result-sub {
  display: block;
  font-size: 0.8em;
  opacity: 0.7;
}

/* Destination picker — the "Where would you like to go?" browse grid (category chips +
   icon cards). Shown once a Start is set / after a QR scan (see navigate.js showPicker). */
#dest-picker {
  margin-top: 6px;
  /* On a phone this list collapses to ONE column (the 380px breakpoint below), so every
     card costs a whole row and only ~4 fit. The spacing through this block is trimmed to
     buy back roughly two more without touching a single font size — the names stay at
     0.9rem, the icons stay legible; it is the padding around them that was generous. */
  max-block-size: 52dvh;
  overflow-y: auto;
}

.wf-picker__title {
  margin: 2px 2px 6px;
  font-size: 1.02rem;
  font-weight: 700;
}

.wf-picker__chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  scrollbar-width: none;
}
.wf-picker__chips::-webkit-scrollbar {
  display: none;
}

.wf-chip {
  flex: 0 0 auto;
  padding: 6px 14px;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-primary) 8%, var(--wf-ink-100));
  color: inherit;
  font: inherit;
  font-size: 0.86rem;
  cursor: pointer;
  white-space: nowrap;
}
.wf-chip--active {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.wf-picker__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
@media (max-width: 380px) {
  .wf-picker__grid {
    grid-template-columns: 1fr;
  }
}

.wf-dest-card {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border: 1px solid color-mix(in srgb, var(--color-primary) 14%, var(--wf-ink-100));
  border-radius: 12px;
  background: var(--wf-paper);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.wf-dest-card:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 6%, var(--wf-paper));
}

.wf-dest-card__icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 34px;
  block-size: 34px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--color-primary) 12%, var(--wf-ink-100));
  color: var(--color-primary);
}

.wf-dest-card__body {
  min-inline-size: 0;
}
.wf-dest-card__name {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wf-dest-card__sub {
  display: block;
  margin-top: 2px;
  font-size: 0.78rem;
  opacity: 0.65;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Floors → rooms directory — shared by the location-info panel and the arrival card.
   The row is a label column + a rooms column, which reads well for the short labels this
   was built for ("Floor 1", "Ground"). Both columns must survive a LONG value too: a floor
   label or room name can be a full sentence, and on a 360 px phone an unshrinkable column
   pushed the row wider than the sheet, which scrolls it sideways (#route-sheet sets
   overflow-y, and CSS then computes overflow-x to auto, so the overflow becomes a
   horizontal scroll rather than being clipped). */
.floors-directory {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  /* Readable once a value actually wraps to several lines. */
  line-height: 1.45;
}
.floor-row {
  display: flex;
  /* A long label takes the whole line and the rooms fall below it, instead of the two
     columns fighting over a width neither can fit in. Short labels are unaffected: they
     still sit beside their rooms exactly as before. */
  flex-wrap: wrap;
  gap: 4px 10px;
  padding: 6px 0;
  border-block-start: 1px solid rgb(0 0 0 / 8%);
}
.floor-row__label {
  /* Was `flex: 0 0 auto` — shrink:0 is what let a sentence-length label force the row
     wider than the viewport. It may now shrink; min-inline-size keeps the short-label
     column aligned as before. */
  flex: 0 1 auto;
  min-inline-size: 68px;
  font-weight: 600;
}
.floor-row__rooms {
  flex: 1 1 auto;
  /* A flex item's min-width defaults to `auto` (its min-content), so without this the
     column refuses to shrink below its longest word and overflows anyway. Same fix as
     .wf-dest-card__body above. */
  min-inline-size: 0;
  opacity: 0.85;
}
/* Wrap anywhere as a last resort, so a single unbroken string — a pasted URL, a long
   word — breaks instead of overflowing. `break-word` first as the widely-supported
   baseline; `anywhere` then wins where supported and, unlike break-word, also lowers the
   min-content size so flex sizing above can actually shrink the column. */
.floor-row__label,
.floor-row__rooms,
.arrival-card {
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}
.floor-row__rooms .room--target {
  font-weight: 700;
  color: var(--color-primary);
}
/* "No floor information for this location." — the whole body of the panel when a place
   has no directory. As naked muted prose under a button it read as a failure; as a quiet
   filled row it reads as a stated fact, and it gives the panel a floor so the sheet has
   something to size to. */
.floors-empty {
  margin: 12px 0 0;
  padding: 12px;
  border-radius: 10px;
  background: var(--wf-ink-100);
  color: var(--wf-ink-700);
  font-size: 13px;
  text-align: center;
}

/* Location-info panel (tap a marker). */
.info-title {
  margin: 4px 0 10px;
  font-size: 1.15rem;
  /* A long place name must wrap rather than widen the sheet — same reasoning as
     .floor-row__label above. */
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}
/* The panel's primary action. Full-width and the same height as #start-nav in the route
   sheet: it is the same kind of commitment, it should not be a small inline chip, and a
   46px target is the minimum comfortable one on a phone. */
.nav-here-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  min-block-size: 46px;
  padding: 10px 16px;
  border: 0;
  border-radius: 10px;
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

/* Rich arrival card (shown in the route sheet on arrival). */
.arrival-card__title {
  margin: 2px 0;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--color-primary);
}
/* Admin-authored arrival message. `pre-line` keeps the operator's paragraph breaks while
   still wrapping normally — `pre`/`pre-wrap` would also preserve the leading indentation
   of a pasted block and force a horizontal scroll on a 360 px phone. The wrap rules match
   the floors directory: break a long unbroken string rather than overflow the sheet. */
.arrival-card__message {
  margin: 6px 0;
  white-space: pre-line;
  line-height: 1.5;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}
.arrival-card__target {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 6px 0;
  padding: 8px 12px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}
.arrival-card__room {
  font-size: 1.05rem;
  font-weight: 700;
}
.arrival-card__floor {
  opacity: 0.85;
}

#route-summary .offline-badge {
  color: var(--color-accent);
  font-weight: 600;
}

/* Active campus-event banner — floats just above the collapsed route sheet so it never
   covers the top search; an expanded sheet (higher z-index) slides over it. */
#event-banner {
  position: absolute;
  inset-inline: 8px;
  inset-block-end: calc(19dvh + 10px);
  z-index: 999;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-accent) 90%, var(--wf-ink-950));
  color: var(--wf-paper);
  box-shadow: 0 2px 12px rgb(0 0 0 / 28%);
}
#event-banner[hidden] {
  display: none;
}
.event-banner__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-inline-size: 0;
}
.event-banner__title {
  font-weight: 700;
}
.event-banner__text {
  font-size: 13px;
  opacity: 0.95;
}
.event-banner__close {
  flex: 0 0 auto;
  padding: 0 2px;
  background: transparent;
  border: 0;
  color: var(--wf-paper);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.85;
}

/* Keep the turn-by-turn list short so it never dominates the panel. */
#route-summary .steps {
  max-block-size: 20dvh;
  overflow-y: auto;
}

/* Weak-GPS notice, under the Start button. It is an advisory, not an error — the fix is
   to wait or step outside, and the route is still usable meanwhile. So it gets the
   smallest type in the sheet and sits centred under the button, reading as a footnote to
   it rather than left-aligned prose competing with the destination and ETA above. */
#gps-indicator {
  color: var(--wf-warning-text);
  margin-block-start: 6px;
  font-size: 11px;
  line-height: 1.3;
  text-align: center;
}

/* Floating status toast — a lightweight snackbar above the map, independent of the route
   sheet, so navigation validations (e.g. an off-campus warning) are visible even when the
   sheet is closed. It is the single notification surface reused by every setStatus() call;
   JS auto-dismisses it after a few seconds. */
#nav-status.nav-toast {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  inset-block-end: calc(19dvh + env(safe-area-inset-bottom, 0px));
  /* Above the route sheet (1000), the event banner (999) AND the AR overlay (1500): AR
     raises warnings here (the compass-denied notice fires right after AR starts), so a
     toast that lost to the camera would never be seen. */
  z-index: 1600;
  inline-size: max-content;
  max-inline-size: min(92vw, 420px);
  padding: 11px 16px;
  border-radius: 14px;
  background: var(--wf-ink-900); /* neutral (info) default; kinds override below */
  color: var(--wf-paper);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  box-shadow: 0 6px 22px rgb(0 0 0 / 30%);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  pointer-events: none; /* never intercept map/sheet taps */
}
#nav-status.nav-toast.nav-toast--in {
  opacity: 1;
  transform: translateY(0);
}
#nav-status[hidden] {
  display: none;
}
/* The only toast whose ground is a TENANT colour, so its label is computed rather than
   inherited from .nav-toast's white — Success green carries white at just 2.74:1, and a
   tenant is free to set the accent to something lighter still. The other kinds sit on the
   deep semantic variants, where white is correct. */
#nav-status[data-kind="success"] {
  background: var(--color-accent);
  color: var(--color-on-accent);
}
#nav-status[data-kind="warn"] {
  background: var(--wf-warning-text);
}
#nav-status[data-kind="error"] {
  background: var(--wf-error-text);
}
#nav-status[data-kind="info"] {
  background: var(--wf-ink-900);
}

/* "Re-centre" — the control that gives the follow camera back after the user has dragged
   or rotated the map mid-navigation. Bottom-RIGHT, clear of the sheet and above it, so it
   sits where the thumb already is and never covers the next-turn banner. It is only in the
   DOM-visible state while the camera is actually released, so it never competes with the
   map when nothing needs recentring. */
.nav-recenter {
  position: absolute;
  inset-inline-end: 12px;
  inset-block-end: calc(19dvh + env(safe-area-inset-bottom, 0px));
  z-index: 1400; /* above the sheet (1000) and event banner (999); UNDER the AR overlay
                    (1500) and the toast (1600) — there is no map to re-centre behind AR */
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 14px;
  border: 0;
  border-radius: 999px;
  background: var(--wf-paper);
  color: var(--wf-ink-900);
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 4px 14px rgb(0 0 0 / 25%);
  cursor: pointer;
}
.nav-recenter:active {
  transform: scale(0.97);
}
.nav-recenter[hidden] {
  display: none;
}

/* Off-route warning. Unlike the status toast this one PERSISTS — it is up for exactly as
   long as the user is off the route — so it sits above the map near the top, clear of the
   toast's band, and carries the error ground rather than a kind attribute. */
#nav-offroute.nav-offroute {
  position: absolute;
  inset-inline: 0;
  margin-inline: auto;
  /* Default is the old guess; navigate.js overrides it with the panel's MEASURED bottom
     edge when the banner is shown, so it lands below the header instead of on top of the
     route selector. The fallback keeps the safe-area term; the measured value is already
     viewport-absolute and must not add it twice. */
  inset-block-start: var(--wf-offroute-top, calc(96px + env(safe-area-inset-top, 0px)));
  z-index: 1100;
  inline-size: max-content;
  max-inline-size: min(92vw, 420px);
  padding: 10px 16px;
  border-radius: 14px;
  background: var(--wf-error-text);
  color: var(--wf-paper);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  box-shadow: 0 6px 22px rgb(0 0 0 / 30%);
  pointer-events: none; /* never intercept map/sheet taps */
}
#nav-offroute[hidden] {
  display: none;
}

/* QR scan overlay — full-screen camera feed with a cancel button. */
#scan-overlay {
  position: fixed;
  inset: 0;
  z-index: 18;
  background: var(--wf-ink-950);
}

#scan-overlay #scan-video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

#scan-overlay .scan-close {
  position: absolute;
  inset-block-start: 16px;
  inset-inline-end: 16px;
  z-index: 1;
  padding: 8px 12px;
}

/* In-route action row (after Start): AR • Voice • End — three EQUAL-width buttons on one
   row, each an icon over a short label (Google-Maps-style) so the sheet stays compact. */
.nav-actions {
  display: flex;
  gap: 8px;
  margin-block-start: 10px;
}
.nav-actions button {
  flex: 1 1 0; /* equal width — [ AR ] [ Voice ] [ End ] */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-inline-size: 0;
  min-block-size: 52px;
  padding: 7px 8px;
  border: 0;
  border-radius: 10px;
  font-weight: 600;
  font-size: 12px;
  cursor: pointer;
}
.nav-actions button svg {
  flex: 0 0 auto;
}
.nav-actions .btn-label {
  line-height: 1;
}
/* AR + Voice are tonal (soft brand tint), End is a quiet destructive — the map stays the star. */
.ar-btn {
  background: color-mix(in srgb, var(--color-accent) 16%, var(--wf-paper));
  color: color-mix(in srgb, var(--color-accent) 75%, var(--wf-ink-950));
}
.speak-btn {
  background: color-mix(in srgb, var(--color-primary) 14%, var(--wf-paper));
  color: color-mix(in srgb, var(--color-primary) 78%, var(--wf-ink-950));
}
.speak-btn--muted {
  opacity: 0.55;
}
.end-btn {
  background: var(--wf-ink-100);
  color: var(--wf-error-text);
}

/* Preview summary card (before Start): a prominent travel time, then distance · arrival. */
.route-preview {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-block: 2px 8px;
}
/* "4 min · 268 m · arrive 06:12" — the Data style (JetBrains Mono). Tabular figures keep
   the numbers from jittering as the ETA counts down mid-walk. */
.route-preview .eta {
  font-family: var(--wf-font-mono);
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-accent);
  line-height: 1.1;
}
.route-preview__sub {
  font-family: var(--wf-font-mono);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--wf-ink-700);
}
.route-preview__sep {
  margin-inline: 5px;
  color: var(--wf-ink-300);
}

/* ── Ongoing-event highlighting: pre-Start destination card, search pills, picker cards ── */

/* "Your destination is X (for <event> event)" card, above Start in preview. */
.dest-card {
  margin-block: 2px 8px;
  padding: 10px 12px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-primary) 8%, var(--wf-paper));
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--wf-ink-900);
}
.dest-card--event {
  background: color-mix(in srgb, var(--color-accent) 14%, var(--wf-paper));
  border: 1px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
}
.dest-card__event {
  color: color-mix(in srgb, var(--color-accent) 78%, var(--wf-ink-950));
  font-weight: 800;
}

/* Ongoing-event search result: the event name is the (highlighted) header line. */
#search-results li .result-name--event {
  color: color-mix(in srgb, var(--color-accent) 78%, var(--wf-ink-950));
  font-weight: 700;
}

/* Ongoing-event highlight on a picker destination card (event name = header, location = sub). */
.wf-dest-card--event {
  border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
  background: color-mix(in srgb, var(--color-accent) 8%, var(--wf-paper));
}
.wf-dest-card__name--event {
  white-space: normal; /* event names are longer than a place name — let them wrap */
  color: color-mix(in srgb, var(--color-accent) 78%, var(--wf-ink-950));
}

/* ── Live navigation sheet (after Start): compact + informative, Google-Maps style ── */

/* Next-turn banner — a bold maneuver glyph + the instruction and the live distance to the
   turn. This is the at-a-glance line the walker reads. */
.nav-next {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-block-start: 0;
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-primary) 12%, var(--wf-paper));
}
.nav-next__glyph {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 38px;
  block-size: 38px;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: 22px;
  line-height: 1;
}
.nav-next__body {
  min-inline-size: 0;
}
.nav-next__instruction {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--wf-ink-900);
  line-height: 1.2;
}
.nav-next__distance {
  margin: 1px 0 0;
  font-family: var(--wf-font-mono);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-primary);
}

/* Meta strip — remaining time • distance • arrival clock, kept on ONE line. */
.nav-meta {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  gap: 6px;
  margin-block-start: 6px;
  font-family: var(--wf-font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--wf-ink-700);
  white-space: nowrap;
  overflow: hidden;
}
.nav-meta__time {
  font-weight: 700;
  color: var(--color-accent);
}
.nav-meta__sep {
  color: var(--wf-ink-300);
}

/* Compact in-route step list (revealed by dragging the sheet up). */
#route-summary .steps {
  list-style: none;
  margin-block: 8px 0;
  padding: 0;
}
#route-summary .steps li {
  padding: 6px 4px;
  border-block-start: 1px solid var(--wf-ink-100);
  font-size: 13px;
  color: var(--wf-ink-700);
}

/* Visitor-capture overlay — a centered, mobile-friendly card shown over the app on a
   first QR scan when the tenant enables it. Sits above the sheets/scan overlay (z 2000). */
.visitor-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgb(0 0 0 / 45%);
  backdrop-filter: blur(2px);
}

.visitor-card {
  inline-size: 100%;
  max-inline-size: 380px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px 20px;
  border-radius: 16px;
  background: var(--wf-paper);
  box-shadow: 0 12px 40px rgb(0 0 0 / 25%);
}

.visitor-card__title {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  color: var(--wf-ink-900);
}

.visitor-card__subtitle {
  margin: -6px 0 2px;
  font-size: 14px;
  color: var(--wf-ink-500);
}

.visitor-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.visitor-field__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--wf-ink-700);
}

.visitor-field__input {
  inline-size: 100%;
  padding: 12px 14px;
  font-size: 16px; /* >=16px avoids iOS zoom-on-focus */
  border: 1px solid var(--wf-ink-300);
  border-radius: 10px;
  background: var(--wf-paper);
  color: var(--wf-ink-900);
}

/* Border emphasis only — the focus RING comes from the global :focus-visible rule, so it
   is the same 2px Route Blue here as everywhere else. Previously this set `outline: none`
   and drew its own tenant-coloured glow, which meant a campus with a pale brand colour had
   an effectively invisible focus indicator. */
.visitor-field__input:focus {
  border-color: var(--color-primary);
}

.visitor-field__input[aria-invalid="true"] {
  border-color: var(--wf-error);
}

.visitor-field__error {
  font-size: 12px;
  color: var(--wf-error-text);
}

.visitor-card__error {
  margin: 0;
  font-size: 13px;
  color: var(--wf-error-text);
}

.visitor-card__submit {
  margin-block-start: 4px;
  padding: 13px 16px;
  font-size: 16px;
  font-weight: 600;
  color: var(--color-on-primary);
  background: var(--color-primary);
  border: none;
  border-radius: 10px;
  cursor: pointer;
}

.visitor-card__submit:disabled {
  opacity: 0.65;
  cursor: default;
}

/* Modal alert — a warning the user has to acknowledge (currently: picking the same place as
   both source and destination). Deliberately NOT the #nav-status toast: that auto-dismisses
   after 3.5s and is easy to miss mid-tap. Same card language as the visitor overlay, one step
   higher (2100) so it also covers that. */
.wf-alert {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgb(0 0 0 / 45%);
  backdrop-filter: blur(2px);
}

.wf-alert__card {
  inline-size: 100%;
  max-inline-size: 340px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px 20px;
  border-radius: 16px;
  background: var(--wf-paper);
  box-shadow: 0 12px 40px rgb(0 0 0 / 25%);
  text-align: center;
}

.wf-alert__title {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--wf-ink-900);
}

.wf-alert__body {
  margin: 0;
  font-size: 14px;
  color: var(--wf-ink-500);
}

.wf-alert__ok {
  margin-block-start: 6px;
  min-block-size: var(--wf-tap-min);
  padding: 12px 16px;
  font-size: 16px;
  font-weight: 600;
  color: var(--color-on-primary);
  background: var(--color-primary);
  border: none;
  border-radius: 10px;
  cursor: pointer;
}

/* The scan chooser's two options. They are EQUAL choices — a scanned code names a place,
   not a role — so neither gets the filled primary treatment that would nudge the user
   towards one. Outlined, full width, stacked: one thumb-sized target each on a phone. */
/* The card takes focus when a chooser opens so keyboard users land inside the dialog, but
   it is a container, not a control — the visible ring belongs on the buttons they tab to. */
.wf-alert__card:focus-visible {
  outline: none;
}

.wf-alert__actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block-start: 6px;
}

.wf-alert__choice {
  min-block-size: var(--wf-tap-min);
  padding: 12px 16px;
  font-size: 16px;
  font-weight: 600;
  color: var(--wf-ink-900);
  background: var(--wf-paper);
  border: 1px solid var(--wf-ink-300);
  border-radius: 10px;
  cursor: pointer;
}

.wf-alert__choice:active {
  background: var(--wf-ink-100);
}

/* Cancel is deliberately quiet — it is the "changed nothing" exit, not a third option. */
.wf-alert__cancel {
  min-block-size: var(--wf-tap-min);
  padding: 10px 16px;
  font-size: 15px;
  color: var(--wf-ink-500);
  background: none;
  border: none;
  cursor: pointer;
}
