/* ========================================
   Map Overlay & Zone Info Panel
   MGH ED Patient Portal
   ======================================== */

/* ========================================
   Map Overlay (Full-screen modal)
   ======================================== */

.map-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background-color: var(--bg-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-sm);
}

.map-overlay[hidden] {
  display: none;
}

.map-container {
  background-color: var(--bg);
  border-radius: var(--border-radius);
  width: 100%;
  max-width: 800px;
  height: 90vh;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--glass-shadow);
  border: 1px solid var(--glass-border);
  position: relative;
}

/* ========================================
   Map Header
   ======================================== */

.map-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.map-header-text {
  display: flex;
  flex-direction: column;
}

.map-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
  margin: 0;
}

.map-subtitle {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  margin: 0;
}

.map-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.map-header-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--min-touch-target);
  height: var(--min-touch-target);
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text);
  border-radius: var(--border-radius-sm);
  transition: background-color var(--transition-fast), transform var(--transition-fast);
}

/* Press feedback (see the press-feedback block in components.css). */
@media (prefers-reduced-motion: no-preference) {
  .map-header-btn:active {
    transform: scale(0.9);
  }
}

.map-header-btn:hover {
  background-color: var(--bg-surface);
}

.map-header-btn:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}

/* ========================================
   Floating zoom controls

   These live over the map rather than in the header: four 44px buttons in
   the header squeezed the text column to 140px and wrapped the gesture hint
   onto a third line, costing map height. Floating them is also the familiar
   maps pattern. In the DOM they sit AFTER the SVG container (2026-07-23
   focus-order review): the tab sequence is the 19 zones, then + / -, then
   the modal trap wraps to the top; immediate keyboard zoom stays available
   through the focused map body's + / - key handling.
   ======================================== */

.map-zoom-controls {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  z-index: 2;
  display: flex;
  flex-direction: column;
  border-radius: var(--border-radius-sm);
  overflow: hidden;
  background-color: var(--bg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-tab);
}

.map-zoom-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--min-touch-target);
  height: var(--min-touch-target);
  border: none;
  background-color: var(--bg);
  cursor: pointer;
  color: var(--text);
  transition: background-color var(--transition-fast);
}

.map-zoom-btn + .map-zoom-btn {
  border-top: 1px solid var(--border);
}

.map-zoom-btn:hover {
  background-color: var(--bg-surface);
}

/* Joined pair in one pill: a background flash reads better than a scale
   (which would shear against the shared border) and works under reduced
   motion. */
.map-zoom-btn:active:not(:disabled) {
  background-color: var(--bg-surface);
}

.map-zoom-btn:focus-visible {
  outline: var(--focus-outline);
  outline-offset: calc(var(--focus-offset) * -1);
}

.map-zoom-btn svg {
  width: var(--icon-size);
  height: var(--icon-size);
}

/* A zoom button whose direction is exhausted (camera at min or max zoom). */
.map-zoom-btn:disabled {
  color: var(--text-muted);
  opacity: 0.5;
  cursor: default;
}

.map-zoom-btn:disabled:hover {
  background-color: transparent;
}

.map-header-btn svg {
  width: var(--icon-size);
  height: var(--icon-size);
}

/* ========================================
   Map Body
   ======================================== */

.map-body {
  flex: 1;
  overflow: hidden;
  touch-action: none;
  -webkit-overflow-scrolling: touch;
  position: relative;
}

/* The map body is focusable (tabindex=0) so keyboard users can pan with the
   arrow keys and zoom with +/-; the ring must be visible against the map.
   Colour pinned for the same reason as .zone:focus-visible - the map stays
   white in dark mode, where the themed ring goes lime-on-white. */
.map-body:focus-visible {
  outline: var(--focus-outline);
  outline-color: #4D732B;
  outline-offset: calc(var(--focus-offset) * -1);
}

/* The clip viewport for the two composited camera layers. contain: paint
   clips children to the container (self-contained clipping) and isolates
   the map's layout/paint work from the rest of the page. */
#map-svg-container {
  position: absolute;
  inset: 0;
  background-color: #FFFFFF;
  contain: layout paint;
}

/* Floorplan bitmap layer: decoded once, sized by map-touch.js for the
   current viewport, then only ever moved by a composited CSS transform.
   Never repainted during pan/zoom or commits. */
#map-svg-container #map-floorplan {
  position: absolute;
  left: 0;
  top: 0;
  /* The camera sizes this layer larger than the container on purpose; the
     global responsive img { max-width: 100% } rule would squish it
     non-uniformly against its JS-set height. */
  max-width: none;
  max-height: none;
  will-change: transform;
  transform-origin: 0 0;
  pointer-events: none;
}

