/* ============================================================= *
 * PlazaDorm v2 — flat / editorial redesign                     *
 * Light is the default theme; dark is opt-in via                *
 * :root[data-theme="dark"].                                      *
 * ============================================================= */

/* Load DM Sans from the stylesheet so every page that links it
   (index + about/privacy/terms) gets the font, not just index. */
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');

:root {
  /* --bg / --ink / --dim / --line / --red / --logo-gold come from chrome.css. */
  --red-dark: #B01228;
  --gold: #cc9b00;
  --field: #fdf8f1;
  --hover: rgba(0, 0, 0, 0.04);
  --green: #0e7a52;
  --orange: #c05f00;
  --plat-grad: linear-gradient(110deg, #5d7488 0%, #c9d6e0 25%, #8fa5b5 45%, #52687b 60%, #c9d6e0 80%, #5d7488 100%);

  /* Legacy aliases so the map / lightbox / nav machinery keeps working. */
  --text: var(--ink);
  --text-dim: var(--dim);
  --border: var(--line);
  --surface: var(--field);
  --surface2: var(--field);
  --surface3: #e9e0d3;
  --nav-bg: var(--bg);
  --blue: #2563eb;

  --font: 'DM Sans', sans-serif;
  --font-map: 'DM Sans', sans-serif;  /* map markers + key */
  font-family: var(--font);
  color: var(--ink);
}

:root[data-theme="dark"] {
  --field: #1b1a1e;
  --hover: rgba(255, 255, 255, 0.06);
  --green: #4cc492;
  --orange: #e59a4b;
  --gold: #f0c53c;
  --plat-grad: linear-gradient(110deg, #9fb6c9 0%, #ffffff 25%, #cfe2ef 45%, #a7c4d8 60%, #ffffff 80%, #9fb6c9 100%);

  --surface3: #2c2a30;
  --blue: #60A5FA;
}

/* The * reset lives in chrome.css. */

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}

/* Scrollbars live in chrome.css (default tokens already match). */

input, textarea, button {
  font-family: var(--font);
}

input::placeholder, textarea::placeholder {
  color: var(--dim);
}

@keyframes platShine { to { background-position: -200% center; } }

/* NAV, theme toggles and the hamburger live in chrome.css (shared with the
   dorm-reviews stylesheet). The nav inherits DM Sans from :root here. */

/* ---------------- HERO ---------------- */
.hero {
  width: 100%;
  padding: 56px 48px;
  border-bottom: 1px solid var(--line);
  position: relative;
  overflow: hidden;
  /* Image-forward hero: tall enough for the photograph to read, capped so
     it never pushes the hall list entirely below the fold. */
  min-height: clamp(360px, 46vh, 540px);
  display: flex;
  align-items: center;
}

/* Full-bleed campus photograph behind the hero copy. This is a real <img>
   rather than a background-image so the browser can pick a srcset candidate
   for the viewport and treat it as the LCP element. */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

/* Blurred flag fallback, carried over from the original hero. It lives on its
   own layer rather than on .hero-media so the blur cannot bleed onto the
   photograph or the scrim. Visible while the photo decodes, and whenever no
   photo has been added yet. scale() hides the soft edge blur() leaves behind. */
.hero-media::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: url("../img/flag-800.png") center / cover no-repeat;
  filter: blur(3px);
  transform: scale(1.05);
  opacity: 0.28;
}

:root[data-theme="dark"] .hero-media::before { opacity: 0.45; }

.hero-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Biased below centre: campus shots put sky in the top third. */
  object-position: center 58%;
  /* Positioned so it paints above the absolutely-placed flag layer. */
  position: relative;
  z-index: 1;
}

/* A missing photo falls back to the flag above rather than a broken-image
   glyph (data-error is set by the img onerror handler). */
.hero-media img[data-error] { display: none; }

/* Scrim. The hero copy is themed (var(--ink)), not hard-coded white, so the
   wash runs toward --bg on both themes and the text keeps its contrast
   whatever photograph gets dropped in. */
.hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    radial-gradient(ellipse 85% 75% at 50% 50%, var(--bg) 25%, rgba(0, 0, 0, 0) 75%),
    linear-gradient(to top, var(--bg) 0%, rgba(0, 0, 0, 0) 45%);
  opacity: 0.93;
}

:root[data-theme="dark"] .hero-media::after { opacity: 0.88; }

/* Every child except the photo layer sits above it. */
.hero > *:not(.hero-media) {
  position: relative;
  z-index: 1;
  width: 100%;
}

/* Hero copy is centred over the photograph. */
.hero-main { text-align: center; }

.hero h1 {
  font-weight: 800;
  font-size: clamp(2.4rem, 4.5vw, 3.8rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 auto 14px;
  max-width: 16em;
}

.hero h1 .dot { color: var(--red); }

.hero p {
  color: var(--dim);
  font-size: 1.02rem;
  margin: 0 auto 40px;
  max-width: 34em;
}

/* Slot-machine reel on the hero subhead: the last word whirls through a
   handful of candidates and decelerates onto "home". The visible reel is
   aria-hidden and paired with an .sr-only "home", so assistive tech reads the
   settled sentence rather than thirteen stray nouns. */
.reel {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Height and width are pinned in px by script.js once the webfont has
     loaded; these are the pre-script fallbacks. */
  height: 1.25em;
  line-height: 1.25em;
}

.reel__track { display: block; }

.reel__word {
  display: block;
  height: 1.25em;
  line-height: 1.25em;
  /* max-content keeps each word at its natural width so the script can
     measure it — as plain blocks they would all inherit the reel width and
     every measurement would come back identical. */
  width: max-content;
  white-space: nowrap;
}

/* Once script.js has measured the words it locks the reel to the widest cell
   and adds .reel--sized. The cells then fill that fixed box and centre their
   text, so the sentence around the reel never reflows mid-spin.

   That box is wider than "home.", though, so laying the line out around it
   would leave the whole sentence off-centre for the length of the spin. The
   surplus is bled back out through negative margins (--reel-bleed, set in px
   by script.js) so the line takes up exactly the width of the settled
   sentence while the reel still paints its full width, centred on the same
   axis as every word inside it. */
.reel--sized {
  margin-left: calc(-1 * var(--reel-bleed, 0px));
  margin-right: calc(-1 * var(--reel-bleed, 0px));
}

.reel--sized .reel__word {
  width: 100%;
  text-align: center;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Search field, styled after resider.ca's hero combobox: a solid rounded
   card floating over the photograph with a leading magnifier, a hairline
   ring at rest and an accent ring on focus. Replaces the old underline
   field, so the grow-on-focus transition is gone — the bar spans the full
   hero width. */
.hero-search-wrap {
  position: relative;
  width: 100%;
}

.hero-search-icon {
  position: absolute;
  z-index: 2;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--dim);
  pointer-events: none;
}

.hero-search {
  width: 100%;
  /* Left padding clears the magnifier. */
  padding: 14px 16px 14px 48px;
  border: none;
  border-radius: 12px;
  /* Slightly translucent so the photograph reads through the card. */
  background: var(--field); /* fallback where color-mix is unsupported */
  background: color-mix(in srgb, var(--field) 94%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--ink);
  font-size: 1rem;
  font-weight: 500;
  outline: none;
  box-shadow:
    0 0 0 1px var(--line),
    0 1px 3px rgba(0, 0, 0, 0.1),
    0 1px 2px -1px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.18s ease, background-color 0.18s ease;
}

.hero-search:focus {
  background: var(--field);
  box-shadow:
    0 0 0 2px var(--red),
    0 1px 3px rgba(0, 0, 0, 0.1),
    0 1px 2px -1px rgba(0, 0, 0, 0.1);
}

/* ---------------- SECTIONS ---------------- */
.section {
  display: none;
  width: 100%;
  padding: 0 48px;
}

.section.active { display: block; }

#section-map.section { padding: 0; }

/* ---------------- FILTER BAND ---------------- */
.filter-band {
  width: 100%;
}

/* Pill with a chevron that flips when the band opens. */
.filter-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Same outlined pill as the sort dropdown it sits opposite: hairline ring,
     no fill, ink label. */
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 8px 16px;
  cursor: pointer;
  color: var(--ink);
  font-size: 0.92rem;
  font-weight: 800;
  transition: background 0.15s;
}

