@import url('/assets/css/bomforge-brand.css?v=20261011t');

/* ============================================================
   BOMAtlas Atlas Shell — the education-atlas app chrome.
   Light, clean, map-first. Used by / and the education shells
   (/trade-schools/, /universities/, /community-colleges/,
   /private-colleges/, /education/).

   THE PALETTE IS NOT DEFINED HERE. bomforge-brand.css?v=20261011t, imported above,
   declares --ink, --ink-2, --ink-3, --line, --line-strong, --paper,
   --paper-2, --accent, --accent-2, --seg-*, --font-sans, --glass and
   --paper-rgb on `:root:root` — a doubled selector that outranks any
   `:root` block in this file. This file used to carry its own full copy
   of all sixteen, and every one of them was inert. Reading them and
   picking a hardcoded neighbour that "matches" is how a hover fill of
   #f4f0ea got shipped against a near-white --ink: 1.03:1 in dark mode.
   Change a colour in bomforge-brand.css?v=20261011t Only add a token here if brand
   does not define it, and say why, as the two below do.

   Type identity is never colour-alone: list rows, the legend, and the
   selection card all carry the type label as text.
   ============================================================ */

:root {
  /* Elevation. Every shadow here used to be rgba(15, 23, 42, …), a navy
     LIGHTER than the dark paper (#0c0a06), so on dark the floating panel,
     the nav dropdown and the map tooltip cast a glow instead of a shadow
     and read as flat against the basemap. The hue flips and the alpha is
     scaled, so each surface keeps its relative weight in both themes. */
  --shadow-rgb: 15, 23, 42;
  --shadow-boost: 1;
  /* Not a colour: bomforge-brand.css?v=20261011t sets no color-scheme, so without this
     the native scrollbars and form controls stay light on the dark theme. */
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --shadow-rgb: 0, 0, 0;
    --shadow-boost: 3;
    color-scheme: dark;
  }
}
/* Manual toggle overrides — [data-theme] beats the OS media query by specificity. */
:root[data-theme='dark'] {
  --shadow-rgb: 0, 0, 0;
  --shadow-boost: 3;
  color-scheme: dark;
}
:root[data-theme='light'] {
  --shadow-rgb: 15, 23, 42;
  --shadow-boost: 1;
  color-scheme: light;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html {
  background: var(--paper);
}
body {
  font-family: var(--font-sans);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* ── Header ─────────────────────────────────────────────────── */
.hp-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3000;
  background: rgba(var(--glass), 0.9);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border-bottom: 1px solid var(--line);
  height: 56px;
}
.hp-header-inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 var(--space-4);
  display: flex;
  align-items: center;
  height: 56px;
}
.hp-brand {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  margin-right: auto;
}
.hp-logo {
  color: var(--ink);
  text-decoration: none;
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -0.03em;
  /* Tap area without moving the layout: the wordmark is 29px tall. */
  display: inline-flex;
  align-items: center;
  padding: 11px 6px;
  margin: -11px -6px;
}
.hp-logo span {
  color: var(--accent);
}
.hp-switcher-btn {
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;
  /* Was 26x34. The chevron is unchanged; only the hit box grows. */
  width: 44px;
  height: 44px;
  border-radius: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-2);
  transition:
    background 0.15s,
    color 0.15s;
}
.hp-switcher-btn[aria-expanded='true'] {
  background: var(--paper-2);
  color: var(--ink);
}
@media (hover: hover) {
  .hp-switcher-btn:hover {
    background: var(--paper-2);
    color: var(--ink);
  }
}
.hp-switcher-btn svg {
  width: 14px;
  height: 14px;
  stroke: currentColor;
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.hp-switcher-panel {
  position: absolute;
  top: 44px;
  left: 0;
  min-width: 300px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: 0 18px 44px rgba(var(--shadow-rgb), calc(0.16 * var(--shadow-boost)));
  padding: 6px;
  z-index: 4000;
}
.hp-switch-item {
  display: block;
  padding: 10px var(--space-3);
  border-radius: 2px;
  text-decoration: none;
  transition: background 0.15s;
}
@media (hover: hover) {
  .hp-switch-item:hover {
    background: var(--paper-2);
  }
}
.hp-switch-item.is-current {
  background: var(--paper-2);
}
.hp-switch-name {
  display: block;
  color: var(--ink);
  font-weight: 800;
  font-size: 14.5px;
  letter-spacing: -0.02em;
}
.hp-switch-name b {
  color: var(--accent);
  font-weight: 800;
}
.hp-switch-item.is-current .hp-switch-name::after {
  content: 'Current';
  margin-left: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-2);
  vertical-align: 1px;
}
.hp-switch-desc {
  display: block;
  color: var(--ink-2);
  font-size: 12.5px;
  margin-top: 1px;
}
.hp-nav {
  display: flex;
  align-items: center;
  gap: 22px;
}
.hp-nav a {
  color: var(--ink-2);
  text-decoration: none;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  transition: color 0.15s;
  display: inline-flex;
  align-items: center;
  /* 44px hit area, same layout box as before. */
  padding: 14px var(--space-1);
  margin: -14px -4px;
}
.hp-nav a[aria-current='page'] {
  color: var(--ink);
}
@media (hover: hover) {
  .hp-nav a:hover {
    color: var(--ink);
  }
}
.hp-nav-group {
  position: relative;
}
.hp-nav-trigger {
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: 14px 6px;
  margin: -14px -6px;
  color: var(--ink-2);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  transition: color 0.15s;
}
.hp-nav-trigger svg {
  width: 12px;
  height: 12px;
  stroke: currentColor;
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  transition: transform 0.15s;
}
.hp-nav-group.open .hp-nav-trigger,
.hp-nav-trigger:focus-visible {
  color: var(--ink);
}
.hp-nav-group.open .hp-nav-trigger svg {
  transform: rotate(180deg);
}
@media (hover: hover) {
  .hp-nav-group:hover .hp-nav-trigger {
    color: var(--ink);
  }
  .hp-nav-group:hover .hp-nav-trigger svg {
    transform: rotate(180deg);
  }
}
.hp-nav-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  padding-top: 14px;
  z-index: 4000;
}
.hp-nav-panel::before {
  content: '';
  position: absolute;
  inset: 14px 0 0 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: 0 18px 44px rgba(var(--shadow-rgb), calc(0.16 * var(--shadow-boost)));
}
.hp-nav-group.open .hp-nav-panel,
.hp-nav-group:focus-within .hp-nav-panel {
  display: block;
}
@media (hover: hover) {
  .hp-nav-group:hover .hp-nav-panel {
    display: block;
  }
}
.hp-nav-panel a {
  position: relative;
  display: block;
  min-width: 250px;
  padding: 9px 14px;
  margin: 0;
  border-radius: 2px;
  transition: background 0.15s;
}
.hp-nav-panel a:first-child {
  margin-top: 6px;
}
.hp-nav-panel a:last-child {
  margin-bottom: 6px;
}
@media (hover: hover) {
  .hp-nav-panel a:hover {
    background: var(--paper-2);
  }
}
.hp-nav-panel a b {
  display: block;
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.hp-nav-panel a span {
  display: block;
  color: var(--ink-2);
  font-size: 12px;
  margin-top: 1px;
}
.hp-nav-panel a {
  margin-left: 6px;
  margin-right: 6px;
}
/* Panels lead with one destination, then shortcuts.
   These were flat lists in which "All states" carried the same visual rank as
   "Pennsylvania", so six unexplained states read as the coverage rather than
   as a sample of it. The primary row now answers "where does this menu go",
   and the grid is labelled a shortcut so nobody reads six states as the set.
   Measured at 1440px the States panel is 273px and Countries 312px, with the
   two columns paying for the primary row rather than adding to it. */
.hp-nav-panel .hp-nav-primary {
  border-bottom: 1px solid var(--line);
  border-radius: 2px;
  padding-bottom: 11px;
  margin-bottom: 2px;
}
.hp-nav-eyebrow {
  margin: var(--space-2) 14px var(--space-1);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-3);
}
.hp-nav-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 2px;
}
.hp-nav-grid a {
  min-width: 0;
}