/* Vector layer (zones + labels). The camera transform lives on this HTML
   wrapper, NOT on the <svg> inside it: Chromium does not composite a
   transform on an SVG root, it re-lays-out the SVG contents on every change
   (measured at one layout per frame, versus zero for an HTML element). The
   SVG's viewBox and CSS size are committed by map-touch.js on gesture rest;
   between commits the wrapper rides the same composited transform as the
   bitmap layer. */
#map-svg-container #map-vector-layer {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform;
  transform-origin: 0 0;
}

#map-svg-container #map-vector-layer svg {
  display: block;
  /* Same exemption as the bitmap layer: with preserveAspectRatio="none" a
     max-width clamp against the JS-set height stretches the map. */
  max-width: none;
  max-height: none;
}

.map-error {
  text-align: center;
  color: var(--text-muted);
  padding: var(--space-xl);
}

/* ========================================
   SVG Map Zone Styles
   ======================================== */

.zone {
  cursor: pointer;
  transition: opacity var(--transition-fast), filter var(--transition-fast);
}

/* Disable hover transitions + zone hit-testing during active pan/zoom so a
   drag never triggers a hover style or stray zone tap. */
.panning .zone {
  transition: none;
  pointer-events: none;
}

/* #map-transform-layer is a structural wrapper only (labels ride inside it);
   gesture transforms live on the HTML layers above, never on SVG elements
   (inner SVG elements get no compositing layer on iOS WebKit). */

.zone:hover {
  opacity: 0.8;
  filter: brightness(1.1);
}

.zone:focus-visible {
  outline: var(--focus-outline);
  /* The map surface is always white (see #map-svg-container), so the ring
     colour is pinned: the dark theme's lime --focus-ring sits at ~1.6:1 on
     white. #4D732B measures 4.98:1 on white. */
  outline-color: #4D732B;
  outline-offset: var(--focus-offset);
}

/* Zone identity: soft tint fill + strong self-coloured border (the Apple
   Maps "district" treatment). The saturated 0.8 slabs shouted at each other;
   colour identity now comes from the edge and the label, the fill recedes.
   The borders double as the boundary cue for colour-vision deficiency
   (adjacent composited fills measured 1.1-1.5:1). CSS opacity/fill-opacity
   override the values baked into the SVG's presentation attributes. */