@media (hover: hover) {
  .filter-toggle:hover { background: var(--hover); }
}

/* Open reads from the fill and the chevron flip; the ring stays neutral. */
.filter-toggle.open { background: var(--hover); }

.filter-row-left {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.filter-toggle svg { transition: transform 0.3s ease; }
.filter-toggle.open svg { transform: rotate(180deg); }

.filter-band-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s ease;
}

.filter-band.open .filter-band-body { grid-template-rows: 1fr; }

.filter-band-inner {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  opacity: 0;
  transform: translateY(-8px);
  /* Padding lives only in the open state: it can't shrink with the 0fr
     row, so it would hold the collapsed band ~34px open. */
  transition: opacity 0.25s ease, transform 0.35s ease, padding 0.35s ease;
}

.filter-band.open .filter-band-inner {
  opacity: 1;
  transform: none;
  padding: 14px 8px 20px;
}

.filter-line {
  display: flex;
  align-items: center;
  gap: 10px 22px;
  flex-wrap: wrap;
}

.filter-label {
  font-size: 0.92rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--dim);
  width: 110px;
  flex-shrink: 0;
}

.chip-group {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.chip {
  border: 1px solid var(--line);
  background: none;
  color: var(--dim);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.chip:hover { border-color: var(--ink); }

.chip.active {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}

.clear-all {
  border: none;
  background: none;
  color: var(--red);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  padding: 6px 4px;
  text-decoration: underline;
}

/* ---------------- COUNT + SORT ---------------- */
/* 8px side padding matches .dorm-row so the toggle lines up with the
   thumbnails and the sort options with the ratings column. */
.count-sort-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 24px 8px 8px;
}

/* Lives at the bottom of the filter band, so it collapses with it. */
.count-label {
  font-size: 0.88rem;
  color: var(--dim);
  font-weight: 600;
  margin-top: 4px;
}

.sort-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
}

.sort-opt {
  background: none;
  border: none;
  padding: 0 0 2px;
  font-size: 0.95rem;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
  color: var(--dim);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color 0.15s;
}

.sort-opt:hover { color: var(--ink); }

.sort-opt.active {
  color: var(--ink);
  border-bottom-color: var(--red);
}

/* Mobile-only replacement for the sort text buttons. */
.sort-select {
  display: none;
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--line);
  background: none;
  color: var(--ink);
  border-radius: 999px;
  padding: 6px 30px 6px 14px;
  font: inherit;
  font-size: 0.92rem;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* ---------------- DIRECTORY LIST ---------------- */
.dorm-list {
  display: flex;
  flex-direction: column;
  padding-bottom: 80px;
}

.dorm-row {
  display: grid;
  grid-template-columns: 104px 1fr auto auto;
  gap: 24px;
  align-items: center;
  padding: 20px 8px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  transition: background 0.15s;
}

.dorm-row:hover { background: var(--hover); }

/* Heart button for saving halls (browse rows + detail header). */
.save-btn {
  background: none;
  border: none;
  padding: 8px;
  display: flex;
  align-items: center;
  color: var(--dim);
  cursor: pointer;
  transition: color 0.15s, transform 0.15s;
}
.save-btn svg { fill: none; }
.save-btn:hover { color: var(--red); transform: scale(1.1); }
.save-btn.saved { color: var(--red); }
.save-btn.saved svg { fill: currentColor; }

.dorm-row .thumb {
  width: 104px;
  height: 72px;
  background-size: cover;
  background-position: center;
  background-color: var(--line);
}

.dorm-row .row-body { min-width: 0; }