/* Auth is not site navigation. Sign in sat in the same row and rhythm as
   Government and States, so the eye read seven peers instead of five
   destinations and two account actions. */
.hp-nav-divider {
  width: 1px;
  height: 20px;
  background: var(--line-strong);
  margin: 0 2px;
  flex-shrink: 0;
}

/* Keyboard users had no visible focus anywhere in the header: the only
   affordance was hover, which a keyboard cannot produce. */
.hp-nav a:focus-visible,
.hp-nav-trigger:focus-visible,
.hp-nav-panel a:focus-visible,
.hp-logo:focus-visible,
.hp-switcher-btn:focus-visible,
.hp-switch-item:focus-visible,
.hp-menu-btn:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 2px;
}

.hp-nav-cta,
.hp-nav a.hp-nav-cta {
  background: var(--accent);
  color: var(--paper) !important;
  /* `.hp-nav a` used to win this padding and squeeze the label to 4px. */
  padding: 0 14px;
  margin: -4px 0;
  min-height: 44px;
  border-radius: 2px;
  font-weight: 600;
}
@media (hover: hover) {
  .hp-nav-cta:hover {
    background: var(--accent-2);
  }
}
.hp-menu-btn {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  /* 22px glyph + 11px padding = 44. Negative margin keeps the header rhythm
     unchanged while the target grows. */
  padding: 11px;
  margin: -11px -11px -11px 0;
}
.hp-menu-btn svg {
  width: 22px;
  height: 22px;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linecap: round;
  fill: none;
}
@media (max-width: 768px) {
  .hp-menu-btn {
    display: block;
  }
  .hp-nav {
    display: none;
    position: absolute;
    top: 56px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 16px 40px rgba(var(--shadow-rgb), calc(0.12 * var(--shadow-boost)));
  }
  .hp-nav.active {
    display: flex;
    max-height: calc(100vh - 56px);
    overflow-y: auto;
  }
  .hp-nav a {
    padding: 13px 20px;
    font-size: 15px;
    border-top: 1px solid var(--line);
  }
  .hp-nav-cta {
    border-radius: 2px;
    text-align: center;
  }
  /* Dropdown groups flatten into always-open sections on mobile. */
  .hp-nav-group {
    border-top: 1px solid var(--line);
  }
  .hp-nav-trigger {
    width: 100%;
    padding: 13px 20px 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--ink-2);
    pointer-events: none;
  }
  .hp-nav-trigger svg {
    display: none;
  }
  .hp-nav-panel {
    display: block;
    position: static;
    transform: none;
    padding-top: 0;
  }
  .hp-nav-panel::before {
    display: none;
  }
  .hp-nav-panel a {
    min-width: 0;
    margin: 0;
    border-radius: 2px;
    border-top: 0;
    padding: 9px 20px;
  }
  .hp-nav-panel a b {
    font-size: 15px;
    font-weight: 500;
  }
  .hp-nav-panel a span {
    display: none;
  }
  .hp-switcher-panel {
    position: fixed;
    top: 52px;
    left: 12px;
    right: 12px;
    min-width: 0;
  }
}

/* A full-viewport drawer flight and an 800ms camera pan are exactly the
   vestibular triggers reduced-motion exists for. */
@media (prefers-reduced-motion: reduce) {
  .hp-nav,
  .hp-scrim,
  .hp-nav-panel,
  .panel {
    transition-duration: 0.01ms !important;
  }
}

/* ── App shell: the map IS the page ─────────────────────────── */
:root {
  --header-h: 56px;
}
html.atlas-app,
body.ts-app {
  height: 100%;
}
body.ts-app {
  overflow: hidden;
}
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 10px var(--space-4);
  border-radius: 2px;
  z-index: 4000;
}
.skip-link:focus {
  left: 0;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.app-main {
  position: fixed;
  top: var(--header-h);
  left: 0;
  right: 0;
  /* NOT bottom:0. A fixed box resolves `bottom` against iOS Safari's LARGE
     viewport (toolbars retracted), so with the toolbar showing the last ~90px
     of this box sits under the chrome, and body{overflow:hidden} means it can
     never be scrolled to. That is what guillotined the panel foot and the
     "See all N in this view" row on every iPhone. dvh tracks the toolbar; the
     vh line above it is the fallback for Safari before 15.4. */
  bottom: auto;
  height: calc(100vh - var(--header-h));
  /* svh, not dvh. Both exclude the toolbar, but dvh RECOMPUTES as the toolbar
     collapses, and every recompute relayouts the shell and resizes a live
     WebGL canvas. This shell never scrolls, so the toolbar never retracts and
     the small viewport is the only viewport there is: svh gives the same
     answer without the continuous relayout. */
  height: calc(100svh - var(--header-h));
}
#map {
  position: absolute;
  inset: 0;
  background: var(--paper-2);
}
.map-dead {
  display: grid;
  place-content: center;
  height: 100%;
  color: var(--ink-2);
  font-size: var(--text-sm);
  text-align: center;
  padding: var(--space-6);
}
.map-attrib {
  position: absolute;
  bottom: 8px;
  left: 10px;
  z-index: 5;
  width: 18px;
  height: 18px;
  overflow: hidden;
  white-space: nowrap;
  font-size: 0;
  line-height: 18px;
  color: var(--ink-3);
  background: rgba(var(--glass), 0.75);
  border-radius: 2px;
  cursor: default;
}
/* License text (OSM ODbL / CARTO) collapses to an i-dot; hover or focus expands it. */
.map-attrib::before {
  content: 'i';
  display: inline-block;
  width: 18px;
  text-align: center;
  font-size: var(--text-xs);
  font-style: italic;
  font-family: Georgia, serif;
}
.map-attrib:focus-within {
  width: auto;
  font-size: var(--text-xs);
  padding: 0 var(--space-2);
}
.map-attrib:focus-within::before {
  content: '';
  width: 0;
}
@media (hover: hover) {
  .map-attrib:hover {
    width: auto;
    font-size: var(--text-xs);
    padding: 0 var(--space-2);
  }
  .map-attrib:hover::before {
    content: '';
    width: 0;
  }
}

/* ── Floating directory panel ───────────────────────────────── */
.panel {
  position: absolute;
  top: 14px;
  right: 14px;
  bottom: 14px;
  /* Responsive: 400px on wide screens, shrinks on narrow desktops so the
     map keeps the majority of the viewport. */
  width: clamp(320px, 33vw, 400px);
  display: flex;
  flex-direction: column;
  background: rgba(var(--glass), 0.96);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow:
    0 2px 8px rgba(var(--shadow-rgb), calc(0.08 * var(--shadow-boost))),
    0 24px 64px rgba(var(--shadow-rgb), calc(0.16 * var(--shadow-boost)));
  overflow: hidden;
  z-index: 10;
}
.panel-head {
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--line);
}
.panel-head h1 {
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.12;
  color: var(--ink);
  margin: 0 0 10px;
}
.panel-head h1 em {
  font-style: normal;
  color: var(--accent);
}
.panel-sub {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
  margin: 0 0 10px;
}

/* Segment tabs: one dot per institution type */
/* These wrap. They were a single-line scroller to save panel head, but the
   type filter is the primary control on this page and the scroller hid it:
   the first pill was clipped at the left edge, the last faded out under the
   mask, and nothing on screen said the row moved. A filter you cannot see is
   worse than a taller panel, and the Focus chips below already wrap, so the
   scroller was also the odd one out. Pills keep white-space: nowrap so a
   label like "Community colleges" never breaks mid-phrase. */
.ts-segments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--space-3);
}
.ts-segments button {
  white-space: nowrap;
}
.ts-segments button {
  flex-shrink: 0;
}
.ts-segments button .lg-n {
  margin-left: 5px;
  font-size: var(--text-xs);
  font-weight: 600;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}
