/* §22.5 — Critical path CSS: tokens, map shell, boot animation.
   Full UI loads async via style.css (see index.html). */

/* ── Design Tokens (minimal above-fold) ── */
:root {
  --bg: #F7F7F7;
  --surface: #FFFFFF;
  --border: #EAEAEA;
  --text: #222222;
  --text-2: #717171;
  --c-accent: #FF5A5F;
  --c-accent-hover: #E31C5F;
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-1: 160ms;
  --dur-3: 320ms;
  --dur-4: 480ms;
  --radius-lg: 16px;
  --radius-full: 9999px;
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
  --shadow-xl: 0 20px 50px rgba(0, 0, 0, 0.12);
  --text-3: #9ca3af;
  --accent: var(--c-accent);
  --accent-hover: var(--c-accent-hover);
  --font-display: 'Plus Jakarta Sans', sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}

html[data-theme="night"] {
  --bg: #1a2030;
  --surface: #242a3a;
  --border: #343d52;
  --text: #e8eaef;
  --text-2: #9aa3b5;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body { overscroll-behavior: none; }

/* §notch/iOS audit — with viewport-fit=cover, iOS paints the safe-area strip
   behind the notch/status bar using the ROOT (html) element's background,
   not body's. Only `body` had one (below); `html` fell back to the browser's
   default (white), which is correct-by-accident only until JS runs
   (syncThemeColorMeta sets documentElement.style.backgroundColor once the
   map's 'load' event fires — main.js ~7283). Until then — and on any device/
   network slow enough for that gap to be visible — the notch strip flashed
   white against the day/night map colour. Mirror body's --bg here so the
   very first paint is already correct with no JS dependency. */
html { background: var(--bg); }

body {
  font-family: var(--font-body);
  font-size: 14px;
  background: var(--bg);
  color: var(--text);
  overflow: hidden;
  height: 100vh;
  width: 100vw;
  -webkit-font-smoothing: antialiased;
}

/* Hide chrome until full stylesheet lands (prevents unstyled flash). */
html.css-loading #left-panel,
html.css-loading #right-panel,
html.css-loading .map-controls-stack,
html.css-loading #ts-topbar,
html.css-loading #ts-route,
html.css-loading .anon-welcome {
  visibility: hidden;
}

/* Size the brand wordmarks in the critical CSS. The SVG is intrinsically
   1409×361, so without this it renders huge until the deferred stylesheet
   shrinks it — the main source of the layout shift (CLS). */
.brand-wordmark { height: 22px; width: auto; max-width: 148px; display: block; }
.brand-wordmark--dark { display: none; }
html[data-theme="night"] .brand-wordmark--light { display: none; }
html[data-theme="night"] .brand-wordmark--dark { display: block; }
.auth-brand-wordmark { height: 26px; width: auto; max-width: 200px; display: block; }
.auth-brand-wordmark--dark { display: none; }
html[data-theme="night"] .auth-brand-wordmark--light { display: none; }
html[data-theme="night"] .auth-brand-wordmark--dark { display: block; }

/* The auth modal is a full-screen fixed overlay in the deferred CSS. Until that
   lands it would otherwise sit IN document flow (a tall block) and then jump out
   of flow when styled — a ~0.68 layout shift. Pin it out of flow + hidden here. */
#auth-modal { position: fixed; inset: 0; opacity: 0; visibility: hidden; }
#auth-modal.active { opacity: 1; visibility: visible; }
/* Same story for the beta-feedback dialog — keep it out of flow until the
   deferred CSS (which makes it position:fixed) lands. */
html.css-loading #beta-feedback-modal { display: none; }

/* ── Map shell ── */
#map-container {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 1;
  background: #b8d4e8;
  min-height: 100vh;
  min-height: 100dvh;
}
html[data-theme="night"] #map-container { background: #0c1219; }

#map-container .maplibregl-map { position: relative; z-index: 2; }

body:not(.globe-live) #map-container {
  contain: layout size;
  overflow: hidden;
}

body:not(.globe-live) #map-container .maplibregl-canvas-container,
body:not(.globe-live) #map-container .maplibregl-canvas {
  width: 100%;
  height: 100%;
  /* maplibre-gl.css (loaded with the map, late) sets position:absolute on the
     canvas; without it here the canvas is in flow until then and jumps out — the
     single biggest layout shift. Match it up-front. */
  position: absolute;
  top: 0;
  left: 0;
}

body.globe-live #map-container {
  contain: none;
  overflow: visible;
}

@media (min-width: 769px) and (hover: none) and (pointer: coarse) {
  #map-container {
    position: fixed;
    inset: 0;
    height: max(100vh, var(--map-h, 0px));
    min-height: 100vh;
  }
}

/* ── Boot veil (minimal — map shows through until globe-live) ── */
.globe-boot {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: max(18vh, 72px);
  pointer-events: none;
  background: transparent;
  transition: opacity 0.35s var(--ease-out);
}

body.globe-live .globe-boot {
  opacity: 0;
}