.dorm-row .row-body h3 {
  font-weight: 700;
  font-size: 1.1rem;
  margin: 0 0 3px;
}

.dorm-row .row-meta {
  font-size: 0.85rem;
  color: var(--dim);
}

.dorm-row .row-rating {
  text-align: right;
  flex-shrink: 0;
}

.dorm-row .row-rating .num {
  font-size: 1.15rem;
  font-weight: 800;
}

.dorm-row .row-rating .cnt {
  font-size: 0.78rem;
  color: var(--dim);
}

.no-results {
  color: var(--dim);
  padding: 40px 8px;
  font-size: 0.95rem;
}

/* Phones: the 4-column row squeezes the hall name into a sliver. Reflow to
   two rows — name + meta on top, the rating line underneath — with the
   photo spanning both rows and the heart pinned to the right. */
@media (max-width: 640px) {
  .dorm-row {
    grid-template-columns: 88px 1fr auto;
    grid-template-rows: auto auto;
    gap: 2px 14px;
    padding: 16px 8px;
  }
  .dorm-row .thumb { grid-row: 1 / -1; width: 88px; height: 66px; }
  .dorm-row .row-body { grid-column: 2; grid-row: 1; }
  .dorm-row .row-body h3 { font-size: 1rem; }
  .dorm-row .row-rating {
    grid-column: 2;
    grid-row: 2;
    display: flex;
    align-items: baseline;
    gap: 8px;
    text-align: left;
  }
  .dorm-row .row-rating .num { font-size: 0.95rem; }
  .dorm-row .save-btn { grid-column: 3; grid-row: 1 / -1; }
}

/* Highlight colour for a hall's rating tier, used wherever hovering or
   selecting a dorm outlines it — the map markers and the sidebar cards. Set
   on the element itself so both read from one place. Diamond gets a flat
   silver here for the halo; the ring itself is drawn as a gradient below.
   Unrated halls highlight in --ink: black on the light theme, per the same
   rule the buildings follow, and legible instead of invisible on the dark. */
.tier-red { --tier-hi: var(--red); }
.tier-orange { --tier-hi: var(--orange); }
.tier-gold { --tier-hi: var(--gold); }
.tier-plat { --tier-hi: #9fb6c9; }
.tier-none { --tier-hi: var(--ink); }

:root[data-theme="dark"] .tier-plat { --tier-hi: #cfe2ef; }

/* Rating tier colours (shared everywhere). */
.rating-red { color: var(--red); }
.rating-orange { color: var(--orange); }
.rating-gold { color: var(--gold); }
.rating-dim { color: var(--dim); }
.rating-plat {
  color: transparent;
  background-image: var(--plat-grad);
  background-size: 200% auto;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: platShine 2.5s linear infinite;
}

/* Loading spinner lives in chrome.css. */

/* ---------------- DISCLAIMER ---------------- */
.site-disclaimer {
  text-align: center;
  font-size: 0.75rem;
  color: var(--dim);
  margin: 0;
  padding: 20px 24px 28px;
}

/* ============================================================= *
 * MAP VIEW (real MapLibre map — production replacement for the  *
 * prototype's placeholder). Re-themed onto v2 tokens.           *
 * ============================================================= */
/* The map view fills the viewport; without this, swipes over marker pills
   or the bottom sheet rubber-band the page behind the map on mobile. */
body.map-view-active { overflow: hidden; }

.map-split {
  display: flex;
  height: calc(100dvh - 62px);
  border-top: 1px solid var(--line);
  position: relative;
}

.map-sidebar {
  width: 420px;
  flex: none;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border-right: 1px solid var(--line);
  overflow: hidden;
  transition: width 0.28s ease, opacity 0.2s ease;
}

.map-split.sidebar-collapsed .map-sidebar {
  width: 0;
  opacity: 0;
  border-right: none;
}

.map-sidebar-head {
  padding: 20px 22px 14px;
  border-bottom: 1px solid var(--line);
  flex: none;
}

.map-sidebar-head h2 {
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-size: 1.7rem;
  margin: 0 0 14px;
  white-space: nowrap;
}

.map-sidebar-head h2 .dot { color: var(--red); }

.map-search {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 13px;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--dim);
}

.map-search input {
  border: none;
  background: transparent;
  outline: none;
  color: var(--ink);
  font: inherit;
  font-size: 0.92rem;
  width: 100%;
}


.map-sidebar-list {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  scrollbar-width: thin;
  scrollbar-color: var(--surface3) transparent;
  /* Keep touch scrolling inside the list: without this, hitting the top or
     bottom chains the scroll to the page and drags the map view with it. */
  overscroll-behavior: contain;
  touch-action: pan-y;
}
.map-sidebar-list::-webkit-scrollbar { width: 6px; }
.map-sidebar-list::-webkit-scrollbar-track { background: transparent; }
.map-sidebar-list::-webkit-scrollbar-thumb { background: var(--surface3); border-radius: 999px; }
.map-sidebar-list::-webkit-scrollbar-thumb:hover { background: var(--dim); }

/* Sidebar dorm cards (map only). */
.map-card {
  flex: none;
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 14px;
  align-items: center;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  cursor: pointer;
  scroll-margin-top: 14px;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.map-card .map-card-img {
  width: 88px;
  height: 62px;
  border-radius: 6px;
  background-size: cover;
  background-position: center;
  background-color: var(--line);
}

.map-card h3 { font-size: 0.98rem; font-weight: 700; margin: 0 0 3px; }
.map-card .map-card-meta { font-size: 0.76rem; color: var(--dim); }
.map-card .map-card-rating { font-size: 0.8rem; font-weight: 800; margin-top: 4px; }

.map-card.marker-active,
.map-card.marker-selected {
  border-color: var(--tier-hi, var(--logo-gold));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--tier-hi, var(--logo-gold)) 55%, transparent);
  transform: translateY(-2px);
}