.ts-segments button {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px var(--space-3);
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s,
    border-color 0.15s,
    transform 0.08s;
}
@media (hover: hover) {
  .ts-segments button:hover {
    border-color: var(--ink-3);
    color: var(--ink);
  }
}
.ts-segments button:active {
  transform: scale(0.96);
}
.ts-segments button.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.seg-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.panel-stats {
  font-size: 11.5px;
  letter-spacing: 0;
  color: var(--ink-2);
  margin: 0 0 10px;
  font-variant-numeric: tabular-nums;
}
.panel-stats strong {
  color: var(--ink);
  font-weight: 700;
}

.search-row {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.search-row input[type='text'] {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 9px var(--space-3);
  background: var(--paper-2);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  color: var(--ink);
  font-size: 16px;
  font-family: inherit;
  transition:
    border-color 0.15s,
    background 0.15s;
}
.search-row input[type='text']:focus {
  border-color: var(--accent);
  background: var(--paper);
}
.search-row input[type='text']::placeholder {
  color: var(--ink-3);
}
.search-row select {
  /* 0 1, not 0 0: at large accessibility text sizes (or any UA that widens
     form controls) a rigid 132px basis pushed the select's tail past the
     panel edge. It may shrink to 96px; the search input still leads. */
  flex: 0 1 132px;
  min-width: 96px;
  /* A flex item's automatic minimum size is its MIN-CONTENT size, and for a
     <select> that is its widest option. The state list is worldwide now, so
     the widest option is "Provence-Alpes-Cote d'Azur (1)" and this select
     floored at 273px of a 362px row, squeezing the search input, the primary
     action of the whole surface, to 66px: the placeholder rendered "Sear". */
  min-width: 0;
  text-overflow: ellipsis;
  appearance: none;
  -webkit-appearance: none;
  padding: 9px 28px 9px var(--space-3);
  background: var(--paper-2)
    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='%230f172a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E")
    no-repeat right 10px center / 12px;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  color: var(--ink);
  font-size: 16px;
  font-family: inherit;
  cursor: pointer;
}

.chips-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-3);
  margin: 0 0 var(--space-1);
}
.ts-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.ts-chips button {
  padding: 5px 11px;
  min-height: 34px;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--ink-2);
  /* Matches .ts-legend button — Focus and Type sit one above the other and
     read as one control surface, so they cannot be a half-pixel apart. */
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s,
    border-color 0.15s,
    transform 0.08s;
}
@media (hover: hover) {
  .ts-chips button:hover {
    border-color: var(--accent);
    color: var(--accent-2);
  }
}
.ts-chips button:active {
  transform: scale(0.96);
}
.ts-chips button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}

/* Type legend (Everything view) */
.ts-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 10px;
}
.ts-legend button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  min-height: 34px;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-2);
  /* 12px, not 11px. These chips carry the type key AND the cohort size —
     the densest information in the panel — and 11px is under the floor
     where a five-word label stays scannable at arm's length. */
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition:
    border-color 0.15s,
    color 0.15s;
}
@media (hover: hover) {
  .ts-legend button:hover {
    border-color: var(--ink-3);
    color: var(--ink);
  }
}
.lg-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
}
.lg-n {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* Focus chips carry counts too now, and an active chip is a dark pill:
   --ink-3 on --accent is 1.4:1, so the count would vanish exactly when the
   filter it describes is the one in force. */
.ts-chips button .lg-n {
  margin-left: 5px;
  font-weight: 600;
}
.ts-chips button.active .lg-n {
  color: var(--paper);
  opacity: 0.62;
}
.sg-type .lg-dot {
  width: 7px;
  height: 7px;
  margin-right: 3px;
  vertical-align: 0;
  display: inline-block;
}

/* Selected school card */
.selected-school-panel {
  display: none;
  padding: var(--space-3) var(--space-4);
  background: var(--paper);
  border-bottom: 2px solid var(--accent);
}
.selected-school-panel.visible {
  display: block;
}
.selected-school-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}
.selected-school-title {
  /* 13.5px for the one string naming what the user just tapped inverted the
     hierarchy against the 16px search input directly above it. */
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.35;
  padding-top: var(--space-1);
}
.selected-school-close {
  background: none;
  border: none;
  color: var(--ink-2);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
  display: grid;
  place-items: center;
  margin: -8px -10px 0 0;
  border-radius: 2px;
}
@media (hover: hover) {
  .selected-school-close:hover {
    color: var(--ink);
    background: var(--paper-2);
  }
}
.selected-school-info {
  margin-top: var(--space-1);
}
.sel-meta {
  color: var(--ink-2);
  font-size: 12.5px;
}
.sel-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.sel-chip {
  font-size: var(--text-xs);
  padding: 2px var(--space-2);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  color: var(--ink-2);
}
/* Awards conferred, riding on the program chip. Tabular so a column of
   chips does not reflow as counts change, and one step darker than the
   label because the number is the evidence and the label is the caption. */
.sel-chip-n {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  margin-left: 2px;
}
.sel-note {
  margin-top: 7px;
  font-size: 10.5px;
  line-height: 1.45;
  color: var(--ink-3);
}
.selected-school-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: 10px;
}
/* The two conversion actions on the card were the smallest primary targets
   on the page (32px tall, side by side in a 358px box, both truncating). */
@media (max-width: 768px) {
  .selected-school-actions {
    flex-direction: column;
  }
  .selected-school-btn {
    width: 100%;
    min-height: 44px;
    font-size: 14px;
  }
}
.selected-school-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--paper);
  border: none;
  border-radius: 2px;
  padding: var(--space-2) 14px;
  min-height: 44px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
}
.selected-school-btn.secondary {
  background: var(--paper-2);
  color: var(--ink);
  /* --line-strong is a HAIRLINE token (0.17 alpha): ~1.4:1 over --paper-2,
     well under the 3:1 floor of WCAG 1.4.11, and it was the ONLY thing
     saying "this is a button". Same lesson the sheet handle already learned
     further down this file. --ink-3 lands at 5.8:1. */
  border: 1px solid var(--ink-3);
}
/* Hover only where a pointer can actually hover. On a touch screen :hover
   sticks after the tap, so a hardcoded light hover fill (was #f4f0ea) stayed
   painted under near-white --ink text in dark mode: 1.03:1, unreadable. The
   fill is now mixed off the theme tokens so it tracks light and dark. */
@media (hover: hover) {
  .selected-school-btn:hover {
    background: var(--accent-2);
  }
  .selected-school-btn.secondary:hover {
    background: color-mix(in srgb, var(--ink) 8%, var(--paper-2));
    color: var(--ink);
  }
}

/* Directory list */
.school-list {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* The last row must clear the rounded panel edge at scroll end. */
  padding-bottom: var(--space-3);
}
/* The directory can run to hundreds of rows inside a sheet that shows six.
   Skipping layout and paint for the offscreen ones is the cheapest INP win
   available here, and the intrinsic size keeps the scrollbar honest. */
.sg-row {
  content-visibility: auto;
  contain-intrinsic-size: auto 44px;
}
/* Scroll hint: fade the clipped row at the panel's bottom edge while more
   rows remain below (filter-panel.js toggles fp-more on scroll). */
