/* MemberHub City.
 *
 * Two independent parts:
 *   1. .mh-city-root  -- the city's own chrome. Applies wherever it mounts,
 *                        including the standalone page.
 *   2. body.mh-city-active -- the landing page's panel transformation.
 *                        INERT unless the city actually mounted, which is what
 *                        keeps the no-WebGL path byte-identical to today.
 */

/* ---------- 1. the city's own chrome ---------- */

/* NO z-index here, deliberately. `position: fixed` plus a z-index would create
   a stacking context and TRAP every child at page level 0 -- which is what put
   the HUD behind the nav and behind the panels no matter what value it carried.
   With z-index auto the children below participate in the page's own stacking
   context, where the real order is:
     canvas 0  <  HUD/minimap 15  <  panels 20  <  nav 300  <  cookie banner 999 */
.mh-city-root { position: fixed; inset: 0; }
.mh-city-stage-inner { position: absolute; inset: 0; z-index: 0; }
.mh-city-stage-inner canvas { display: block; width: 100%; height: 100%; }

.mh-city-hud {
  /* z-index 17: city chrome must outrank the invisible touch gesture zones
     (.mh-city-touch and children sit at 16) or the HUD's own buttons become
     untappable underneath them on phones -- while staying below panels (20)
     and the site nav (300). */
  position: absolute; top: 12px; left: 12px; z-index: 17;
  /* Two stacked strips now: a core that always shows and a collapsible rest. */
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  font: 13px/1.4 system-ui, sans-serif; color: #1c2b38;
  background: rgba(255, 255, 255, .65);
  padding: 8px 12px; border-radius: 8px;
  backdrop-filter: blur(4px);
}
.mh-city-hud-core, .mh-city-hud-more {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* ⛔ The `display: flex` above BEATS the `hidden` attribute, so without this a
   "collapsed" toolbar stays fully on screen -- which reads as the feature
   never having been implemented. This rule is what makes `hidden` the real
   mechanism, and it is why collapsing uses the attribute at all: the strip
   leaves the tab order and the accessibility tree, not just the paint. */
.mh-city-hud [hidden] { display: none !important; }
.mh-city-hud button {
  cursor: pointer; font-size: 13px; padding: 3px 10px;
  border: 1px solid #8aa; border-radius: 6px;
  background: rgba(255, 255, 255, .8);
}
/* ☰ and ? are icon-only; their accessible names come from the catalog. */
.mh-city-iconbtn { font-size: 13px; line-height: 1; padding: 4px 9px; font-weight: 700; }
.mh-city-seed { opacity: .6; font-size: 11px; }
.mh-city-cash { font-size: 12px; font-weight: 700; color: #2a5c2a; }

.mh-city-quest {
  position: absolute; bottom: 22px; left: 50%; transform: translateX(-50%);
  z-index: 15; color: #fff; background: rgba(20, 28, 40, .75);
  padding: 9px 18px; border-radius: 10px;
  font: 600 15px/1.4 system-ui, sans-serif; backdrop-filter: blur(4px);
  /* A completed delivery adds a second line -- the one sentence about not
     having had to make the trip. pre-line keeps it a single textContent
     write; centring stops two lines of different length looking broken. */
  white-space: pre-line; text-align: center; max-width: min(92vw, 640px);
}

/* ⛔ The line is a SPAN now, not a bare textContent write, so a ✕ can sit
   beside it. `pre-line` and the centring move onto the span — the host becomes
   a flex row only when a button is actually drawn, so every undismissable line
   keeps the block layout its look was tuned against. */
.mh-city-quest { align-items: center; gap: 14px; }
.mh-city-quest-text { white-space: pre-line; text-align: center; }
/* ⚠️ Only ever drawn when a cursor exists — on foot the pointer is locked and
   every click goes to the canvas, so this could not be pressed at all; the X
   key does that job. 30px keeps it a real touch target on the phone, which is
   the one place walking DOES have a pointer. */
.mh-city-quest-close {
  flex: none; min-width: 30px; min-height: 30px; padding: 3px 8px;
  cursor: pointer; font-size: 13px; line-height: 1;
  color: inherit; background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .35); border-radius: 8px;
}
.mh-city-minimap {
  position: absolute; bottom: 18px; right: 18px; z-index: 15;
  width: 180px; height: 180px; border-radius: 10px; opacity: .88;
  pointer-events: none; box-shadow: 0 2px 12px rgba(0, 0, 0, .35);
}
/* ---- help sheet ----------------------------------------------------------
   z-index 25: ABOVE the marketing panels (20), BELOW the site nav (300). City
   chrome must never cover site navigation -- the same call that put the HUD at
   top:87px rather than raising it over the nav. Which is also why this cannot
   occupy the top strip: it clears the 75px sticky nav and scrolls internally.
   ⚠️ Colours come from the PAGE's own tokens. A previous panel used
   --bg-elevated, which this page does not define, and rendered near-black on
   near-black at 1.05:1. */
.mh-city-help {
  position: absolute; top: 87px; left: 50%; transform: translateX(-50%);
  z-index: 25; width: min(560px, calc(100vw - 24px));
  max-height: calc(100vh - 100px); overflow-y: auto;
  background: var(--bg-card, #fff); color: var(--text, #0f172a);
  border: 1px solid var(--border, #cbd5e1); border-radius: 12px;
  padding: 20px 22px; box-shadow: 0 8px 32px rgba(0, 0, 0, .3);
  font: 14px/1.6 system-ui, sans-serif;
}
.mh-city-help[hidden] { display: none !important; }
.mh-city-help h3 { margin: 16px 0 6px; font-size: 14px; }
.mh-city-help h3:first-of-type { margin-top: 0; }
.mh-city-help p { margin: 0 0 4px; }
.mh-city-help ul { margin: 0; padding-left: 18px; }
.mh-city-help li { margin: 2px 0; }
.mh-city-help-close {
  float: right; cursor: pointer; border: 1px solid var(--border, #cbd5e1);
  border-radius: 6px; background: transparent; color: inherit;
  padding: 2px 9px; font-size: 14px; line-height: 1.4;
}
.mh-city-root.photo .mh-city-help { display: none !important; }

/* Photo mode hides the chrome. */
.mh-city-root.photo .mh-city-hud,
.mh-city-root.photo .mh-city-minimap,
.mh-city-root.photo .mh-city-quest { display: none !important; }

/* ...but NOT this bar. Photo mode hides the HUD that contains the Photo button
   itself, so without this a touch visitor had no way to capture and no way out
   except reloading. Hidden outside photo mode; the only chrome inside it.
   Safe for the screenshot: the capture is canvas-only (toBlob on the renderer's
   domElement), so DOM chrome never lands in the saved image. */
.mh-city-photobar { display: none; }
.mh-city-root.photo .mh-city-photobar {
  display: flex; gap: 10px;
  position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 18;                      /* above the touch overlay (16) and HUD (17) */
  pointer-events: auto;
}
.mh-city-root.photo .mh-city-photobar button {
  cursor: pointer; font: 600 15px/1 system-ui, sans-serif;
  padding: 12px 18px; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .55);
  background: rgba(20, 24, 32, .78); color: #fff;
  backdrop-filter: blur(6px);
  touch-action: manipulation;       /* no 300ms tap delay, no double-tap zoom */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
@media (max-width: 640px) {
  /* Clear the iOS home-indicator strip, same reasoning as the thumbstick. */
  .mh-city-root.photo .mh-city-photobar { bottom: max(24px, calc(env(safe-area-inset-bottom) + 16px)); }
}

/* ---------- 2. the landing page's panel transformation ---------- */

body.mh-city-active { overflow: hidden; }

/* The nav needs NO help from us. landing/index.html sets .nav to
   `position: sticky; top: 0; z-index: 300` with a comment saying the value is
   deliberately left alone -- lowering it re-opens a mobile-menu bleed-through.
   An earlier version here overrode it to `position: relative; z-index: 30`,
   which silently un-stuck the nav and undid that fix. */

/* The HUD clears the sticky nav. Measured: the nav is 75px tall and owns the
   top strip, so a HUD at top:12px was hidden UNDERNEATH it -- a geometric
   overlap, not a stacking one. Raising the HUD above z-index 300 would "fix"
   the symptom by putting city controls over the site's own navigation, which
   is the wrong trade. It moves down instead. Only when mounted on the landing
   page; the standalone page has no nav. */
body.mh-city-active .mh-city-hud { top: 87px; }

/* Panels ARE the page's own <section> elements. Hidden by default, one shown
   at a time. Nothing here changes their content -- see panels.js. */
body.mh-city-active .mh-city-panel {
  position: fixed;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(0.98);
  width: min(880px, calc(100vw - 48px));
  max-height: calc(100vh - 160px);
  overflow-y: auto;
  z-index: 20;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
  /* The page's OWN tokens, not invented ones. An earlier version used
     var(--bg-elevated, #12151c) -- a variable the page does not define -- so
     it fell back to a hardcoded dark panel while the headings kept
     var(--text), which in LIGHT theme is #0f172a. Near-black text on a
     near-black panel: roughly 1.05:1, invisible. Using --bg-card means the
     panel tracks the theme toggle and the existing type colours work as
     designed in both. */
  background: var(--bg-card, #ffffff);
  color: var(--text, #0f172a);
  border: 1px solid var(--border, #e2e8f0);
  border-radius: 16px;
  padding: 32px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, .45);
}
body.mh-city-active .mh-city-panel-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity .22s ease, transform .22s ease, visibility 0s;
}

/* Sections with no district stay out of the way while the city is up.
   display:none hides them VISUALLY but keeps them in the DOM, which is what
   preserves the indexable copy. Do not "tidy" this into removing elements --
   panels.test.html's copy-preservation assertion exists to catch exactly that. */
body.mh-city-active > section:not(.mh-city-panel) { display: none; }

/* ⛔ THE FOOTER USED TO BE HIDDEN HERE TOO, AND IT CARRIES THE ONLY PERMANENT
   LINK TO /privacy. The cookie banner has the other one and deletes itself for
   good once consent is given, so a RETURNING visitor had no route to the
   privacy policy anywhere on memberhub.live -- verified on prod 2026-08-16,
   zero visible /privacy links with the city up. It took /ja-us with it.
   ⛔ IT WAS A CHIP AT top:87px RIGHT FIRST, AND THAT COLLIDED WITH THE HUD.
   The HUD is top-left but wraps rightward, so at ~780px it ran straight under
   the chip and buried Job / New city / the cash and seed labels. Caught by
   screenshot, not by reading -- the two rules were each correct alone.
   The bottom edge is the one strip nothing else claims at ANY width: the
   minimap sits 18px up, the thumbstick 18px, the quest banner 22px and the
   photo bar 24px, so a ~20px strip at bottom 0 passes under all of them.
   z-index 19 clears the touch overlay (16) and the HUD (17) while staying
   under the panels (20) and the site nav (300). */
body.mh-city-active > footer {
  position: fixed;
  top: auto; left: 0; right: 0; bottom: 0;
  z-index: 19;
  margin: 0;
  padding: 4px 12px max(4px, env(safe-area-inset-bottom));
  text-align: center;
  font: 11px/1.5 system-ui, sans-serif;
  /* ⛔ OPAQUE, AND ON THE PAGE'S OWN TOKENS -- the same rule the panel
     contrast fix landed on. A translucent bar (rgba(255,255,255,.62) with
     --text-sec) MEASURED 2.79:1 in day and 2.32:1 at night against the real
     rendered pixels: the scene bleeds through and dilutes the background,
     and it gets worse the darker the city gets. Tokens also mean the bar
     tracks the theme toggle instead of being a white slab in dark mode.
     Measured after: 12.6:1 light, 15.7:1 dark. */
  color: var(--text, #0f172a);
  background: var(--bg-card, #ffffff);
  border-top: 1px solid var(--border, #e2e8f0);
}
body.mh-city-active > footer p { margin: 0; display: inline; }
body.mh-city-active > footer p + p { margin-left: 8px; }
body.mh-city-active > footer a { color: inherit; text-decoration: underline; pointer-events: auto; }
/* The strip sits ABOVE the touch overlay (16) so its links are tappable, which
   would otherwise let it swallow the bottom sliver of the thumbstick. Only the
   links need to catch a pointer; the bar itself must not. */
body.mh-city-active > footer { pointer-events: none; }
/* ...and lift the two bottom-anchored city elements clear of the ~24px strip
   rather than letting it cover their lower edge. Measured: the minimap's
   bottom 5px was under it at 780x493. */
body.mh-city-active .mh-city-minimap { bottom: 46px; }
body.mh-city-active .mh-city-quest { bottom: 50px; }

/* Phones are IN for Phase 1 (owner decision). A 180px minimap and 32px panel
   padding do not fit a 360px screen, so this block is not optional polish --
   without it the panels are unusable on devices the gate now admits. */
@media (max-width: 640px) {
  body.mh-city-active .mh-city-panel {
    width: calc(100vw - 20px);
    max-height: calc(100vh - 96px);
    padding: 20px 16px;
    border-radius: 12px;
  }
  .mh-city-minimap { width: 96px; height: 96px; bottom: 12px; right: 12px; }
  .mh-city-hud { left: 8px; gap: 6px; font-size: 12px; padding: 6px 8px; }
  body.mh-city-active .mh-city-hud { top: 80px; }
  .mh-city-hud button { font-size: 12px; padding: 2px 8px; }

  /* Same strip, tightened. 384px is what a real flagship reports (an S26 Ultra
     is NARROWER than an iPhone 13 because of Android display scaling), so the
     two links have to survive that width, not a comfortable 414. */
  body.mh-city-active > footer { padding-left: 6px; padding-right: 6px; font-size: 10px; }
  body.mh-city-active > footer p + p { margin-left: 6px; }
  /* ⛔ At 384px -- what a real flagship reports -- the copyright and the two
     links do not fit on one line, so the strip wrapped to 39px and clipped the
     minimap again. The links are the functional half (the privacy policy is
     the whole reason this bar exists); the copyright is a courtesy line. Hide
     it here so the strip stays one predictable line. It stays IN THE DOM, like
     every other section the city hides. */
  body.mh-city-active > footer p:first-child { display: none; }
  /* the phone minimap is 96px at bottom 12; lift both clear of the strip here too */
  body.mh-city-active .mh-city-minimap { bottom: 38px; }
  body.mh-city-active .mh-city-quest { bottom: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  body.mh-city-active .mh-city-panel { transition: none; }
}

/* ---------- 3. touch controls (Phase 2) ---------- */
/* Only rendered on coarse-pointer devices; main.js decides, not CSS, so the
   decision is testable and matches the quality tier's own capability check. */

.mh-city-touch {
  position: absolute; inset: 0; z-index: 16; pointer-events: none;
  /* Suppress iOS's long-press callout/selection magnifier: raising it
     releases pointer capture and fires pointercancel mid-press, cutting the
     horn out. Inherits to the stick, look zone and every action button. */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}

.mh-city-stick {
  position: absolute; left: 18px;
  bottom: max(18px, calc(env(safe-area-inset-bottom) + 12px));
  width: 132px; height: 132px; border-radius: 50%;
  background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .35);
  pointer-events: auto; touch-action: none;
}
.mh-city-stick-knob {
  position: absolute; left: 50%; top: 50%;
  width: 56px; height: 56px; margin: -28px 0 0 -28px; border-radius: 50%;
  background: rgba(255, 255, 255, .55);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
  transition: transform .05s linear;
}
/* The look zone is the right half, BELOW the nav and clear of the stick. */
.mh-city-look {
  position: absolute; right: 0; top: 87px; bottom: 0; width: 50%;
  pointer-events: auto; touch-action: none;
}
.mh-city-actions {
  position: absolute; right: 14px; bottom: 120px;
  display: grid; grid-template-columns: repeat(2, 56px); gap: 10px;
  pointer-events: auto;
}
.mh-city-action {
  width: 56px; height: 56px; border-radius: 50%;
  font-size: 20px; line-height: 1;
  background: rgba(255, 255, 255, .55);
  border: 1px solid rgba(255, 255, 255, .5);
  touch-action: none;
}
/* Photo mode hides these too. */
.mh-city-root.photo .mh-city-touch { display: none !important; }

/* ---------- 3. the boot overlay ---------- */

/* What it is for: boot.js is a MODULE script, so it runs only after the whole
   marketing document has been parsed and painted, and it then resolves four
   more imports before it reaches the line that adds `mh-city-active`. The
   classic page is fully visible for all of that, and after it is hidden there
   is still the three.js download and the ~750ms Worker city build. This covers
   the gap with something that says "a 3D thing is coming".

   ⛔ It COVERS the copy, it never removes it. See boot-overlay.js.

   z-index 500: above the nav (300) because a boot cover with a navbar poking
   through it looks broken -- but DELIBERATELY below the cookie banner (999).
   A consent prompt must never be obscured by a loading screen. */
.mh-boot {
  position: fixed; inset: 0; z-index: 500;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 28px;
  background: var(--bg, #0a0f1e);
  background-image: var(--gradient-bg, none);
  opacity: 1;
  transition: opacity 380ms ease;

  /* FAILSAFE. Fires even if the entire module graph fails to load, so a
     network fault or a JS error can never leave the sales page permanently
     behind a loading screen. 0s duration at an 8s delay = a hard cutoff, not
     a fade. The three ways out share no logic on purpose. */
  animation: mh-boot-failsafe 0s linear 8s forwards;
}

/* Set by hideBootOverlay() on every boot path: mounted, refused, or threw. */
.mh-boot.mh-boot-done { opacity: 0; visibility: hidden; pointer-events: none; }

@keyframes mh-boot-failsafe { to { opacity: 0; visibility: hidden; } }

/* The cube. CSS 3D, not WebGL: it has to paint while three.js is still
   downloading, which is the whole point of it. */
.mh-boot-cube {
  position: relative; width: 84px; height: 84px;
  transform-style: preserve-3d;
  animation: mh-boot-spin 5.2s linear infinite;
}
.mh-boot-face {
  position: absolute; inset: 0;
  border: 2px solid var(--primary, #6366f1);
  background: rgba(99, 102, 241, .09);
  box-shadow: inset 0 0 22px rgba(99, 102, 241, .18);
}
/* 84px cube, so each face sits 42px off centre. */
.mh-boot-face.f1 { transform: rotateY(  0deg) translateZ(42px); }
.mh-boot-face.f2 { transform: rotateY( 90deg) translateZ(42px); }
.mh-boot-face.f3 { transform: rotateY(180deg) translateZ(42px); }
.mh-boot-face.f4 { transform: rotateY(270deg) translateZ(42px); }
.mh-boot-face.f5 { transform: rotateX( 90deg) translateZ(42px); }
.mh-boot-face.f6 { transform: rotateX(270deg) translateZ(42px); }

@keyframes mh-boot-spin {
  from { transform: rotateX(-24deg) rotateY(0deg); }
  to   { transform: rotateX(-24deg) rotateY(360deg); }
}

.mh-boot-label {
  font-size: .8125rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-sec, #94a3b8);
}

/* A spinning box is exactly the kind of motion this setting is about. Hold the
   cube at a pose that still reads as three-dimensional instead of stopping it
   flat-on, and keep the overlay itself doing its job. */
@media (prefers-reduced-motion: reduce) {
  .mh-boot-cube { animation: none; transform: rotateX(-24deg) rotateY(-38deg); }
  .mh-boot { transition: none; }
}

/* ---- tool modal ----------------------------------------------------------
   Clicking a labelled building opens that category's real feature cards.
   ⛔ Uses the PAGE'S OWN tokens, never `--bg-elevated`, which the page does not
   define — that undefined variable once produced 1.05:1 contrast, invisible
   text on a live site. Measured on these tokens: 17.85:1 light, 16.19:1 dark.
   ⚠️ Every token carries a FALLBACK, like the help sheet and the panels above:
   city/index.html (the standalone dev page) defines no :root tokens at all, so
   an unfallbacked var() would render a transparent body over the backdrop.
   ⚠️ z-index 400: above the nav (300) so the modal is not pierced by it, but
   BELOW the cookie banner (999). A consent prompt must never be obscured. */
.mh-tool-modal {
  position: fixed; inset: 0; z-index: 400;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(2, 6, 23, 0.72);
}
.mh-tool-modal-body {
  width: 100%; max-width: 720px; max-height: 78vh; overflow-y: auto;
  background: var(--bg-card, #ffffff); color: var(--text, #0f172a);
  border: 1px solid var(--border, #e2e8f0); border-radius: 16px; padding: 28px;
  display: grid; gap: 16px;
}

/* ⛔ THE ONLY VISIBLE WAY OUT OF THE PANEL. Clicking the backdrop and pressing
   Escape both close it and neither announces itself, which is the same failure
   as a clickable building with no cursor: the mechanism exists and nothing says
   so. ⚠️ Sticky, so it is still reachable once a long category is scrolled, and
   it carries the card background rather than a transparent one — a transparent
   sticky button would have cards sliding through it.
   ⚠️ 34px min: a real touch target, since this is the phone's way out too. */
.mh-tool-modal-close {
  justify-self: end; position: sticky; top: 0; z-index: 1;
  min-width: 34px; min-height: 34px; padding: 4px 10px;
  cursor: pointer; font-size: 15px; line-height: 1;
  color: inherit; background: var(--bg-card, #ffffff);
  border: 1px solid var(--border, #e2e8f0); border-radius: 8px;
}

/* ⛔ The cursor is the ONLY thing telling a visitor a building can be opened.
   Set from a pointermove test in main.js that projects the six sign quads —
   never a raycast: one intersectObject against the merged city mesh costs 35ms
   here, more than twice a 60fps frame. */
.mh-city-stage canvas.mh-tool-hover { cursor: pointer; }

/* ⛔ THE WALK-MODE AFFORDANCE. On foot the pointer is LOCKED: the cursor is
   invisible and clientX/clientY are frozen by the spec, so `cursor: pointer`
   is meaningless there and a pointer-position hit test aims at a stale point.
   You aim with the CAMERA, so the marker sits at the centre of the screen.
   ⚠️ z-index 26: above the help sheet's 25 so it is never buried, still far
   below the site nav (300) and the cookie banner (999). */
.mh-city-aim {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 26; pointer-events: none;
  display: flex; align-items: center; gap: 8px;
  border-radius: 999px; color: #fdf6e3;
  font: 600 13px/1 system-ui, sans-serif; white-space: nowrap;
  /* Bare crosshair by default: no plate, no text, just the dot below. */
  padding: 0; background: none; border: 1px solid transparent;
  transition: padding .12s ease, background-color .12s ease;
}
/* ⛔ The dot carries its own dark ring rather than relying on a backdrop: at
   this size a plain white dot vanishes against a pale facade or the sky, and
   the crosshair has to be readable everywhere the visitor can look. */
.mh-city-aim::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: #fdf6e3; box-shadow: 0 0 0 1.5px rgba(2, 6, 23, .75);
}
/* On target: the dot grows a plate and says what will open. */
.mh-city-aim--on {
  padding: 6px 12px;
  background: rgba(2, 6, 23, .72);
  border-color: rgba(253, 246, 227, .35);
}

/* The tour's fade-through-black for indoor cuts. Above the canvas (0), below
   the touch zones (16) and the HUD (17); pointer-events none so it never
   swallows the drag that stops the tour. */
.mh-city-fade { position: absolute; inset: 0; background: #000; opacity: 0;
  pointer-events: none; transition: opacity .25s ease; z-index: 8; }
.mh-city-fade.on { opacity: 1; }