/* Diamond outlines in the real gradient: the border box carries --plat-grad
   while a solid layer masks the padding box back to the card's own colour. */
.map-card.tier-plat.marker-active,
.map-card.tier-plat.marker-selected {
  border-color: transparent;
  background:
    linear-gradient(var(--field), var(--field)) padding-box,
    var(--plat-grad) border-box;
  background-size: auto, 200% auto;
  animation: platShine 2.5s linear infinite;
}

/* Open button: only visible on the selected card. */
.map-card .map-card-open {
  display: none;
  border: none;
  border-radius: 8px;
  background: var(--red);
  color: #fff;
  padding: 7px 14px;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s;
}
.map-card .map-card-open:hover { background: var(--red-dark); }
.map-card.marker-selected { grid-template-columns: 88px 1fr auto; }
.map-card.marker-selected .map-card-open { display: inline-block; }

.map-sheet-handle { display: none; }

.map-sidebar-toggle {
  position: absolute;
  top: 18px;
  left: 380px;
  transform: translateX(-50%);
  z-index: 5;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--field);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
  transition: left 0.28s ease;
}
.map-split.sidebar-collapsed .map-sidebar-toggle { left: 8px; transform: none; }
.map-split.sidebar-collapsed .map-sidebar-toggle svg { transform: rotate(180deg); }

.map-container {
  background: var(--field);
  overflow: hidden;
  position: relative;
  flex: 1;
  min-width: 0;
  /* Own stacking context: markers (z-index up to 10) must never paint over
     the sidebar/bottom sheet (z-index 6), which lives outside this box. */
  z-index: 1;
}

#mapFrame { width: 100%; height: 100%; }

/* Markers. touch-action keeps a swipe that starts on a marker from turning
   into a page scroll on mobile. The element is a zero-size point that the
   capsule hangs off (see .map-marker-plate); the capsule still takes clicks
   and hovers, since hit-testing follows the painted child, not the parent. */