.panel::after {
  content: '';
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 1px;
  height: 44px;
  border-radius: 2px;
  background: linear-gradient(to bottom, transparent, rgba(var(--glass), 0.94));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s;
}
.panel.fp-more::after {
  opacity: 1;
}
.school-list::-webkit-scrollbar {
  width: 8px;
}
.school-list::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 2px;
}
.loading {
  padding: var(--space-6);
  text-align: center;
  color: var(--ink-2);
  font-size: var(--text-sm);
}
.sg-count {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: var(--space-2) var(--space-4) var(--space-1);
  font-size: var(--text-xs);
  color: var(--ink-2);
}
.sg-hide {
  border: none;
  background: none;
  /* Negative margin keeps the 11px label visually where it was while giving
     the hit area the 24px WCAG 2.2 (2.5.8) floor. */
  padding: 7px 6px;
  margin: -7px -6px;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
@media (hover: hover) {
  .sg-hide:hover {
    color: var(--ink);
  }
}

/* Collapsed directory: the list is one click away, not a wall of rows. */
.school-list.is-collapsed {
  flex: 0 0 auto;
  /* The 180px floor below exists so a live directory is never squeezed to
     one row. A collapsed directory is one row on purpose. */
  min-height: 0;
  overflow: visible;
  padding-bottom: 0;
}
/* On the phone sheet the preview must still SHRINK: flex-basis auto at a
   fixed sheet height meant the 3-row preview kept its full content height
   and the flex overflow spilled the foot (and its capture CTA) past the
   viewport edge — the sheet looked sheared off. It scrolls instead. */
@media (max-width: 768px) {
  .school-list.is-collapsed {
    flex: 0 1 auto;
    /* At least one full row stays visible on the shortest phones; the
       rest of the preview scrolls. */
    min-height: 72px;
    overflow-y: auto;
    overflow-x: hidden;
  }
}
.sg-browse {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 56px;
  padding: var(--space-3) var(--space-4);
  border: none;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background 0.12s,
    box-shadow 0.12s;
}
/* A persistent rail, not the accent bar: touch has no hover to discover the
   row with, but .sg-row:hover already means "accent bar = you are on this
   row", and two meanings for one mark is one too many. The foot draws its own
   top border, so this row must not draw the same line again. */
.sg-browse {
  box-shadow: inset 3px 0 0 var(--line-strong);
  border-bottom: none;
}
.sg-browse:focus-visible {
  background: var(--paper-2);
  box-shadow: inset 3px 0 0 var(--accent);
}
@media (hover: hover) {
  .sg-browse:hover {
    background: var(--paper-2);
    box-shadow: inset 3px 0 0 var(--accent);
  }
}
.sg-browse-body {
  flex: 1;
  min-width: 0;
}
.sg-browse-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.sg-browse-hint {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--ink-2);
}
.sg-browse-arrow {
  font-size: var(--text-base);
  color: var(--ink-2);
  flex-shrink: 0;
}
.sg-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 10px var(--space-4);
  cursor: pointer;
  border: none;
  border-bottom: 1px solid var(--line);
  background: none;
  font-family: inherit;
  text-align: left;
  transition:
    background 0.12s,
    box-shadow 0.12s;
}
@media (hover: hover) {
  .sg-row:hover {
    background: var(--paper-2);
    box-shadow: inset 3px 0 0 var(--accent);
  }
}
.sg-fav {
  width: 20px;
  height: 20px;
  border-radius: 2px;
  flex-shrink: 0;
}
.sg-mono {
  width: 20px;
  height: 20px;
  border-radius: 2px;
  background: var(--ink);
  color: var(--paper);
  font-size: var(--text-xs);
  font-weight: 700;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}
.sg-body {
  min-width: 0;
  flex: 1;
  display: block;
}
.sg-name {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sg-meta {
  display: block;
  font-size: 11.5px;
  color: var(--ink-2);
  margin-top: 1px;
}
.sg-empty {
  min-height: 60%;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 6px;
  text-align: center;
  padding: var(--space-6);
}
.sg-empty-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.sg-empty-hint {
  font-size: 12px;
  color: var(--ink-2);
  margin-bottom: 6px;
}

/* Panel foot: state links (SEO) + cross-links */
.panel-foot {
  border-top: 1px solid var(--line);
  padding: var(--space-2) var(--space-4) var(--space-3);
  /* The CTA card inside this foot is var(--paper-2). Sharing that value gave
     the card no surface of its own: it read as flatter than the list rows
     above it. The foot is the recessive chrome; the card sits on it. */
  background: var(--paper);
}
.panel-foot details summary {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-2);
  cursor: pointer;
  padding: 10px 0;
  display: flex;
  align-items: center;
  min-height: 44px;
}
.ts-state-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 10px;
  margin: 6px 0 var(--space-1);
  max-height: 180px;
  overflow-y: auto;
}
.ts-state-grid a {
  font-size: 12px;
  color: var(--ink-2);
  text-decoration: none;
  padding: 2px 0;
  display: flex;
  align-items: center;
  min-height: 44px;
}
@media (hover: hover) {
  .ts-state-grid a:hover {
    color: var(--accent);
  }
}
.foot-note {
  font-size: 11.5px;
  margin-top: var(--space-1);
  line-height: 1.5;
}
.foot-link {
  color: var(--ink-2);
  font-weight: 600;
  text-decoration: none;
  display: inline-block;
  padding: 9px 0;
  margin: -5px 0;
}
@media (hover: hover) {
  .foot-link:hover {
    color: var(--accent);
  }
}
.foot-tools {
  font-size: var(--text-xs);
  color: var(--ink-3);
  margin-top: var(--space-1);
}
.foot-tools a {
  display: inline-block;
  padding: 10px 2px;
  margin: -7px 0;
}
.foot-tools a {
  color: var(--ink-2);
  text-decoration: none;
  font-weight: 500;
}
@media (hover: hover) {
  .foot-tools a:hover {
    color: var(--accent);
  }
}

/* Embed mode: bare map */
.embed-mode .hp-header,
.embed-mode .panel {
  display: none !important;
}
.embed-mode .app-main {
  top: 0;
  height: 100vh;
  height: 100svh;
}

/* Sheet handle: mobile-only affordance */
.sheet-handle {
  display: none;
}

/* ── Mobile: map on top, panel as bottom sheet ──────────────── */
@media (max-width: 768px) {
  .app-main {
    display: flex;
    flex-direction: column;
  }
  #map {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
    min-height: 90px;
  }
  .sheet-handle {
    display: grid;
    place-items: center;
    width: 100%;
    /* Was 19px tall in total: below the 24px WCAG floor and far below the
       44px thumb target, on the one control that owns the whole sheet. The
       bar still reads as a 5px grip; the button around it is now 44. */
    min-height: 44px;
    padding: 14px 0 10px;
    background: none;
    border: none;
    cursor: grab;
    flex-shrink: 0;
    /* Without this the browser can claim the vertical gesture as a scroll
       and fire pointercancel mid-drag, which reads as a dead handle. */
    touch-action: none;
  }
  .sheet-handle:active::before {
    background: var(--ink-2);
  }
  .sheet-handle::before {
    content: '';
    width: 44px;
    height: 5px;
    border-radius: 2px;
    /* --line-strong is a HAIRLINE token (0.16 alpha): 1.44:1 light and
       1.57:1 dark, under the 3:1 floor for a graphical control. */
    background: var(--ink-3);
  }
  .panel {
    position: relative;
    inset: auto;
    width: auto;
    flex: 0 0 56%;
    max-height: 56%;
    border-radius: 2px;
    border-left: none;
    border-right: none;
    border-bottom: none;
    transition:
      flex-basis 0.28s ease,
      max-height 0.28s ease;
  }
  .panel.sheet-expanded {
    flex: 0 0 88%;
    max-height: 88%;
  }
  .panel.sheet-peek {
    flex: 0 0 22%;
    max-height: 22%;
  }
  .panel.sheet-peek .panel-head,
  .panel.sheet-peek .panel-foot,
  .panel.sheet-peek .selected-school-panel {
    display: none;
  }
  .panel-head {
    padding: 10px 14px var(--space-2);
  }
  .panel-head h1 {
    font-size: 19px;
    margin-bottom: var(--space-2);
  }
  .panel-sub {
    display: none;
  }
  /* One swipeable line on phones. See the head-budget note further down for
     why it stopped wrapping here and nowhere else. */
  .ts-segments {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    scroll-padding-inline: 14px;
    margin-bottom: var(--space-2);
  }
  .ts-segments button {
    white-space: nowrap;
    padding: 9px 13px;
    min-height: 44px;
    scroll-snap-align: start;
  }
  .panel-stats {
    margin-bottom: var(--space-2);
  }
  /* 16px inputs: iOS Safari zooms the page on focus below 16px */
  .search-row input[type='text'],
  .search-row select {
    font-size: 16px;
  }
  /* One-line scroll rows on narrow screens, but never a chip sliced through
     the middle of a word with no explanation. Two things make a cut edge
     legible instead of broken:

       mask-image  the row fades out at whichever end has more to scroll, so
                   a half-visible chip reads as "there is more this way"
       scroll-snap the row cannot come to REST mid-chip; it settles on a
                   chip boundary, so at rest every visible label is whole

     The hidden scrollbar was the whole problem: it removed the only signal
     that the row scrolled at all, leaving a clipped word that looked like a
     layout bug. Keep it hidden, and pay for it with the fade. */
  .ts-chips,
  .ts-legend {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    -webkit-mask-image: linear-gradient(
      to right,
      transparent 0,
      #000 14px,
      #000 calc(100% - 22px),
      transparent 100%
    );
    mask-image: linear-gradient(
      to right,
      transparent 0,
      #000 14px,
      #000 calc(100% - 22px),
      transparent 100%
    );
    /* The left fade is dead space at scrollLeft 0, so pad it back. */
    padding-left: 14px;
    margin-left: -14px;
  }
  .ts-chips::-webkit-scrollbar,
  .ts-legend::-webkit-scrollbar {
    display: none;
  }
  .ts-chips button,
  .ts-legend button {
    white-space: nowrap;
    scroll-snap-align: start;
  }
  .ts-chips button {
    padding: 7px 11px;
  }
  .ts-legend {
    margin-bottom: var(--space-2);
  }
  /* When a school is selected, hand its card the head's space */
  .panel.has-selection .panel-stats,
  .panel.has-selection .chips-label,
  .panel.has-selection .ts-chips,
  .panel.has-selection .ts-legend,
  .panel.has-selection .panel-sub {
    display: none;
  }
  .panel-foot {
    padding: 6px 14px var(--space-2);
  }
  .map-attrib {
    display: none;
  }
}