.zone rect,
.zone path {
  opacity: 1;
  fill-opacity: 0.5;
  stroke: var(--zone-border, #ABABAB);
  stroke-width: 2;
  stroke-linejoin: round;
}

.zone[data-zone="red"] { --zone-border: #C62F27; }
.zone[data-zone="blue-1"],
.zone[data-zone="blue-2"],
.zone[data-zone="blue-3"] { --zone-border: #2A6FC4; }
.zone[data-zone="green"] { --zone-border: #1E9E44; }
.zone[data-zone="pink"] { --zone-border: #E36BA8; }
.zone[data-zone="purple"] { --zone-border: #6A55D8; }
.zone[data-zone="mssu"] { --zone-border: #E07000; }
.zone[data-zone="raz"] { --zone-border: #C9A227; }
.zone[data-zone="yellow"] { --zone-border: #CBBE4B; }
.zone[data-zone="cyez"] { --zone-border: #00A5BF; }

/* Back-of-house mass: flatten to a near-solid light panel so the raster's
   room detail stops competing with the ED zones (figure-ground). */
#zone-hospital {
  fill: #EDEDEE;
  fill-opacity: 0.92;
  stroke: #C8CBCC;
  stroke-width: 1.5;
}

#zone-main-WR {
  fill-opacity: 0.55;
}

/* Waiting-room seat pictogram (injected by map-labels.js): replaces three of
   the four repeated "Waiting Room" text labels; the main waiting room keeps
   the words and acts as the legend by adjacency. */
.wr-icon {
  /* The icons sit in the label layer ABOVE the zone shapes: without this
     they would swallow the tap on the waiting-room area beneath them. */
  pointer-events: none;
}

.wr-icon rect {
  fill: #5C5B5D;
  stroke: #FFFFFF;
  stroke-width: 2;
  paint-order: stroke fill;
}

/* ========================================
   Semantic zoom (progressive disclosure)

   The overview shows destinations: zones, their expansions, and the
   journey stops (Triage / Registration / Intake). Secondary detail - the
   street name, the seat pictograms, the main waiting room's own text -
   fades in once the reader zooms past DETAIL_ZOOM_FACTOR; map-touch.js
   stamps data-zoom-tier on the container at every camera commit. The
   default (no attribute yet, or tier 1) hides detail, so the overview is
   calm before the first commit too. The global reduced-motion guard in
   utilities.css collapses the fade to an instant swap.
   ======================================== */
.street-label,
.wr-icon,
.facility-label-detail {
  opacity: 0;
  transition: opacity 250ms ease;
}

#map-svg-container[data-zoom-tier="2"] .street-label,
#map-svg-container[data-zoom-tier="2"] .wr-icon,
#map-svg-container[data-zoom-tier="2"] .facility-label-detail {
  opacity: 1;
}

/* A tier flip during an active gesture (pinch crossing the threshold) must
   swap instantly: a 250ms opacity animation on SVG text mid-gesture is
   per-frame repaint work exactly when the frame budget is tightest. Same
   pattern as the .panning .zone hover-transition kill above. */
.panning .street-label,
.panning .wr-icon,
.panning .facility-label-detail {
  transition: none;
}

.zone-selected {
  /* Charcoal, not the brand greens: the lime primary sits at ~1.3:1 and the
     dark green at 1.7-2.9:1 against the mid-tone zone fills (blue, purple,
     red, green). #414042 clears 3:1 (WCAG 1.4.11) on every fill. */
  outline: 3px solid #414042;
  outline-offset: 1px;
  opacity: 0.9;
  filter: brightness(1.15);
}

/* Dynamic zone labels (injected by map-labels.js) */

/* All label colours below are literal, not theme tokens: the map surface is
   hardcoded white (see #map-svg-container) in BOTH themes, so theme-flipping
   fills turned labels near-invisible in dark mode (#E5E5E5 on white: 1.26:1).
   The solid white halo (paint-order: stroke fill) is the label's effective
   background on every zone fill: every ink below measures >=7:1 (AAA) on
   white, so contrast no longer depends on which zone a label happens to sit
   on. The halo is fully opaque and a step wider than it used to be
   (6px, was 5px at 0.9 alpha) so the text stands clear of the busy floorplan
   raster underneath. */
.zone-label,
.zone-label-sub,
.facility-label,
.street-label,
.entrance-label {
  font-family: var(--font-family);
  pointer-events: none;
  user-select: none;
  text-anchor: middle;
  dominant-baseline: central;
  paint-order: stroke fill;
  stroke: #FFFFFF;
  stroke-width: 6px;
  stroke-linejoin: round;
}

/* Semantic label scale (set by map-touch.js on every frame whose zoom
   moved - live through a pinch/wheel/zoom animation, the native-maps feel):
   labels counter-scale against the zoom so they hold a readable on-screen
   size at the overview without ballooning over their neighbours when zoomed
   in. Each label block scales about its own centre; the clamp lives in
   map-camera.js (labelScaleForZoom). Entrance labels never scale below 1:
   their halo deliberately masks the raster floorplan's own baked-in entrance
   text, and a shrunken halo would let the stale English text peek out. */
.map-label-block {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(var(--map-label-scale, 1));
}

.entrance-marker .map-label-block,
.map-label-block.entrance-block {
  transform: scale(max(1, var(--map-label-scale, 1)));
}

.zone-label {
  font-size: 18px;
  font-weight: 700;
  fill: #414042;
}

/* Typographic hierarchy (the cartographic convention: one bold style for
   destinations, smaller lighter styles for context). Zone names are the only
   bold-charcoal tier; expansions and facilities step down in size, weight
   and ink so the map reads as layers instead of a uniform shout. */
.zone-label-sub {
  /* Expansion line under the zone name. */
  font-size: 12px;
  font-weight: 500;
  fill: #4D4C4E;
}

.facility-label {
  font-size: 13px;
  font-weight: 500;
  fill: #4E4E50;
}

.entrance-marker {
  pointer-events: none;
}

.street-label {
  font-size: 20px;
  font-weight: 600;
  font-style: italic;
  fill: #4D4C4E;
}

.entrance-label {
  font-size: 13px;
  font-weight: 700;
  fill: #414042;
}

.entrance-arrow {
  fill: var(--entrance-arrow, #005EB8);
}

/* ========================================
   Zone Info Panel (Modal sheet - slides up)
   ======================================== */

.zone-info-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: var(--glass-bg-heavy);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-top: 1px solid var(--glass-border);
  padding: 0 var(--space-md) calc(var(--space-md) + env(safe-area-inset-bottom, 0px));
  box-shadow: var(--glass-shadow);
  border-radius: var(--border-radius) var(--border-radius) 0 0;
  transform: translateY(0);
  /* transform (show/hide slide) is compositor-cheap; height (the .expanded
     toggle) is a deliberate one-shot layout animation - the sheet's two
     heights lay content out differently, so a transform trick can't fake
     it. border-radius was in this list but no state ever changes it -
     removed (dead transition entries invite accidental layout anims). */
  transition: transform var(--transition-normal),
              height var(--transition-normal);
  z-index: 1001;
  max-width: 800px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  height: clamp(15rem, 40vh, 24rem);
  max-height: calc(100vh - env(safe-area-inset-top, 0px) - var(--space-lg));
  overflow: hidden;
  box-sizing: border-box;
}

.zone-info-panel[hidden] {
  display: flex;
  transform: translateY(calc(100% + env(safe-area-inset-bottom, 0px)));
  pointer-events: none;
}

.zone-info-panel.dragging {
  transition: none;
}

.zone-info-panel.expanded {
  height: calc(100vh - env(safe-area-inset-top, 0px) - var(--space-lg));
}

.zone-info-handle-area {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--min-touch-target);
  padding: var(--space-sm) 0;
  cursor: grab;
  flex-shrink: 0;
  touch-action: none;
  width: 100%;
  border: 0;
  background: none;
  color: inherit;
  appearance: none;
}

.zone-info-panel.dragging .zone-info-handle-area {
  cursor: grabbing;
}

.zone-info-handle {
  width: 36px;
  height: 5px;
  background-color: var(--text-muted);
  border-radius: 2px;
  opacity: 0.4;
}

.zone-info-handle-area:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
  border-radius: var(--border-radius-sm);
}

.zone-info-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-xs);
  flex-shrink: 0;
  touch-action: none;
  user-select: none;
}

.zone-info-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--min-touch-target);
  height: var(--min-touch-target);
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-secondary);
  border-radius: var(--border-radius-sm);
  flex-shrink: 0;
  margin-right: calc(-1 * var(--space-xs));
}