.map-marker {
  cursor: pointer;
  touch-action: none;
  width: 0;
  height: 0;
}
.map-labels-hidden .map-marker { display: none; }
/* Map key (bottom-left control): building colors + route line legend, with
   the labels toggle inside. Swatch colors mirror DORM_AREA_COLORS in
   script.js — keep the two in sync. */
/* Map key and markers set the font explicitly because MapLibre puts
   Helvetica on the map container — see --font-map in :root. */
.map-key {
  background: var(--field);
  border-radius: 14px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
  padding: 9px 12px 10px;
  font-family: var(--font-map);
  color: var(--ink);
}
.map-key-row {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.74rem;
  font-weight: 600;
  line-height: 1.9;
  white-space: nowrap;
}
.map-key-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.map-key-swatch.north { background: #e21833; }
.map-key-swatch.south { background: #e8a000; }
.map-key-swatch.commons { background: #2f6fd0; }
.map-key-line { width: 12px; border-top: 3px solid #e21833; border-radius: 2px; flex: none; }
.map-key .map-labels-toggle {
  margin-top: 8px;
  width: 100%;
  height: 28px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: none;
  color: var(--ink);
  font-family: var(--font-map);
  font-size: 0.72rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s;
}
.map-key .map-labels-toggle:hover { background: rgba(0, 0, 0, 0.05); }

/* One capsule per location: the coloured disc and the name share a single
   plate, so the icon reads as part of the label rather than a chip parked
   next to it.

   The marker element itself is a zero-size point and the capsule hangs off it
   absolutely. MapLibre anchors on the element box, so a capsule in the flow
   would centre the whole pill on the coordinate and push the disc off the
   roof point liftMarkersToRoofs() picked; at 0x0 the anchor is the point and
   the offsets below put the disc exactly on it. */
.map-marker-plate {
  position: absolute;
  bottom: 0;
  left: -15.5px; /* 1.5px border + 3px padding + half the 22px disc, so the disc centres on the point */
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 3px 11px 3px 3px;
  /* A real border rather than a ring shadow, so the diamond tier can paint
     the gradient into it (see below). */
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: var(--field);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  font-family: var(--font-map); /* MapLibre sets Helvetica on the map container */
  line-height: 1.25;
  white-space: nowrap;
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}

/* Filled disc with a knocked-out glyph. The fill is set per marker from
   DORM_AREA_COLORS so it matches the building underneath and the swatches
   in the key. */
.map-marker-icon {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--marker-color, var(--dim));
  color: #fff;
}

.map-marker-glyph { width: 13px; height: 13px; display: block; }

.map-marker-name {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink);
}

.map-marker-sub {
  display: block;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--dim);
}

.map-marker-star { color: var(--logo-gold); }

.map-marker.marker-active, .map-marker.marker-selected { z-index: 10; }

/* Grow from the disc, not the capsule's centre, so the marker stays pinned
   to its building while it lifts. */
.map-marker.marker-active .map-marker-plate,
.map-marker.marker-selected .map-marker-plate {
  transform: scale(1.06);
  transform-origin: 15.5px center;
  border-color: var(--tier-hi, var(--logo-gold));
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
}

.map-marker.tier-plat.marker-active .map-marker-plate,
.map-marker.tier-plat.marker-selected .map-marker-plate {
  border-color: transparent;
  background:
    linear-gradient(var(--field), var(--field)) padding-box,
    var(--plat-grad) border-box;
  background-size: auto, 200% auto;
  animation: platShine 2.5s linear infinite;
}

.map-marker.marker-active .map-marker-name,
.map-marker.marker-selected .map-marker-name { font-weight: 700; }

/* Walk-times panel (map overlay, top-left). Shows precomputed walking
   ETAs from the selected dorm; clicking a row draws the route. */
.map-directions {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 12; /* above active/selected marker pills (z-index 10) */
  width: 272px;
  max-width: calc(100% - 90px); /* keep clear of the zoom controls */
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.14);
  overflow: hidden;
  font-family: var(--font);
}

.map-directions-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px 10px;
  border-bottom: 1px solid var(--line);
}

.map-directions-kicker {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}