/* Short phones. 56% of a 667px screen is 341px, and the head, the collapsed
   three-row directory and the foot need 366: the sheet arrived at its
   DEFAULT stop already scrolling, so the access CTA and the browse links
   were cut off before the visitor had touched anything. Give the rest stop
   the height its own contents ask for. */
@media (max-width: 768px) and (max-height: 720px) {
  .panel {
    flex: 0 0 64%;
    max-height: 64%;
  }
}

/* Landscape phones: the sheet itself scrolls */
@media (max-height: 500px) {
  .panel {
    overflow-y: auto;
    flex: 0 0 62%;
    max-height: 62%;
  }
  .panel-head {
    flex-shrink: 0;
  }
  .school-list {
    flex: none;
    min-height: 160px;
  }
}

/* ── Map inspect HUD + density scale (kepler/Tufte overlays) ── */
.map-inspect {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 20;
  pointer-events: none;
  min-width: 150px;
  /* Five type rows plus a place line; 240px ellipsised "community colleges"
     on the widest row. */
  max-width: 264px;
  padding: 9px 11px;
  background: rgba(var(--glass), 0.96);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  box-shadow: 0 8px 28px rgba(var(--shadow-rgb), calc(0.14 * var(--shadow-boost)));
  font-variant-numeric: tabular-nums;
  will-change: transform;
}
.map-inspect .mi-n {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1.25;
}
.map-inspect .mi-mix {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--ink-2);
  line-height: 1.4;
}
.map-inspect .mi-act {
  margin-top: 5px;
  font-size: var(--text-xs);
  letter-spacing: 0;
  color: var(--ink-3);
}

/* Cluster tip: place, count, composition. The chip on the map can only
   carry a number; everything that makes that number ACTIONABLE — which
   town it covers, and what mix of institutions it is — lives here. */
.map-inspect .mi-place {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: 0.01em;
  line-height: 1.3;
  margin-bottom: 1px;
  /* One line, ellipsised: a two-line place name would shove the count off
     the cursor and the tip would jump as it resolves in. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.map-inspect .mi-bar {
  display: flex;
  gap: 1px;
  height: 5px;
  margin: 7px 0 6px;
  border-radius: 2px;
  overflow: hidden;
}
.map-inspect .mi-bar i {
  /* flex-basis 0 so the flex-grow the JS writes is a pure proportion of the
     cluster; with the default `auto` basis a segment's share would depend on
     nothing at all, since these have no content. */
  flex-basis: 0;
  min-width: 2px;
}
.map-inspect .mi-row {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.map-inspect .mi-row b {
  font-weight: 700;
  color: var(--ink);
}
.map-inspect .mi-sw {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
/* ── Cluster chooser: small clusters list their members on click ────── */
/* "2 in this cluster · click to expand" used to zoom one step and often
   land you on the same cluster. For clusters small enough to enumerate,
   the click now opens the actual schools so one more tap gets you THERE. */
.cluster-pop .maplibregl-popup-content {
  padding: 6px;
  min-width: 216px;
  max-width: 280px;
  background: rgba(var(--glass), 0.97);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  box-shadow: 0 10px 34px rgba(var(--shadow-rgb), calc(0.2 * var(--shadow-boost)));
}
.cluster-pop .maplibregl-popup-tip {
  border-top-color: var(--line-strong);
  border-bottom-color: var(--line-strong);
}
.cluster-pop .maplibregl-popup-close-button {
  width: 26px;
  height: 26px;
  top: 2px;
  right: 2px;
  border-radius: 2px;
  font-size: 16px;
  color: var(--ink-2);
}
.cluster-pop .maplibregl-popup-close-button:hover {
  background: var(--paper-2);
  color: var(--ink);
}
.cp-title {
  padding: 5px 30px var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-3);
}
.cp-list {
  max-height: 230px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.cp-row {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 6px var(--space-2);
  border: none;
  border-radius: 2px;
  background: none;
  color: var(--ink);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
@media (hover: hover) {
  .cp-row:hover {
    background: var(--paper-2);
  }
}
.cp-row .cp-city {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink-2);
}

.map-density-scale {
  position: absolute;
  left: 12px;
  bottom: 36px;
  z-index: 8;
  width: 148px;
  padding: var(--space-2) 10px;
  background: rgba(var(--glass), 0.88);
  backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  border-radius: 2px;
  pointer-events: none;
}
.panel-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 10px var(--space-3);
  margin-bottom: 10px;
  background: var(--ink);
  border-radius: 2px;
  color: var(--paper);
}
.panel-cta-copy {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.panel-cta-copy strong {
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.panel-cta-copy span {
  font-size: 11.5px;
  color: var(--paper);
  opacity: 0.75;
  line-height: 1.35;
}
.panel-cta-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.panel-cta-signin {
  font-size: 12px;
  color: var(--paper);
  opacity: 0.8;
  text-decoration: none;
}
@media (hover: hover) {
  .panel-cta-signin:hover {
    opacity: 1;
  }
}

.map-density-scale .mds-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 5px;
}
.map-density-scale .mds-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-2);
}
.map-density-scale .mds-3d {
  pointer-events: auto;
  cursor: pointer;
  font: inherit;
  min-height: 32px;
  min-width: 32px;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 2px var(--space-2);
  border-radius: 2px;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink-2);
  transition:
    background 0.15s,
    color 0.15s;
}
@media (hover: hover) {
  .map-density-scale .mds-3d:hover {
    border-color: var(--ink-3);
    color: var(--ink);
  }
}
.map-density-scale .mds-3d.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.map-density-scale .mds-bar {
  height: 6px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--accent) 8%, transparent),
    var(--accent)
  );
}
.map-density-scale .mds-hint {
  margin-top: 5px;
  font-size: var(--text-xs);
  color: var(--ink-3);
  line-height: 1.35;
}
@media (max-width: 768px) {
  /* Hidden on purpose, and for a different reason than before. The density
     field is switched OFF on a coarse pointer (see HANDOFF in map-globe.js):
     a phone renders a cluster map where every mark carries a count, so there
     is no ramp left for this legend to key. It was previously hidden with no
     stated reason while ~700 unkeyed dots were still on screen, which is the
     bug; the dots are what changed, not the verdict on the legend. */
  .map-density-scale {
    display: none;
  }
  .map-inspect {
    max-width: 180px;
    font-size: 12px;
  }
}
body.embed-mode .map-density-scale {
  display: none;
}