.globe-boot-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-2);
  opacity: 0;
  animation: globe-fade-in 0.4s var(--ease-out) 0.15s forwards;
}

.globe-boot-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c-accent, #FF5A5F);
  animation: globe-pulse 1.2s ease-in-out infinite;
}

@keyframes globe-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes globe-pulse {
  0%, 100% { opacity: 0.45; transform: scale(0.85); }
  50% { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .globe-boot-label { animation: none; opacity: 0.85; }
  .globe-boot-dot { animation: none; opacity: 0.8; }
}

/* City-entry loading veil */
.map-loading-veil {
  position: absolute;
  inset: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface, #fff);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.map-loading-veil[hidden] { display: none; }
.map-loading-veil.is-visible { opacity: 1; pointer-events: auto; }

.map-loading-spinner {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 3px solid var(--border);
  border-top-color: var(--c-accent, #ff385c);
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

body.map-interactions-locked #map { cursor: progress; }

/* Marketing hero (LCP first-run) */
.marketing-hero {
  position: fixed;
  inset: 0;
  z-index: 1400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(20px, env(safe-area-inset-top)) 20px max(20px, env(safe-area-inset-bottom));
  pointer-events: auto;
}
.marketing-hero-backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(14px) saturate(110%);
  -webkit-backdrop-filter: blur(14px) saturate(110%);
}
.marketing-hero-card {
  position: relative;
  width: min(480px, 100%);
  padding: 28px 24px 22px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-xl);
  text-align: center;
  opacity: 1;
}
.marketing-hero-wordmark {
  width: min(200px, 62vw);
  height: auto;
  margin: 0 auto 18px;
  display: block;
  object-fit: contain;
}
.marketing-hero-title {
  font-family: var(--font-display);
  font-size: clamp(1.45rem, 4.8vw, 1.85rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--text);
  margin: 0 0 10px;
}
.marketing-hero-sub {
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0 0 18px;
  max-width: 38ch;
  margin-left: auto;
  margin-right: auto;
}
.marketing-hero-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: stretch;
}
.marketing-hero-actions .ym-btn {
  width: 100%;
  justify-content: center;
  min-height: 44px;
}
.marketing-hero-skip {
  margin-top: 12px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-3);
  padding: 8px;
  border-radius: var(--radius-sm);
}
body.marketing-hero-open #left-panel,
body.marketing-hero-open #right-panel,
body.marketing-hero-open .map-controls-stack,
body.marketing-hero-open .auth-gate-banner,
body.marketing-hero-open #persona-invite,
body.marketing-hero-open .anon-welcome,
body.marketing-hero-open #dream-overlay {
  visibility: hidden;
  pointer-events: none;
}

/* Hero CTA button (critical first-run) */
.ym-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.84rem;
  line-height: 1;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  padding: 10px 16px;
  cursor: pointer;
  white-space: nowrap;
}
.ym-btn--primary {
  background: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-sm);
}

/* Hero title LCP — visible on first paint (skip deferred word animation) */
.marketing-hero-title.ym-text-animate .ym-text-word {
  opacity: 1;
  transform: none;
  animation: none;
}

/* Cold-start splash (main.js coldStartSplash) — CLS fix. The splash <div> is
   appended to <body> during main.js boot, but its styling used to live ONLY in
   the deferred onboarding-port.css. Until that stylesheet loaded (~2 rAF later)
   the splash rendered as an in-flow block that reflowed the tabbar/content, then
   snapped to position:fixed — a ~0.55 layout shift on every cold first visit.
   (E2E never saw it: the splash is skipped under navigator.webdriver.) Mirroring
   the layout-critical subset here keeps it out of flow from first paint. Full
   visual rules stay in styles/onboarding-port.css; these must match it. */
.ym-splash {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(120% 80% at 50% 0%, color-mix(in srgb, var(--accent) 22%, var(--bg)) 0%, var(--bg) 60%);
  transition: opacity 0.32s var(--ease-out, ease);
}
.ym-splash.is-in { opacity: 1; }
.ym-splash.is-out { opacity: 0; }
.ym-splash__glow { position: absolute; }
.ym-splash__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.ym-splash__mark img {
  width: 72px;
  height: 72px;
  border-radius: 22px;
  display: block;
}
.ym-splash__name { font-size: 1.5rem; font-weight: 800; letter-spacing: -0.02em; color: var(--text); }
.ym-splash__tag { font-size: 0.9rem; color: var(--text-2); }
.ym-splash__dots { display: flex; gap: 7px; }
.ym-splash__dots i { width: 7px; height: 7px; border-radius: 50%; }

/* Mobile bottom tabbar is static markup in index.html but its base rule
   (display:none; it only shows via a position:fixed media-query) lives in the
   deferred app.css. Without this, the <nav> flashes in-flow on cold start and
   reflows the content when app.css lands. Mirror its default hidden state so it
   stays out of flow until the deferred rule positions it (fixed → no shift). */
.ym-tabbar { display: none; }