.map-directions-title {
  font-size: 0.95rem;
  font-weight: 800;
  color: var(--ink);
  margin-top: 1px;
}

.map-directions-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

.map-directions-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--dim);
  font-size: 1.35rem;
  line-height: 1;
  padding: 0 2px;
  transition: color 0.15s;
}

.map-directions-close:hover { color: var(--ink); }

/* Collapsed: just the header, so the panel doesn't bury the map on phones.
   The whole header toggles; the chevron flips to show the direction. */
.map-directions-head { cursor: pointer; }
.map-directions.collapsed .map-directions-list { display: none; }
.map-directions.collapsed .map-directions-head { border-bottom: none; }
.map-directions-collapse {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--dim);
  padding: 2px;
  line-height: 0;
  transition: color 0.15s;
}
.map-directions-collapse:hover { color: var(--ink); }
.map-directions-collapse svg { transition: transform 0.2s; }
.map-directions:not(.collapsed) .map-directions-collapse svg { transform: rotate(180deg); }

.map-directions-list { padding: 6px; display: flex; flex-direction: column; }

.map-directions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
  padding: 8px;
  border-radius: 8px;
  transition: background 0.15s;
}

.map-directions-name { display: flex; flex-direction: column; gap: 1px; }

.map-directions-kind {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--dim);
}

.map-directions-row:hover { background: var(--hover); }
.map-directions-row.active { background: rgba(226, 24, 51, 0.08); }
.map-directions-row:disabled { color: var(--dim); cursor: default; }
.map-directions-row:disabled:hover { background: none; }

.map-directions-eta {
  font-weight: 800;
  color: var(--red);
  white-space: nowrap;
}

.map-directions-row:disabled .map-directions-eta { color: var(--dim); }

.map-directions-empty {
  padding: 10px 12px;
  font-size: 0.8rem;
  color: var(--dim);
}

/* Destination marker: red accents instead of the dorm gold, and it stays
   visible when the dorm labels are toggled off. */
.route-dest-marker .map-marker-icon { background: var(--red); }
.route-dest-marker .map-marker-sub { color: var(--red); }
.map-labels-hidden .route-dest-marker { display: block; }