/* ── Lead CTA (panel foot on map shells) ────────────────────── */
.foot-cta {
  display: block;
  text-align: center;
  background: var(--accent);
  color: var(--paper);
  border-radius: 2px;
  padding: 10px 14px;
  margin: var(--space-1) 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 700;
  text-decoration: none;
  transition: background 0.15s;
}
@media (hover: hover) {
  .foot-cta:hover {
    background: var(--accent-2);
  }
}

/* ── Program-domain chips (trade segment) ────────────────────────────── */
/* Separates the industrial training pipeline from the beauty/health cohorts.
   Visually distinct from the program chips below them: these scope the
   cohort, those filter within it. */
.ts-domains {
  margin-bottom: 10px;
}
.ts-domains button {
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-2);
  font-weight: 600;
}
@media (hover: hover) {
  .ts-domains button:hover {
    border-color: var(--ink-3);
    color: var(--ink);
  }
}
.ts-domains button.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* ══ Mobile sheet budget ══════════════════════════════════════════════
   The sheet is a fixed-height flex column (56% of the viewport). Head and
   foot are auto-sized, and the list is the only flexible child — so when
   head + foot exceed the sheet, flexbox crushes the list to ZERO and the
   directory silently disappears. Measured at 390x844: head 216 + foot 212
   + handle 19 = 447 against a 441 budget, list = 0px.

   Three rules keep that from recurring: the list gets a hard floor, the
   foot stays compact, and the chip rows scroll horizontally instead of
   wrapping into extra lines (or clipping off-screen, which is what they
   did — the segment row measured 667px wide inside a 362px panel). */
@media (max-width: 768px) {
  /* Non-negotiable: the directory is the product. It can shrink, not vanish.
     96px = two preview rows; 132 forced the flex math over budget on
     375x667 once the Type tabs joined the default stop, and the foot (with
     no floor of its own) was what silently paid. */
  .school-list {
    flex: 1 1 auto;
    min-height: 96px;
  }
  /* The foot is links + capture; if the budget is short the LIST shrinks
     to its floor, the foot never collapses to an unclickable sliver. */
  .panel-foot {
    flex-shrink: 0;
  }

  /* Open-ended chip rows become single-line swipeable scrollers. Wrapping
     cost two lines each and the program list has no fixed length.

     .ts-segments is in this group ON PHONES ONLY (it still wraps on desktop).
     Six pills wrapped to three lines at 375px and five at 320px: 124px and
     210px of a sheet whose default stop is ~340px total, which is what put
     the directory under its own 96px floor and pushed the capture band off
     the bottom edge at 375x667. It was excluded originally because a rail
     "clipped the first pill and nothing said the row had moved" — the
     measured [data-ovf] fades and scroll-snap below are the answer to
     exactly that, and updateSegmentChrome() scrolls the active pill in. */
  .ts-segments,
  .ts-legend,
  .ts-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 2px;
  }
  .ts-segments::-webkit-scrollbar,
  .ts-legend::-webkit-scrollbar,
  .ts-chips::-webkit-scrollbar {
    display: none;
  }
  .ts-segments button,
  .ts-chips button,
  .ts-legend > * {
    flex-shrink: 0;
  }

  /* Readability floor is 11px; letting the line wrap on narrow phones
     costs less than text nobody can read. */
  .panel-stats {
    font-size: var(--text-xs);
    letter-spacing: 0.03em;
    margin-bottom: var(--space-2);
  }

  /* The conversion band collapses to a single row: the supporting sentence
     costs ~50px of directory on a phone and the buttons carry the message. */
  .panel-cta {
    padding: var(--space-2) 10px;
    margin-bottom: var(--space-2);
  }
  .panel-cta-copy span {
    display: none;
  }
  .panel-cta-copy strong {
    font-size: 12.5px;
  }

  /* Duplicated by the header nav (Education / Suppliers / Government /
     States) and by "Browse by state" directly above them. */
  .foot-note,
  .foot-tools {
    display: none;
  }
}

/* The trade segment adds two more chip rows (Focus + Programs), which pushed
   the sheet's children to 526px inside a 441px box — the conversion band
   overflowed off-screen entirely. Chips get compact on phones, and the sheet
   itself scrolls as a safety valve so no segment can ever strand the foot
   below the fold no matter how much chrome it adds. */
@media (max-width: 768px) {
  .panel {
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .ts-segments,
  .ts-domains,
  .ts-chips {
    gap: 5px;
    margin-bottom: 6px;
  }
  .ts-segments button,
  .ts-domains button,
  .ts-chips button {
    padding: 5px 10px;
    font-size: 11.5px;
  }
  .chips-label {
    margin-bottom: 3px;
    font-size: var(--text-xs);
  }
  .panel-head h1 {
    margin-bottom: 7px;
  }

  /* Scroll affordance: without it a chip cut mid-word reads as a layout bug
     rather than "swipe for more". .ts-segments wraps, so it gets no mask. */
  .ts-domains,
  .ts-chips,
  .ts-legend {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
}

/* ══ Panel budget at every width ══════════════════════════════════════
   Adding the Focus row pushed the desktop head to 539px inside an 816px
   panel, leaving the directory 60px — one row. The chip rows wrap into
   three lines at a 400px panel width, so the program row scrolls
   horizontally instead, the list gets a floor it cannot be squeezed
   below, and the panel scrolls if a segment's chrome still overruns. */
.panel {
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Desktop floor only. This rule sits AFTER the mobile block, so unscoped it
   silently out-voted the phone's smaller floor (equal specificity, later
   source order) and the sheet's flex math could never resolve: children
   totalled ~500px inside a 441px sheet and the foot rendered clipped below
   the viewport edge. */
@media (min-width: 769px) {
  .school-list {
    min-height: 180px;
  }
}
.ts-chips {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
.ts-chips::-webkit-scrollbar {
  display: none;
}
.ts-chips button {
  flex-shrink: 0;
}

/* ── Country scope ───────────────────────────────────────────────────── */
.country-select {
  width: 100%;
  padding: var(--space-2) 11px;
  margin-bottom: var(--space-2);
  background: var(--paper-2);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  color: var(--ink);
  font-size: var(--text-sm);
  font-family: inherit;
}
/* Says what is missing outside the US instead of letting an empty filter row
   imply the data is merely hidden. */
.country-note {
  margin: 0 0 10px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-2);
  background: var(--paper-2);
  border-left: 2px solid var(--line-strong);
  padding: 7px 9px;
  border-radius: 2px;
}

/* ── Reviewed Atlas access ─────────────────────────────────────────── */
.atlas-access-status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0 12px;
  padding: 9px 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--line));
  border-radius: 2px;
  background: color-mix(in srgb, var(--accent) 7%, var(--paper-2));
}
.atlas-access-status[hidden],
.atlas-locked-teasers[hidden] {
  display: none;
}
.atlas-access-status > div {
  min-width: 0;
  flex: 1;
}
.atlas-access-status strong,
.atlas-access-status span {
  display: block;
}
.atlas-access-status strong {
  color: var(--ink);
  font-size: 12px;
  letter-spacing: 0.01em;
}
.atlas-access-status span {
  margin-top: 1px;
  color: var(--ink-2);
  font-size: 10.5px;
  line-height: 1.35;
}
.atlas-access-status button {
  flex-shrink: 0;
  min-height: 34px;
  padding: 6px 9px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  background: var(--accent);
  color: var(--paper);
  font: 700 10.5px/1.2 var(--font-sans);
  cursor: pointer;
}
.atlas-access-status.is-approved {
  border-color: var(--line-strong);
  background: var(--paper-2);
}
.atlas-access-status.is-approved button {
  display: none;
}