.zone-info-close:hover {
  color: var(--text);
  background-color: var(--bg-surface);
}

.zone-info-close:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}

.zone-info-close svg {
  width: 20px;
  height: 20px;
}

.zone-info-title-group {
  min-width: 0;
}

.zone-info-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
  margin: 0;
}

.zone-info-altname {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  margin: 0;
}

.zone-info-altname[hidden] {
  display: none;
}

/* Scrollable body (below sticky header) */
.zone-info-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* The dock floats above the map stack (~70-100px at the bottom of the
     screen incl. safe area); without this clearance the sheet's last lines
     sit permanently under it. */
  padding-bottom: calc(var(--space-sm) + 84px + env(safe-area-inset-bottom, 0px));
}

.zone-info-rooms {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  margin-bottom: var(--space-xs);
}

.zone-info-rooms:empty {
  display: none;
}

.zone-info-description {
  color: var(--text-secondary);
  margin-bottom: var(--space-xs);
}

/* Dark mode: the panel's glass composites over the always-white map body to
   roughly #404040, brighter than the near-black surfaces the dark muted
   tokens are calibrated for. Lift the secondary text to hold AAA 7:1
   against the composited glass (#D9D9D9 measures ~7.2:1 on #404040). */
[data-theme="dark"] .zone-info-rooms,
[data-theme="dark"] .zone-info-altname {
  color: #D9D9D9;
}

[data-theme="dark"] .zone-info-description {
  color: #DCDCDC;
}

/* Flow blocks inside the panel */
.zone-info-flow {
  margin-top: var(--space-sm);
}

.zone-info-flow:empty {
  display: none;
}

/* ========================================
   Mobile Adjustments
   ======================================== */

@media (max-width: 480px) {
  .map-overlay {
    padding: 0;
    align-items: stretch;
  }

  .map-container {
    max-width: 100%;
    max-height: 100vh;
    border-radius: 0;
    height: 100%;
  }
}

/* Landscape on phones: fill viewport, fit SVG inside, rely on pan/zoom */
@media (max-height: 500px) and (orientation: landscape) {
  .map-overlay {
    padding: 0;
    align-items: stretch;
  }

  .map-container {
    max-width: 100%;
    max-height: 100vh;
    max-height: 100dvh;
    height: 100%;
    border-radius: 0;
  }
}