:root[data-theme="dark"] .map-directions-row.active { background: rgba(255, 84, 104, 0.14); }
:root[data-theme="dark"] .map-directions-eta,
:root[data-theme="dark"] .route-dest-marker .map-marker-sub { color: #ff5468; }
:root[data-theme="dark"] .route-dest-marker .map-marker-icon { background: #ff5468; }

/* Dark basemap: the capsule needs a ring that lifts off the dark ground
   rather than sinking into it. */
:root[data-theme="dark"] .map-marker-plate {
  background: #14161c;
  border-color: rgba(255, 255, 255, 0.16);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
:root[data-theme="dark"] .map-marker.tier-plat.marker-active .map-marker-plate,
:root[data-theme="dark"] .map-marker.tier-plat.marker-selected .map-marker-plate {
  background:
    linear-gradient(#14161c, #14161c) padding-box,
    var(--plat-grad) border-box;
  background-size: auto, 200% auto;
}

/* Dark-map recolouring of the sidebar + MapLibre chrome. */
:root[data-theme="dark"] .map-key {
  background: #14161c;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
}
:root[data-theme="dark"] .map-key-swatch.north { background: #ff5468; }
:root[data-theme="dark"] .map-key-swatch.south { background: #ffc233; }
:root[data-theme="dark"] .map-key-swatch.commons { background: #5b96f0; }
:root[data-theme="dark"] .map-key-line { border-top-color: #ff5468; }
:root[data-theme="dark"] .map-key .map-labels-toggle:hover { background: rgba(255, 255, 255, 0.08); }
:root[data-theme="dark"] .map-sidebar { background: #0f1116; }
:root[data-theme="dark"] .maplibregl-ctrl-group {
  background: #14161c;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
}
:root[data-theme="dark"] .maplibregl-ctrl-group button + button { border-top-color: rgba(255, 255, 255, 0.14); }
:root[data-theme="dark"] .maplibregl-ctrl-group button:hover { background: rgba(255, 255, 255, 0.08); }
:root[data-theme="dark"] .maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: invert(1) brightness(1.05); }
:root[data-theme="dark"] .maplibregl-ctrl-attrib.maplibregl-compact { background-color: #14161c; }
:root[data-theme="dark"] .maplibregl-ctrl-attrib.maplibregl-compact,
:root[data-theme="dark"] .maplibregl-ctrl-attrib.maplibregl-compact a { color: #c2c7d1; }
:root[data-theme="dark"] .maplibregl-ctrl-attrib-button { background-color: transparent; filter: invert(1); }

.maplibregl-map { background: #f8f4f0; font: inherit; }
:root[data-theme="dark"] .maplibregl-map { background: #0f1116; }

.maplibregl-popup { max-width: 240px !important; }
.maplibregl-popup .maplibregl-popup-content {
  background: var(--field);
  color: var(--ink);
  padding: 12px 15px;
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
  font-size: 0.88rem;
  line-height: 1.5;
}
.maplibregl-popup .maplibregl-popup-content strong { display: block; font-size: 1rem; margin-bottom: 3px; }
.maplibregl-popup .maplibregl-popup-close-button {
  top: 4px; right: 4px; width: 24px; height: 24px;
  border-radius: 6px; color: var(--dim); font-size: 17px; line-height: 1;
}
.maplibregl-popup .maplibregl-popup-close-button:hover { background: var(--hover); color: var(--ink); }
.maplibregl-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: var(--field); }
.maplibregl-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: var(--field); }
.maplibregl-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--field); }
.maplibregl-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--field); }

.maplibregl-ctrl-group {
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
}
.maplibregl-ctrl-group button { width: 32px; height: 32px; }
.maplibregl-ctrl-group button + button { border-top: 1px solid var(--line); }
.maplibregl-ctrl-group button:hover { background: var(--hover); }

@media (max-width: 768px) {
  .hero { padding: 40px 20px; min-height: clamp(300px, 42vh, 420px); }
  .section { padding: 0 20px; }

  .filter-label { width: 100%; }

  /* Sort collapses into a dropdown that stays on the same line as the
     "Filter by..." toggle instead of wrapping below it. */
  .count-sort-row { flex-wrap: nowrap; }
  .filter-row-left { min-width: 0; }
  .count-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .sort-opt { display: none; }
  .sort-select { display: block; flex-shrink: 0; }

  /* Mobile nav rules live in chrome.css. */

  .map-split { height: calc(100dvh - 60px); display: block; position: relative; }
  .map-container { position: absolute; inset: 0; }
  .map-sidebar {
    position: absolute; left: 0; right: 0; bottom: 0;
    width: 100%; height: 82dvh; opacity: 1;
    border-right: none; border: 1px solid var(--line);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.25);
    transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
    will-change: transform;
    z-index: 6;
  }
  .map-sidebar:not(.sheet-open) { transform: translateY(calc(82dvh - 168px)); }
  .map-sidebar.sheet-dragging { transition: none; }
  .map-sheet-handle {
    display: flex; justify-content: center; align-items: center;
    padding: 10px 0 6px; cursor: grab; touch-action: none;
  }
  /* The whole card is a drag surface: the head always, the list only while
     the sheet is closed (open sheet keeps pan-y so the list can scroll). */
  .map-sidebar-head { touch-action: none; }
  .map-sidebar:not(.sheet-open) .map-sidebar-list { touch-action: none; }
  /* Keep the map key visible above the peeking bottom sheet (168px). */
  .map-container .maplibregl-ctrl-bottom-left { bottom: 172px; }
  .map-sheet-handle span { width: 40px; height: 5px; border-radius: 999px; background: var(--surface3); }
  .map-sidebar-head { padding-top: 4px; }
  .map-sidebar-toggle { display: none; }
  .map-split.sidebar-collapsed .map-sidebar { width: 100%; }
}