.atlas-locked-teasers {
  padding: 8px 12px 10px;
  border-top: 1px solid var(--line);
  background: var(--paper-2);
}
.atlas-fuzz-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 8px;
  margin: 3px 0;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--ink-2);
  font-size: 11px;
  opacity: 0.58;
  overflow: hidden;
  user-select: none;
}
.atlas-fuzz-name,
.atlas-fuzz-place {
  filter: blur(2.8px);
  white-space: nowrap;
}
.atlas-locked-teasers p {
  margin: 7px 2px 0;
  color: var(--ink-2);
  font-size: 10.5px;
  text-align: center;
}
.ts-segments button.atlas-scope-locked,
.ts-state-grid a.atlas-scope-locked {
  opacity: 0.46;
}
.ts-segments button.atlas-scope-locked::after {
  content: '\1f512';
  margin-left: 4px;
  /* Was 9px, which is below the size at which a glyph reads as a symbol
     rather than as dirt on the screen. This is the only mark distinguishing
     an unavailable filter from an available one. */
  font-size: 11px;
  opacity: 0.9;
}
.ts-state-grid a.atlas-scope-locked::after {
  content: '\2022\2022\2022  \1f512';
  margin-left: auto;
  padding-left: 5px;
  filter: blur(0.7px);
  font-size: 9px;
}

/* ── Atlas access capture ────────────────────────────────────────────── */
/* The form requests a reviewed grant; it does not unlock data by itself. */
.panel-cta {
  display: block;
  position: relative;
  background: var(--paper-2);
  border: 1px solid var(--line-strong);
  color: var(--ink);
  padding: 10px 34px 10px var(--space-3);
}
.panel-foot .panel-cta-copy span {
  color: var(--ink);
  opacity: 0.65;
}
.panel-foot .panel-cta-form input {
  border-color: var(--line-strong);
  background: var(--paper);
  color: var(--ink);
}
.panel-foot .panel-cta-form input::placeholder {
  color: var(--ink);
  opacity: 0.45;
}
.panel-foot .panel-cta-msg {
  color: var(--ink);
  opacity: 0.7;
}
.panel-cta[hidden] {
  display: none;
}
.panel-cta-close {
  position: absolute;
  top: 0;
  right: 0;
  /* Was 24x24 — the legal WCAG floor exactly, and unusable with a thumb.
     The 13px glyph stays put; the box grows around it. */
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 2px;
  background: transparent;
  cursor: pointer;
}
.panel-cta-close svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: var(--ink);
  opacity: 0.55;
  stroke-width: 2;
  stroke-linecap: round;
}
@media (hover: hover) {
  .panel-cta-close:hover {
    background: var(--line-strong);
  }
  .panel-cta-close:hover svg {
    opacity: 1;
  }
}
.panel-cta-form {
  display: flex;
  gap: 6px;
  margin-top: var(--space-2);
}
.panel-cta-form input {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-2) 11px;
  font-size: 16px;
  font-family: inherit;
  border: 1px solid rgba(var(--paper-rgb), 0.22);
  background: rgba(var(--paper-rgb), 0.08);
  color: var(--paper);
  border-radius: 2px;
}
.panel-cta-form input::placeholder {
  color: rgba(var(--paper-rgb), 0.55);
}
.panel-cta-form input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.panel-cta-form button {
  flex-shrink: 0;
  padding: var(--space-2) 14px;
  font-size: 12.5px;
  font-weight: 700;
  font-family: inherit;
  color: var(--paper);
  background: var(--accent);
  border: none;
  border-radius: 2px;
  cursor: pointer;
}
@media (hover: hover) {
  .panel-cta-form button:hover {
    background: var(--accent-2);
  }
}
.panel-cta-msg {
  margin: 7px 0 0;
  font-size: 11.5px;
  color: var(--ink-2);
  min-height: 0;
}

/* ══ Mobile sheet, corrected ═══════════════════════════════════════════
   Three faults shipped together and made the sheet unusable on a phone:
   the panel AND the list were both overflow:auto, so every touch drag was
   ambiguous between two nested scrollers; the head had grown to 327px of a
   441px sheet (country selector + focus chips + note + signup); and the
   handle was a click toggle with no drag.

   One scroll surface only: the list. The panel never scrolls. */
@media (max-width: 768px) {
  .panel {
    overflow: hidden;
    touch-action: pan-y;
  }
  /* The EXPANDED stop shows every control plus the capture form and the
     state browser; on a 667px phone that stack exceeds the 88% sheet and
     the foot used to shear off past the viewport with no way to reach it.
     At this stop the map is already surrendered, so the sheet may scroll. */
  .panel.sheet-expanded {
    /* Block flow, not flex: shrink-to-fit flex children under-report the
       scroll extent, which left the last rows unreachable at max scroll. */
    display: block;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .panel.sheet-expanded .school-list {
    /* Content height; the sheet is the one scroll surface at this stop. */
    flex: none;
    min-height: 0;
    overflow: visible;
  }
  .panel.sheet-expanded .panel-foot {
    /* Clearance at the scroll tail: the last row must fully clear the
       viewport edge at max scroll. */
    padding-bottom: var(--space-5);
  }
  .school-list {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  /* Head must leave the list room without being squeezed out of existence.
     max-height + flex-shrink collapsed it to the h1 and took the segment
     tabs, country selector, search and Focus chips off the phone entirely.
     flex-shrink:0 keeps the controls; the trimming rules below keep it small. */
  .panel-head {
    flex-shrink: 0;
    padding: 10px 14px var(--space-2);
  }

  /* THE HEAD BUDGET.
     .panel-head is flex-shrink:0 at a fixed 332px inside a 340px sheet, and
     the panel deliberately never scrolls, so at the default stop the
     directory had a visible height of ZERO: not below the fold, unreachable.
     Measured 0 of 3 preview rows on 375x667 and 390x664. Someone typing
     "welding" watched fourteen matching rows render where they could not be
     seen, and no gesture revealed them.

     So the sheet earns its filters. At the default stop it keeps the three
     things the first decision needs: the title, the TYPE tabs, and the
     search row. Country, Focus and Programs are one tap up, at the stop
     that has room for them.

     The Type tabs used to be in this hide list and the stats line was not —
     which inverted the priority: the one control that says what the atlas
     HOLDS (universities / colleges / trade schools) was invisible until you
     found the drag handle, while a passive counter kept its row. The counts
     come back at the expanded stop; the list itself already answers "how
     many are in view" at the default one.

     The h1 is SHRUNK, never hidden: it is this page's only h1, and this site
     has already paid once for shipping crawl content as hidden text.

     The state select joined this list when search was promoted out of the
     Where section: search used to share a row with it, so the two cost 49px
     together. Full-width search plus a full-width select costs 99px, which
     is the whole directory on a 375x667 phone. Search stays; state goes up
     one stop with country, focus and programs — and "Browse by state" in
     the foot is at that same stop. */
  .panel:not(.sheet-expanded) #fpSecWhere .search-row,
  .panel:not(.sheet-expanded) .panel-stats,
  .panel:not(.sheet-expanded) #programChipsLabel,
  .panel:not(.sheet-expanded) #programChips,
  .panel:not(.sheet-expanded) #countryLabel,
  /* #countryPicker, not #countryFilter. #countryFilter is the native <select>,
     already aria-hidden and tabindex=-1; the control the user actually sees is
     #countryPickerBtn inside #countryPicker. Hiding the wrong one left a 40px
     combobox in a 151px head budget, stripped of the "Country" label the rule
     above it did hide, reading only "All countries" with no context. */
  .panel:not(.sheet-expanded) #countryPicker,
  .panel:not(.sheet-expanded) #countryNote,
  .panel:not(.sheet-expanded) #domainChipsLabel,
  .panel:not(.sheet-expanded) #domainChips {
    display: none;
  }
  .panel:not(.sheet-expanded) .panel-head h1 {
    /* 15px put the page's only h1 BELOW the 16px search input directly under
       it, so the input out-typed the headline. The head budget that shrank it
       is right; this is one step back up, not a reversal. */
    font-size: var(--text-lg);
    margin-bottom: var(--space-1);
  }

  .ts-segments,
  .ts-domains,
  .ts-chips {
    margin-bottom: 5px;
  }
  .search-row {
    margin-bottom: 5px;
  }
  .panel-stats {
    margin-bottom: 5px;
  }
  .country-note {
    display: none;
  }
  .panel-head h1 {
    font-size: 18px;
    margin-bottom: 6px;
  }
  .panel-sub {
    display: none;
  }
  .country-select {
    padding: 6px 10px;
    margin-bottom: 6px;
  }
  /* The access ask belongs in the foot on a phone, not above the directory. */
  .panel-cta-form,
  .panel-cta-copy span {
    display: none;
  }
  .panel.sheet-expanded .panel-cta-form {
    display: flex;
  }
  /* "Browse by state" duplicates the state filter one tap away; at the
     default stop its row is what pushed the foot past the 375x667 budget.
     It returns at the expanded stop. */
  .panel:not(.sheet-expanded) .panel-foot details {
    display: none;
  }

  /* A real drag target: 44px, the iOS minimum. */
  .sheet-handle {
    padding: 14px 0 var(--space-2);
    touch-action: none;
    cursor: grab;
  }
  .sheet-handle:active {
    cursor: grabbing;
  }
  .panel.sheet-dragging {
    transition: none;
  }
}

/* Basemap attribution is a license requirement (OSM ODbL + CARTO), so it
   stays — but it whispers until you ask for it. */
.maplibregl-ctrl-attrib,
.leaflet-control-attribution {
  opacity: 0.35;
  font-size: var(--text-xs);
  transition: opacity 0.15s;
}
@media (hover: hover) {
  .maplibregl-ctrl-attrib:hover,
  .leaflet-control-attribution:hover {
    opacity: 1;
  }
}

/* Theme toggle (hp shell): quiet ghost icon button, no enclosing ring. */
.ba-theme-toggle {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--ink-2);
  line-height: 0;
  /* Was 34x34. The icon still reads at 18px; the tap area is now the 44pt
     platform floor. */
  width: 44px;
  height: 44px;
  border-radius: 2px;
  cursor: pointer;
  margin: 0 6px 0 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    color 0.15s,
    background 0.15s;
}
.ba-theme-toggle svg {
  width: 17px;
  height: 17px;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
@media (hover: hover) {
  .ba-theme-toggle:hover {
    color: var(--ink);
    background: var(--paper-2);
  }
}

/* ══ Chip-rail scroll affordance, both edges ═══════════════════════════
   The rails already faded their RIGHT edge so "a chip cut mid-word reads
   as a layout bug rather than swipe for more". The left edge never got the
   same treatment, and setSegment() scrolls the rail (scrollLeft =
   activeSeg.offsetLeft - 12) to bring the route's own tab into view — so on
   /universities/ and /education/ the rail loads already scrolled and the
   preceding chip is sliced against a hard edge with no fade at all. That is
   the exact bug the right-edge mask exists to prevent, on the other side.

   Fading both edges unconditionally is not the fix either: at scrollLeft 0
   a permanently dimmed first chip reads as disabled. So only the edge that
   actually has content behind it fades. syncRailEdges() in map-globe.js
   writes data-ovf on scroll and resize; these rules are the whole vocabulary
   of that attribute. Selectors carry the class so they outrank the
   single-edge masks earlier in this file at every breakpoint. */
.ts-segments[data-ovf],
.ts-domains[data-ovf],
.ts-chips[data-ovf],
.ts-legend[data-ovf] {
  /* The active chip must not land flush against a fading edge. */
  scroll-padding-inline: 14px;
  -webkit-mask-image: none;
  mask-image: none;
}
.ts-segments[data-ovf='start'],
.ts-domains[data-ovf='start'],
.ts-chips[data-ovf='start'],
.ts-legend[data-ovf='start'] {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22px);
  mask-image: linear-gradient(to right, transparent 0, #000 22px);
}
.ts-segments[data-ovf='end'],
.ts-domains[data-ovf='end'],
.ts-chips[data-ovf='end'],
.ts-legend[data-ovf='end'] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
}
.ts-segments[data-ovf='both'],
.ts-domains[data-ovf='both'],
.ts-chips[data-ovf='both'],
.ts-legend[data-ovf='both'] {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 22px,
    #000 calc(100% - 22px),
    transparent
  );
  mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 22px,
    #000 calc(100% - 22px),
    transparent
  );
}

@media (max-width: 768px) {
  /* ── The "See all N" row must never be the thing that gets clipped ──
     The collapsed preview is a scroller with a shrinking flex basis, and
     .sg-browse is its last child. Measured on a 390x844 phone the list box
     is 193px against a 217px scrollHeight, so the browse row (58px, the one
     row whose entire job is to open the directory) rendered 35px tall with
     its hint line "Or click a pin on the map" sliced through the middle.
     Pinning it to the bottom of the scroller keeps the whole control on
     screen and turns the preview rows into what scrolls under it. */
  .school-list.is-collapsed .sg-browse {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: var(--paper);
    box-shadow:
      inset 3px 0 0 var(--line-strong),
      0 -6px 12px -8px rgba(0, 0, 0, 0.45);
  }
  .school-list.is-collapsed .sg-browse:focus-visible {
    box-shadow:
      inset 3px 0 0 var(--accent),
      0 -6px 12px -8px rgba(0, 0, 0, 0.45);
  }

  /* The sheet's own bottom edge is the viewport's bottom edge, so on any
     phone with a home indicator the capture CTA sat underneath it. */
  .panel-foot {
    padding-bottom: max(12px, env(safe-area-inset-bottom));
  }
}

/* Product-wide keyboard focus, system contrast, and reduced-motion support. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  /* One ring for the product: 2px forge red, 2px off the element. */
  outline: 2px solid var(--accent) !important;
  outline-offset: 2px;
  scroll-margin-block: 5rem;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
@media (forced-colors: active) {
  :where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
    outline-color: Highlight !important;
  }
  button,
  input,
  textarea,
  select {
    border: 1px solid ButtonText;
  }
  [aria-selected='true'],
  [aria-pressed='true'] {
    outline: 2px solid Highlight;
  }
}

/* ── MapLibre navigation control ─────────────────────────────
   The one control on the page the reader touches most was also the only
   piece of unbranded chrome on it, at MapLibre's stock 29x29 — under the
   24px WCAG 2.5.8 floor once you count the border, and far under 44pt. */
.maplibregl-ctrl-group {
  background: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: none;
  overflow: hidden;
}
.maplibregl-ctrl-group button {
  width: 44px;
  height: 44px;
  background: transparent;
}
.maplibregl-ctrl-group button + button {
  border-top: 1px solid var(--line);
}
.maplibregl-ctrl-group button .maplibregl-ctrl-icon {
  filter: var(--maplibre-icon-filter, none);
}
:root[data-theme='dark'] .maplibregl-ctrl-group {
  --maplibre-icon-filter: invert(1);
}

@media (max-width: 768px) {
  .maplibregl-ctrl-group button {
    width: 44px;
    height: 44px;
  }
  /* Bottom-right of the map band, clear of the sheet's centre drag handle
     and inside the thumb arc. The sheet is a sibling that sits below the
     map, so the control needs no safe-area inset of its own. */
  .maplibregl-ctrl-bottom-right {
    bottom: 8px;
    right: 8px;
  }
  .maplibregl-ctrl-bottom-right .maplibregl-ctrl {
    margin: 0;
  }
}

/* ── Cross-document view transitions ─────────────────────────
   Safari 18.2+, Chrome 126+. Purely progressive: browsers without it just
   navigate. The map is excluded by name because snapshotting a live WebGL
   canvas mid-navigation is the documented hazard here, so only the shell
   chrome animates. prefers-reduced-motion is honoured by the UA. */
@view-transition {
  navigation: auto;
}
#map,
.maplibregl-canvas-container,
.maplibregl-canvas {
  view-transition-name: none;
}

/* ── Map pending state ───────────────────────────────────────
   The map container was bare --paper-2 until tiles arrived, which on a
   trade-show LTE connection reads as broken rather than loading. The dead
   path already has .map-dead; this is the pending one. Cleared by a
   `map-ready` class on <html> from the map's first idle. */
#map::after {
  content: 'Loading map';
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  z-index: 4;
  pointer-events: none;
  background: var(--paper-2);
  color: var(--ink-3);
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  animation: map-pending 1.4s ease-in-out infinite;
}
html.map-ready #map::after {
  content: none;
  animation: none;
}
@keyframes map-pending {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.62;
  }
}
@media (prefers-reduced-motion: reduce) {
  #map::after {
    animation: none;
  }
}
