*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; overflow: hidden; }
html { -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; }
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  -webkit-font-smoothing: antialiased;
  touch-action: manipulation;
  background: #05070d;
  color: var(--color-text);
  transition: background 0.4s ease, color 0.4s ease;
}
/* ============== FIX IOS: TODOS LOS ELEMENTOS CLICKEABLES ============== */
button, .pill, .play-btn, .progress-wrap,
.mode-toggle-thumb, .mode-toggle-option,
.sheet-handle, .suggest-chip,
.leaflet-marker-icon, .custom-pin, [role="button"], .brand {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
button { -webkit-appearance: none; appearance: none; border: none; background: none; font: inherit; color: inherit; }
/* ============== / FIX IOS ============== */

:root {
  --color-primary: #F59E0B;
  --color-primary-soft: #FCD34D;
  --color-bg: #0F172A;
  --color-surface: #1E293B;
  --color-surface-2: #111C2E;
  --color-text: #F1F5F9;
  --color-text-soft: #94A3B8;
  --color-border: #334155;
  --color-accent: #FBBF24;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.25);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.35), 0 12px 32px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 -8px 32px rgba(0, 0, 0, 0.45), 0 -2px 8px rgba(0, 0, 0, 0.3);
  --radius-lg: 20px;
  --radius-xl: 26px;
  --radius-pill: 999px;
  --map-filter: none;
}
:root[data-mode="kids"] {
  --color-primary: #6366F1;
  --color-primary-soft: #A5B4FC;
  --color-bg: #12102b;
  --color-surface: #1E1B4B;
  --color-surface-2: #14123a;
  --color-text: #EEF2FF;
  --color-text-soft: #A5A6D6;
  --color-border: #312E81;
  --color-accent: #818CF8;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 10px rgba(99, 102, 241, 0.12);
  --shadow-md: 0 6px 16px rgba(99, 102, 241, 0.18), 0 14px 36px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 -10px 32px rgba(0, 0, 0, 0.45), 0 -2px 8px rgba(99, 102, 241, 0.15);
}

.app {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  position: relative;
  overflow: hidden;
  background: var(--color-bg);
}

/* ---------- PANTALLA DE BIENVENIDA ----------
 * Tokens propios (independientes de --color-*): esta pantalla es siempre
 * clara con foto real de fondo, tanto si el resto de la app está en modo
 * día como en modo niño. --color-primary sí se reutiliza a propósito para
 * los acentos (icono activo, botón "Adultos/Niños"), porque ya cambia solo
 * entre naranja/índigo según el modo elegido — un detalle bonito que vale
 * la pena conservar. */
.onboarding {
  --ob-text: #0F172A;
  --ob-text-soft: #64748B;
  --ob-card-bg: #FFFFFF;
  --ob-card-bg-soft: #F8FAFC;
  --ob-border: #E5E9F0;
  position: fixed;
  inset: 0;
  z-index: 5000;
  overflow: hidden;
  background: #0B1220;
  color: var(--ob-text);
}
.onboarding[hidden] { display: none; }

/* Carrusel de fotos reales de las 6 ciudades: 6 capas apiladas, cada una
   se desvanece dentro/fuera con un retraso escalonado (36s / 6 = 6s cada
   una) para que el fondo cambie de ciudad sin cortes ni parpadeos. */
.onboarding-hero {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.onboarding-hero-img {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 42%;
  opacity: 0;
  animation: onboardingHeroFade 36s ease-in-out infinite;
}
.onboarding-hero-img.-toledo {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Toledo_Skyline_Panorama%2C_Spain_-_Dec_2006.jpg/1280px-Toledo_Skyline_Panorama%2C_Spain_-_Dec_2006.jpg');
  animation-delay: 0s;
}
.onboarding-hero-img.-madrid {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Gran_V%C3%ADa_%28Madrid%29_45.jpg/1280px-Gran_V%C3%ADa_%28Madrid%29_45.jpg');
  animation-delay: 6s;
}
.onboarding-hero-img.-peniscola {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Castillo_de_Pe%C3%B1%C3%ADscola_desde_el_mar.jpg/1280px-Castillo_de_Pe%C3%B1%C3%ADscola_desde_el_mar.jpg');
  animation-delay: 12s;
}
.onboarding-hero-img.-cdmx {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Palaco_de_Bellas_Artes%2C_Mexico_City%2C_from_across_Eje_Central_Lazaro_Cardenas_in_evening.jpg/1280px-Palaco_de_Bellas_Artes%2C_Mexico_City%2C_from_across_Eje_Central_Lazaro_Cardenas_in_evening.jpg');
  animation-delay: 18s;
}
.onboarding-hero-img.-berlin {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Brandenburger_Tor%2C_Berlin%2C_Germany.jpg/1280px-Brandenburger_Tor%2C_Berlin%2C_Germany.jpg');
  animation-delay: 24s;
}
.onboarding-hero-img.-roma {
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/Colosseo_2020.jpg/1280px-Colosseo_2020.jpg');
  animation-delay: 30s;
}
@keyframes onboardingHeroFade {
  0%, 1.25% { opacity: 0; }
  5.42%, 14.58% { opacity: 1; }
  18.33%, 100% { opacity: 0; }
}
/* Oscurece un poco la parte de arriba (donde va la marca y el titular en
   blanco) sin necesidad de que el degradado llegue a blanco sólido: la
   tarjeta de abajo lleva su propio fondo opaco, así que no depende de
   que este velo "acierte" el punto exacto de la pantalla. */
.onboarding-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(5, 10, 20, 0.35) 0%, rgba(5, 10, 20, 0.08) 45%, rgba(5, 10, 20, 0.25) 100%);
}

.onboarding-scroll {
  position: relative;
  z-index: 1;
  height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: max(22px, env(safe-area-inset-top)) 20px calc(70px + env(safe-area-inset-bottom));
}
.onboarding-version {
  position: absolute;
  right: 14px;
  bottom: calc(12px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
  z-index: 2;
  padding: 10px;
  margin: -10px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  opacity: 0.7;
  cursor: pointer;
}
.onboarding-reset {
  position: absolute;
  left: 14px;
  bottom: calc(12px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
  z-index: 2;
  padding: 10px;
  margin: -10px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  opacity: 0.7;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.onboarding-reset:active { opacity: 0.9; }
/* El desplegable de ciudad (position:absolute, ver .onboarding-city-list)
   puede crecer bastante con varios países/ciudades y llegar a solaparse con
   este pie -- subirle el z-index no basta porque el desplegable y el pie
   viven en contextos de apilamiento distintos (cada contenedor intermedio
   tiene su propio z-index) y encima el hueco alrededor del texto del
   desplegable es transparente, así que el pie se seguía viendo a través.
   Más simple y fiable: ocultarlo mientras el desplegable está abierto (aria-
   expanded lo pone wireOnboarding en app.js), que es el único momento en
   que pueden coincidir. */
#onboarding:has(#obPickCity[aria-expanded="true"]) .onboarding-version,
#onboarding:has(#obPickCity[aria-expanded="true"]) .onboarding-reset,
#onboarding:has(#obPickCity[aria-expanded="true"]) .onboarding-legal {
  opacity: 0;
  pointer-events: none;
}
.onboarding-legal {
  position: absolute;
  left: 50%;
  bottom: calc(12px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)));
  transform: translateX(-50%);
  z-index: 2;
  padding: 10px;
  margin: -10px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  opacity: 0.7;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.onboarding-legal:active { opacity: 0.9; }

/* ---------- PANTALLA DE ACCESO (LICENCIA) ---------- */
.license-gate {
  position: fixed;
  inset: 0;
  z-index: 7000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--color-bg, #0F172A);
}
.license-gate[hidden] { display: none; }
.license-gate-card {
  width: min(92vw, 360px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 30px 24px 24px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.license-gate-icon { font-size: 32px; line-height: 1; margin-bottom: 10px; }
.license-gate-title { font-size: 18px; font-weight: 800; margin: 0 0 8px; color: var(--color-text); }
.license-gate-text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-text-soft);
  margin: 0 0 18px;
}
.license-gate-input {
  width: 100%;
  height: 44px;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  padding: 0 14px;
  font-size: 14px;
  margin-bottom: 12px;
}
.license-gate-input:focus { outline: 2px solid var(--color-accent, #F5A623); outline-offset: 1px; }
.license-gate-submit {
  width: 100%;
  height: 44px;
  border-radius: 12px;
  border: 0;
  background: var(--color-accent, #F5A623);
  color: #0F172A;
  font-weight: 800;
  font-size: 14px;
  cursor: pointer;
}
.license-gate-submit:active { transform: scale(0.98); }
.license-gate-submit:disabled { opacity: 0.7; cursor: default; }
.license-gate-error {
  margin: 14px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: #F87171;
}

/* ---------- CONFIRMACIÓN DE REINICIO ---------- */
.reset-confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 6000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.reset-confirm-modal.-open {
  opacity: 1;
  visibility: visible;
}
.reset-confirm-card {
  width: min(92vw, 360px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 26px 22px 20px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.reset-confirm-icon { font-size: 30px; line-height: 1; margin-bottom: 8px; }
.reset-confirm-title { font-size: 16px; font-weight: 800; margin: 0 0 10px; }
.reset-confirm-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--color-text-soft);
  margin: 0 0 20px;
}
.reset-confirm-actions {
  display: flex;
  gap: 10px;
}
.reset-confirm-cancel,
.reset-confirm-ok {
  flex: 1;
  height: 42px;
  border-radius: 12px;
  border: 0;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.reset-confirm-cancel {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}
.reset-confirm-ok {
  background: #DC2626;
  color: #fff;
}
.reset-confirm-ok:active,
.reset-confirm-cancel:active { transform: scale(0.97); }

/* Mismo patrón visual que .reset-confirm-*, pero con el color de marca en
   vez del rojo de "acción destructiva": abrir Maps no borra nada. */
.directions-confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 6000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.directions-confirm-modal.-open {
  opacity: 1;
  visibility: visible;
}
.directions-confirm-card {
  width: min(92vw, 360px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 26px 22px 20px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.directions-confirm-icon { font-size: 30px; line-height: 1; margin-bottom: 8px; }
.directions-confirm-title { font-size: 16px; font-weight: 800; margin: 0 0 10px; }
.directions-confirm-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--color-text-soft);
  margin: 0 0 20px;
}
.directions-confirm-actions {
  display: flex;
  gap: 10px;
}
.directions-confirm-cancel,
.directions-confirm-ok {
  flex: 1;
  height: 42px;
  border-radius: 12px;
  border: 0;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.directions-confirm-cancel {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}
.directions-confirm-ok {
  background: var(--color-accent, #F5A623);
  color: #0F172A;
}
.directions-confirm-ok:active,
.directions-confirm-cancel:active { transform: scale(0.97); }

/* Mismo patrón visual que .directions-confirm-*, pero para la bienvenida
   corta a cada ciudad (una vez por ciudad, la primera vez que se entra).
   Card algo más ancha/alta que la de Maps porque el texto es un párrafo
   entero, no una frase. */
/* EXPERIMENTO (rama experimento-diseno-editorial): fondo con la foto real
   de la ciudad (--city-intro-photo, fijada en maybeShowCityIntro en
   app.js) en vez del negro liso de siempre -- un degradado oscuro encima
   sigue dejando la tarjeta blanca y su texto perfectamente legibles. Sin
   foto disponible (--city-intro-photo sin fijar), el degradado solo cae
   sobre el mismo negro de respaldo de antes. */
.city-intro-modal {
  position: fixed;
  inset: 0;
  z-index: 6100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background:
    linear-gradient(rgba(5, 7, 13, 0.55), rgba(5, 7, 13, 0.85)),
    var(--city-intro-photo, none) center / cover no-repeat,
    #05070d;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease;
}
.city-intro-modal.-open {
  opacity: 1;
  visibility: visible;
}
/* EXPERIMENTO (rama experimento-diseno-editorial): la tarjeta (texto +
   botones) se queda invisible ~2s tras abrirse el modal -- ver el
   setTimeout en maybeShowCityIntro, app.js -- para dar tiempo a ver la
   foto de la ciudad sola antes de que aparezca la intro encima.
   pointer-events:none mientras tanto, para que no se pueda tocar "Saltar
   intro" ni "Empezar a explorar" antes de que se vean de verdad. */
.city-intro-card {
  width: min(92vw, 400px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 22px;
  padding: 16px 22px 22px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity 0.45s ease, transform 0.45s ease;
}
.city-intro-modal.-card-visible .city-intro-card {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.city-intro-skip {
  display: block;
  margin: 0 0 4px auto;
  border: 0;
  background: transparent;
  color: var(--color-text-soft);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 2px 0;
}
.city-intro-icon {
  display: block;
  margin: 0 auto 10px;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 4px;
}
.city-intro-icon img {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  display: block;
}
.city-intro-icon.-tap { animation: cityIntroTapPulse 1.1s ease-in-out infinite; }
@keyframes cityIntroTapPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.14); }
}
.city-intro-hint {
  font-size: 12px;
  font-weight: 600;
  color: #F5B942;
  text-align: center;
  margin: -4px 0 12px;
}
.city-intro-text {
  font-size: 14px;
  line-height: 1.55;
  color: var(--color-text);
  margin: 0 0 20px;
  text-align: left;
}
.city-intro-start {
  width: 100%;
  height: 46px;
  border-radius: var(--radius-pill);
  border: 0;
  background: #F5B942;
  color: #1a1300;
  font-weight: 800;
  font-size: 14px;
  cursor: pointer;
}
.city-intro-start:active { transform: scale(0.97); }

/* Registro (oculto) de fotos escaneadas sin match curado: se abre tocando
   varias veces seguidas la etiqueta "Beta VXX" de la portada (ver
   wireScanLogTrigger en app.js). Pensado solo para el propio desarrollador,
   no es una función de cara al usuario final. */
.scan-log-modal {
  position: fixed;
  inset: 0;
  z-index: 6100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.scan-log-modal.-open {
  opacity: 1;
  visibility: visible;
}
.scan-log-card {
  width: min(92vw, 420px);
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 18px 18px 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.scan-log-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.scan-log-title { font-size: 15px; font-weight: 800; margin: 0; }
.scan-log-close {
  width: 30px;
  height: 30px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  font-size: 13px;
}
.scan-log-hint {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--color-text-soft);
  margin: 6px 0 12px;
}
.scan-log-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  min-height: 40px;
}
.scan-log-empty {
  font-size: 12px;
  color: var(--color-text-soft);
  text-align: center;
  padding: 20px 0;
}
.scan-log-item {
  display: flex;
  gap: 10px;
  align-items: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 8px;
}
.scan-log-item img {
  width: 48px;
  height: 48px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--color-border);
}
.scan-log-item-text { min-width: 0; flex: 1; }
.scan-log-item-name {
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.scan-log-item-meta {
  font-size: 10.5px;
  color: var(--color-text-soft);
}
.scan-log-actions {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}
.scan-log-export,
.scan-log-clear {
  flex: 1;
  height: 40px;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  font-weight: 700;
  font-size: 12.5px;
  cursor: pointer;
  background: var(--color-surface);
  color: var(--color-text);
}
.scan-log-clear { color: #DC2626; }
.scan-log-export:active,
.scan-log-clear:active { transform: scale(0.97); }
.onboarding-brand {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 24px;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
  flex-shrink: 0;
}
.onboarding-brand-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  flex-shrink: 0;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
}
.onboarding-hero-copy {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  /* Empuja el titular + tarjeta hacia la mitad inferior, dejando que la
     foto respire arriba (como una portada), sin depender de alturas fijas. */
  margin-top: auto;
  margin-bottom: 18px;
  color: #fff;
}
.onboarding-hero-copy h1 {
  font-size: 30px;
  line-height: 1.12;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-shadow: 0 4px 18px rgba(0, 0, 0, 0.55);
  margin-bottom: 8px;
}
.onboarding-hero-copy p {
  font-size: 14px;
  line-height: 1.4;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
  max-width: 34ch;
}

.onboarding-card {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  background: var(--ob-card-bg);
  border-radius: 26px;
  padding: 22px 20px;
  box-shadow: 0 -8px 40px rgba(15, 23, 42, 0.18), 0 20px 50px rgba(15, 23, 42, 0.35);
}
/* Estado de carga: se activa mientras se descarga data/cities/<id>.js
   la primera vez que se elige una ciudad (ver loadCityData en app.js). */
.onboarding-card.-loading > * { opacity: 0.25; pointer-events: none; }
.onboarding-card.-loading::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: 26px;
  background: rgba(255, 255, 255, 0.6);
}
.onboarding-card.-loading::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--color-primary) 20%, transparent);
  border-top-color: var(--color-primary, #F59E0B);
  animation: onboardingSpin 0.8s linear infinite;
}
@keyframes onboardingSpin { to { transform: rotate(360deg); } }
.onboarding-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.onboarding-section h2 {
  font-size: 13px;
  font-weight: 700;
  color: var(--ob-text-soft);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.onboarding-btn {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 13px 14px;
  border-radius: 16px;
  border: 1px solid var(--ob-border);
  background: var(--ob-card-bg-soft);
  color: var(--ob-text);
  text-align: left;
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.2s ease, background 0.2s ease;
}
.onboarding-btn:hover { border-color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 8%, var(--ob-card-bg-soft)); }
.onboarding-btn:active { transform: scale(0.98); }
.onboarding-btn-ico { flex-shrink: 0; display: grid; place-items: center; }
.onboarding-btn-ico svg { width: 22px; height: 22px; }
.onboarding-btn-ico.-location,
.onboarding-btn-ico.-map {
  width: 38px; height: 38px; border-radius: 50%;
  background: color-mix(in srgb, var(--color-primary) 16%, transparent);
  color: var(--color-primary);
}
.onboarding-btn strong { display: block; font-size: 14.5px; }
.onboarding-btn small { display: block; font-size: 12px; color: var(--ob-text-soft); margin-top: 2px; }
.onboarding-btn.-mode, .onboarding-btn.-lang { flex-direction: column; text-align: center; gap: 6px; }
.onboarding-btn.-mode[data-active="true"], .onboarding-btn.-lang[data-active="true"] { border-color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 12%, var(--ob-card-bg-soft)); }
.onboarding-mode-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.onboarding-dropdown { position: relative; }
.onboarding-dropdown-caret {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-left: auto;
  color: var(--ob-text-soft);
  transition: transform 0.2s ease;
}
.onboarding-btn[aria-expanded="true"] .onboarding-dropdown-caret { transform: rotate(180deg); }

.onboarding-city-list {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  z-index: 20;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  padding: 8px;
  border-radius: 12px;
  border: 1px solid var(--ob-border);
  background: var(--ob-card-bg);
  box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
  /* Resta el hueco del gesto de inicio de Android y un margen extra: sin
     esto, con varios países/ciudades la lista podía asomar justo detrás de
     esa barra del sistema. var(--safe-area-inset-bottom) (lo pone el propio
     plugin nativo en <html>, ver SystemBars) en vez de env(...): este
     último no devuelve nada dentro del WebView de Capacitor aquí, así que
     un intento anterior con env() no tuvo ningún efecto real. */
  max-height: calc(45vh - var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)) - 12px);
  overflow-y: auto;
}
.onboarding-city-list[hidden] { display: none; }
/* Barrita con degradado + flecha pegada abajo del desplegable (ver
   finalizeCityList en app.js): solo se hace visible (-visible) cuando el
   contenido de verdad no cabe entero en el alto disponible, para avisar de
   que hay más opciones haciendo scroll. sticky (no absolute) porque vive
   dentro del propio contenedor con scroll (.onboarding-city-list) y tiene
   que quedarse pegada abajo mientras se hace scroll, sin taparlo cuando ya
   se ha llegado al final. */
.onboarding-city-scroll-hint {
  grid-column: 1 / -1;
  position: sticky;
  bottom: -10px;
  margin: -8px -10px -10px;
  padding-top: 16px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 26px;
  color: var(--ob-text-soft);
  background: linear-gradient(to bottom, transparent, var(--ob-card-bg) 65%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.onboarding-city-scroll-hint.-visible { opacity: 1; }
/* Recuadros en vez de una lista de filas: cada opción (continente, país o
   ciudad) es una caja del mismo tamaño, dos por fila -- se ve igual en los
   tres niveles del selector porque los tres usan makeRow/esta misma clase.
   Grid (no flex) a propósito: con flex-grow, la última caja sola en una fila
   impar (p.ej. "Vaticano") se estiraba a todo el ancho por no tener con
   quién repartir el espacio sobrante -- en grid cada caja ocupa su propia
   celda de tamaño fijo, así que la última simplemente deja hueco vacío al
   lado en vez de crecer. */
.onboarding-city-option {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 38px;
  padding: 7px 6px;
  border: 1px solid var(--ob-border);
  border-radius: 9px;
  background: var(--ob-card-bg-soft);
  color: var(--ob-text);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.onboarding-city-option small { display: block; margin-top: 2px; font-size: 11.5px; font-weight: 400; color: var(--ob-text-soft); }
.onboarding-city-option:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.onboarding-city-back {
  display: flex;
  grid-column: 1 / -1;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 8px 10px;
  margin-bottom: 2px;
  border: none;
  border-bottom: 1px solid var(--ob-border);
  border-radius: 0;
  background: transparent;
  color: var(--ob-text-soft);
  font-size: 12px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.onboarding-city-back:hover { color: var(--color-primary); }

/* ---------- HEADER (flotante sobre el mapa) ---------- */
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   el degradado + desenfoque de fondo tenía sentido cuando aquí abajo vivía
   la barra ancha de píldoras (necesitaba contraste contra el mapa en todo
   ese ancho); ahora que el propio header-bottom es una cajita compacta con
   su propio fondo, esa franja difuminada de ancho completo se queda vacía
   y de más — se ve como una mancha sin contenido encima. Se quita, y el
   título/subtítulo llevan su propia text-shadow (ver .brand-mark/.brand-sub)
   para seguir leyéndose bien sobre el mapa sin necesitar el difuminado.
   BORRAR este comentario si se retira el experimento. */
.header {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 2100;
  /* env(safe-area-inset-top): en iPhone la app ocupa toda la pantalla
     (viewport-fit=cover) y sin esto la barra de estado (hora, batería)
     queda encima del título. En el WebView de Android vale 0, así que
     allí no cambia nada. */
  padding: calc(16px + env(safe-area-inset-top, 0px)) 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* BUG REAL (reportado: tocar un pin del mapa a veces no responde): .header
     ocupa TODO el ancho de la pantalla y tanto de alto como sumen sus hijos
     -- con el menú Inicio/Filtros/Capas ahora más alto que los 2 iconos de
     antes (y más aún con un panel abierto), esa caja invisible llegaba a
     tapar una franja bastante grande del mapa por debajo de donde se ve
     contenido real, y ahí NINGÚN pin respondía al toque porque el clic lo
     interceptaba .header antes de llegar al mapa. pointer-events:none aquí
     (con :auto de vuelta en .header-top/.header-bottom, sus únicos hijos)
     hace que solo el contenido visible del header sea clicable, y el resto
     de su caja deje pasar el toque al mapa de debajo. BORRAR este comentario
     si se retira el experimento (el fix se queda si el cambio se mantiene). */
  pointer-events: none;
}
.header-top, .header-bottom {
  pointer-events: auto;
}
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   el degradado + desenfoque ya no vive en TODO .header (se quitó de ahí
   porque se extendía de más, tapando un tramo de mapa vacío donde antes
   estaba la barra ancha de píldoras) — se queda solo detrás de esta fila
   (título + selector Adultos/Niños), que sigue necesitando contraste
   contra el mapa. .header-bottom (casita + flechita) ya tiene su propio
   fondo aparte, no hace falta que comparta este. El margen/padding
   negativo-positivo hace que el fondo llegue a los bordes de la pantalla
   igual que antes, aunque .header-top ya no ocupe todo el header. BORRAR
   este comentario si se retira el experimento. */
.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  row-gap: 8px;
  gap: 12px;
  margin: calc(-16px - env(safe-area-inset-top, 0px)) -16px 0;
  padding: calc(16px + env(safe-area-inset-top, 0px)) 16px 18px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--color-bg) 94%, transparent) 0%,
    color-mix(in srgb, var(--color-bg) 55%, transparent) 70%,
    transparent 100%);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.brand-icon {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  font-size: 17px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-primary) 24%, var(--color-surface));
  box-shadow: 0 2px 8px color-mix(in srgb, var(--color-primary) 35%, transparent);
}
/* En modo adultos no hay mascota persistente (Billy) para repetir el
   tutorial tocándola: el logo hace de "botón de ayuda" (ver wireTutorial
   en app.js). En modo niño sigue siendo solo decorativo. */
[data-mode="adult"] .brand-icon { cursor: pointer; }
[data-mode="adult"] .brand-icon:active { transform: scale(0.92); }
.brand-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.brand-mark {
  font-weight: 800;
  font-size: 17px;
  line-height: 1.1;
  color: #FFFFFF;
  letter-spacing: -0.3px;
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  /* Sustituye al difuminado de fondo que llevaba .header antes — sin él,
     el título necesita su propio contraste contra el mapa. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 1px 10px rgba(0, 0, 0, 0.35);
}
.brand-badge {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-primary) 22%, transparent);
  color: var(--color-primary-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 120px;
  flex-shrink: 1;
}
.points-badge {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--explorer-color, #F5B942) 22%, transparent);
  color: var(--explorer-color, #F5B942);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ---------- QUIZ (modo niño): pregunta con opciones tocables ---------- */
.quiz-question {
  font-weight: 700;
  margin-bottom: 10px;
  color: var(--color-text);
}
.quiz-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.quiz-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  border-radius: var(--radius-md, 12px);
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.quiz-option:hover:not(:disabled) { border-color: var(--color-primary); }
.quiz-option:active:not(:disabled) { transform: scale(0.98); }
.quiz-option:disabled { cursor: default; opacity: 0.75; }
.quiz-option.-correct {
  background: color-mix(in srgb, #22C55E 25%, transparent);
  border-color: #22C55E;
  opacity: 1;
}
.quiz-option.-incorrect {
  background: color-mix(in srgb, #EF4444 20%, transparent);
  border-color: #EF4444;
  opacity: 1;
}
.brand-sub {
  font-size: 11px;
  font-weight: 500;
  color: var(--color-text-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* ---------- EXPLORADOR: mascota (modo niño), avatar propio y visible
   junto al selector de modo; el nivel se funde con el badge de puntos. ---------- */
.explorer-avatar-wrap {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  cursor: pointer; /* tocar a Billy repite el tutorial (ver wireTutorial) */
}
.explorer-avatar-wrap[hidden] { display: none; }
.explorer-avatar-wrap:active { transform: scale(0.92); }
.explorer-avatar {
  width: 48px;
  height: 48px;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.45));
}

/* ---------- BOTON "VER TUTORIAL" (repetirlo a voluntad desde el header) ---------- */
.tutorial-help-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 0;
  background: color-mix(in srgb, #fff 22%, transparent);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.15s ease;
  padding: 0;
}
.tutorial-help-btn:active { transform: scale(0.9); }
.tutorial-help-btn:hover { background: color-mix(in srgb, #fff 32%, transparent); }

/* ---------- COFRE DEL TESORO (modo niño) ---------- */
.reward-chest-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 0;
  background: color-mix(in srgb, #F5B942 20%, transparent);
  font-size: 17px;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.15s ease;
  padding: 0;
}
.reward-chest-btn[hidden] { display: none; }
.reward-chest-btn:active { transform: scale(0.9); }
#rewardChestBtnIcon { width: 26px; height: 26px; }
#rewardChestTitleIcon { width: 22px; height: 22px; vertical-align: -5px; }
.reward-chest-modal {
  position: fixed;
  inset: 0;
  z-index: 6100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.reward-chest-modal.-open {
  opacity: 1;
  visibility: visible;
}
.reward-chest-card {
  width: min(92vw, 460px);
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 18px 18px 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.reward-chest-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.reward-chest-title { font-size: 15px; font-weight: 800; margin: 0; }
.reward-chest-close {
  width: 30px;
  height: 30px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  font-size: 13px;
}
.reward-chest-hint {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--color-text-soft);
  margin: 6px 0 14px;
}
.reward-item-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 10px;
  overflow-y: auto;
}
.reward-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 6px 8px;
  border-radius: 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  text-align: center;
}
.reward-item-icon {
  width: 56px;
  height: 56px;
}
.reward-item-label {
  font-size: 10px;
  line-height: 1.25;
  color: var(--color-text-soft);
}
.reward-item.-claimed {
  border-color: color-mix(in srgb, #F5B942 45%, var(--color-border));
  background: color-mix(in srgb, #F5B942 10%, var(--color-surface));
}
.reward-item.-claimed .reward-item-label {
  color: var(--color-text);
  font-weight: 700;
}
.reward-item.-locked .reward-item-icon {
  filter: grayscale(1) brightness(0.65);
  opacity: 0.6;
}
.reward-item.-claimable {
  border-color: color-mix(in srgb, #22C55E 55%, var(--color-border));
  background: color-mix(in srgb, #22C55E 12%, var(--color-surface));
}
.reward-item.-claimable .reward-item-label {
  color: var(--color-text);
  font-weight: 700;
}
.reward-item-claim-btn {
  margin-top: 2px;
  padding: 4px 8px;
  border-radius: var(--radius-pill);
  border: 0;
  background: #22C55E;
  color: #05210F;
  font-size: 10px;
  font-weight: 800;
  cursor: pointer;
}
.reward-item-claim-btn:active { transform: scale(0.94); }

.city-badge-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--color-border);
}
.city-badge-row:empty { display: none; }
.city-badge-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
  width: 62px;
  text-align: center;
}
.city-badge-item-icon { width: 48px; height: 48px; }
.city-badge-item-label {
  font-size: 9px;
  line-height: 1.2;
  color: var(--color-text-soft);
}
.city-badge-item.-earned .city-badge-item-label { color: var(--color-text); font-weight: 700; }
.city-badge-item.-locked .city-badge-item-icon {
  filter: grayscale(1) brightness(0.65);
  opacity: 0.55;
}
.city-badge-item { cursor: pointer; }
.city-badge-item:active { transform: scale(0.93); }

.badge-zoom-modal {
  position: fixed;
  inset: 0;
  /* Por encima de .reward-chest-modal (6100, ver arriba): se abre DESDE
     dentro de la mochila, así que tiene que quedar delante de ella o
     parece que no pasa nada al tocar la medalla (bug real: quedaba a
     5600, detrás de la mochila, invisible hasta cerrarla). */
  z-index: 6150;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 32px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.badge-zoom-modal.-open { opacity: 1; visibility: visible; }
.badge-zoom-canvas {
  width: min(70vw, 300px);
  height: min(70vw, 300px);
  filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.5));
}
.badge-zoom-canvas.-locked { filter: grayscale(1) brightness(0.65) drop-shadow(0 12px 30px rgba(0, 0, 0, 0.5)); opacity: 0.6; }
.badge-zoom-label {
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  text-align: center;
  margin: 0;
}
.badge-zoom-close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 16px;
}

/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo).
   Cuarta vuelta: Inicio/Filtros/Capas son tres filas iguales, siempre
   visibles, con icono + texto — ver el comentario del bloque
   "FILTROS + CAPAS" más abajo para el resto de este rediseño. Antes
   .change-city-btn era un círculo de solo icono (antes de eso, gris
   apagado); ahora comparte estilo con los otros dos botones de fila.
   BORRAR este comentario si se retira el experimento. */
.menu-row-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 36px;
  padding: 0 10px;
  border-radius: 12px;
  border: 0;
  background: transparent;
  color: var(--color-text);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  position: relative;
  transition: transform 0.15s ease, color 0.2s ease, background 0.2s ease;
}
.menu-row-btn svg { width: 17px; height: 17px; flex-shrink: 0; }
.menu-row-btn img { width: 17px; height: 17px; object-fit: contain; flex-shrink: 0; }
.menu-row-btn:active { transform: scale(0.97); }
.menu-row-btn:hover { background: color-mix(in srgb, var(--color-primary) 12%, transparent); }
.menu-row-btn[aria-expanded="true"] { color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 14%, transparent); }
/* Puntito que avisa de que hay un filtro activo distinto de "Todos". */
.menu-row-btn.-active::after {
  content: '';
  position: absolute;
  top: 6px;
  right: 8px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-primary);
  border: 1.5px solid var(--color-surface);
}

.mode-toggle {
  display: inline-flex;
  background: color-mix(in srgb, var(--color-surface) 92%, black);
  border-radius: var(--radius-pill);
  padding: 4px;
  position: relative;
  border: 1px solid var(--color-border);
  min-width: 156px;
  height: 38px;
  flex-shrink: 0;
  box-shadow: var(--shadow-sm);
  transition: background 0.3s ease, border-color 0.3s ease;
}
.mode-toggle-option {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-soft);
  background: transparent;
  border: 0;
  cursor: pointer;
  border-radius: var(--radius-pill);
  position: relative;
  z-index: 2;
  padding: 0 6px;
  transition: color 0.3s ease;
  min-height: 28px;
}
.mode-toggle-option .mode-ico { font-size: 14px; }
.mode-toggle-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  width: calc(50% - 4px);
  background: var(--color-primary);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
  z-index: 1;
}
:root[data-mode="kids"] .mode-toggle-thumb { transform: translateX(100%); }
.mode-toggle-option[data-active="true"] { color: #0B1120; }

/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   antes esta barra era horizontal y ancha (la fila entera de píldoras);
   luego pasó a ser una columna compacta de solo 2 iconos circulares
   (casita + flechita del menú). Cuarta vuelta: ahora son 3 filas de
   icono+texto SIEMPRE visibles (Inicio/Filtros/Capas, ver .menu-row-btn),
   así que la columna se estira (align-items:stretch) para que las 3 filas
   compartan el mismo ancho, en vez de centrarse como los iconos sueltos de
   antes. Sexta vuelta: vuelve a ser fit-content (se ajusta a la palabra
   más larga de Inicio/Filtros/Capas) en vez de un ancho fijo -- el ancho
   fijo evitaba que la columna se "agrandara" al elegir una ruta larga,
   pero a cambio la dejaba más ancha de lo necesario en reposo. El
   problema de fondo (un panel CERRADO seguía aportando su ancho al
   fit-content, por ejemplo la píldora "Rutas recomendadas" mostrando el
   nombre de la ruta activa) se arregla ahora en el propio
   #filtersMenuSlot/#layersMenuSlot: cuando un slot está cerrado también
   colapsa su ANCHO a 0, no solo el alto, así que deja de contar para el
   fit-content -- ver .app-menu-slot más abajo. Así la columna se ajusta a
   las 3 palabras en reposo, y crece de verdad (con la nueva fila estirada
   por align-items:stretch) SOLO mientras un panel esté realmente abierto.
   BORRAR este comentario si se retira el experimento (la forma compacta
   se queda si el cambio se mantiene). */
.header-bottom {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  width: fit-content;
  position: relative;
  padding: 6px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
  box-shadow: var(--shadow-sm);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.dropdown-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
  border-radius: 12px;
  background: none;
  border: none;
  color: var(--color-text);
  font-family: inherit;
  /* Mismo tamaño/peso que .menu-row-btn (Inicio/Filtros/Capas) -- antes
     era 14px/600, más grande y más fino que las filas de arriba, y se
     notaba como si fuera una fuente distinta al ver todo el bloque junto. */
  font-size: 12.5px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.dropdown-option:hover, .dropdown-option:active {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}
.dropdown-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   ya no existe .route-picker como dropdown flotante propio (se solapaba
   con el menú Filtros/Capas) — ahora #routePicker es un simple contenedor
   dentro del nivel #appMenuRoutes, con el mismo look que .dropdown-option
   por CSS heredado. BORRAR este comentario si se retira el experimento. */

/* ---------- MENÚ: FILTROS + CAPAS ---------- */
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo).
   Historial de este menú: píldoras siempre visibles → dos botones de texto
   en línea → un solo icono que escondía Filtros/Capas detrás de un nivel
   intermedio → Filtros y Capas como filas siempre visibles con un panel
   flotante (position:absolute) compartido debajo de TODA la columna →
   ahora (quinta vuelta) cada fila tiene su propio "slot" de panel EN EL
   FLUJO normal, justo debajo de sí misma (ver #filtersMenuSlot/
   #layersMenuSlot en index.html): abrir Filtros empuja la fila "Capas"
   hacia abajo en vez de superponer un panel flotante encima de ella, así
   que todo el bloque (Inicio/Filtros/[panel]/Capas/[panel]) se lee como
   una sola columna continua. BORRAR este bloque si se retira el
   experimento (el propio rediseño se queda si el cambio se mantiene). */
/* CERRADO: ancho 0 además de alto 0. .header-bottom es fit-content (se
   ajusta a la palabra más larga de Inicio/Filtros/Capas), y ese cálculo
   mira el ancho INTRÍNSECO de todos los hijos en flujo, incluido un slot
   con grid-template-rows:0fr -- colapsar solo el alto no basta, porque un
   panel cerrado (ej.: la píldora "Rutas recomendadas" mostrando el nombre
   de la ruta activa) seguiría "queriendo" su ancho de contenido y
   ensanchando la columna aunque no se vea. Con width:0 + overflow:hidden
   en el propio slot, un panel cerrado no cuenta para nada; al abrirse
   (width:auto) vuelve a aportar su ancho real y la columna crece con él. */
.app-menu-slot {
  display: grid;
  grid-template-rows: 0fr;
  width: 0;
  overflow: hidden;
  transition: grid-template-rows 0.28s cubic-bezier(.4, 0, .2, 1);
}
.app-menu-slot.-open {
  grid-template-rows: 1fr;
  width: auto;
}
/* El grid-item necesita overflow:hidden + min-height:0 para poder
   encogerse a 0 de verdad -- sin min-height:0, el alto mínimo por defecto
   de un item de grid es "auto" (el alto de su contenido), y grid-rows:0fr
   no lograría colapsarlo. El padding vive en .app-menu-level (un nivel más
   adentro) y no en este contenedor, precisamente para que ese padding no
   cuente como "contenido mínimo" y deje un hueco residual visible cuando
   el slot está cerrado. */
.app-menu-slot-inner {
  overflow: hidden;
  min-height: 0;
  border-radius: 14px;
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--color-surface) 90%, var(--color-primary) 10%) 0%,
    var(--color-surface) 70%);
  opacity: 0;
  /* Misma duración que grid-template-rows en .app-menu-slot (0.28s) para
     que, al cerrar, el fundido y el colapso de alto terminen A LA VEZ --
     antes esto duraba solo 0.16s (sin el retraso de abrir, que solo
     aplica en el estado -open), así que la opacidad llegaba a 0 bastante
     antes de que el alto llegara a 0 de verdad, dejando ver un hueco
     gris vacío durante esa diferencia. */
  transition: opacity 0.28s ease;
}
.app-menu-slot.-open .app-menu-slot-inner {
  opacity: 1;
  transition: opacity 0.18s ease 0.08s;
}
.app-menu-level {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
}
.app-menu-level[hidden] { display: none; }
#routePicker { display: flex; flex-direction: column; gap: 2px; }
/* Fila "volver" en la cabecera de cada submenú: mismo componente que
   .dropdown-option, un poco más apagada y con una línea que la separa de
   las opciones de debajo. */
.dropdown-option.-back {
  color: var(--color-text-soft);
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
  border-radius: 0;
  margin-bottom: 4px;
  padding-bottom: 12px;
}
.dropdown-option .chev { margin-left: auto; flex-shrink: 0; color: var(--color-text-soft); }
.filters .pill-icon { width: 18px; height: 18px; flex-shrink: 0; }

/* Las píldoras de Filtros y los chips de Capas pasan de chip suelto/redondo
   a filas de menú completas (icono + texto), reutilizando exactamente el
   mismo componente visual .dropdown-option en vez de inventar uno nuevo. */
.filters, .layers-panel {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.filters .pill, .layer-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: auto;
  padding: 11px 12px;
  border-radius: 12px;
  background: none;
  border: none;
  color: var(--color-text);
  font-family: inherit;
  /* Mismo tamaño/peso que .menu-row-btn y .dropdown-option -- ver
     comentario en .dropdown-option sobre por qué antes se notaba como una
     fuente distinta al ver el bloque Inicio/Filtros/Capas junto con su
     panel abierto. */
  font-size: 12.5px;
  font-weight: 700;
  text-align: left;
  white-space: normal;
  cursor: pointer;
}
.filters .pill:hover, .layer-chip:hover,
.filters .pill:active, .layer-chip:active {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}
.filters .pill[data-active="true"] {
  background: color-mix(in srgb, var(--pill-color, var(--color-primary)) 16%, transparent);
  color: var(--pill-color, var(--color-primary));
  border-color: transparent;
  box-shadow: none;
}
.layer-chip svg { width: 18px; height: 18px; flex-shrink: 0; }
.layer-chip img { width: 18px; height: 18px; object-fit: contain; flex-shrink: 0; }
.layer-chip.-active { color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 10%, transparent); }

/* Aviso de que hay más categorías por la derecha: solo visible mientras
   quede scroll pendiente (ver updateFiltersScrollHint en app.js), para no
   dejarlo puesto cuando ya se ve el final de la lista. */
.filters-more {
  position: absolute;
  top: 4px;
  bottom: 4px;
  right: 4px;
  width: 34px;
  border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 6px;
  background: linear-gradient(to right,
    transparent,
    color-mix(in srgb, var(--color-surface) 65%, transparent) 60%);
  color: var(--color-text-soft);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.filters-more.-visible { opacity: 1; }
.filters-more svg { width: 15px; height: 15px; }
.pill {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-soft);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.22s ease;
  white-space: nowrap;
}
.pill:hover { background: color-mix(in srgb, var(--color-surface) 90%, transparent); }
.pill:active { transform: scale(0.96); }
/* EXPERIMENTO (rama experimento-diseno-editorial): las píldoras con texto
   partido en dos líneas ("Rutas<br>recomendadas" y "Puntos<br>de interés",
   ver setStateMode/essentialFallback en app.js) -- el <br> ya viene en el
   propio texto (se inserta con innerHTML), aquí solo se les da sitio: alto
   automático en vez del fijo de 34px, y algo menos de tamaño de letra
   para que quepan dos líneas cortas sin verse apretadas. */
.pill[data-category="essential"],
.pill[data-category="rincones-ocultos"] {
  height: auto;
  min-height: 34px;
  padding-top: 4px;
  padding-bottom: 4px;
  text-align: left;
}
.pill[data-category="essential"] span:last-child,
.pill[data-category="rincones-ocultos"] span:last-child {
  font-size: 11px;
  line-height: 1.2;
}
.pill[data-active="true"] {
  background: var(--pill-color, var(--color-primary));
  color: #0B1120;
  border-color: var(--pill-color, var(--color-primary));
  box-shadow: 0 4px 12px color-mix(in srgb, var(--pill-color, var(--color-primary)) 40%, transparent);
}
.pill-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
}
.pill-icon svg { width: 100%; height: 100%; }

/* ---------- MAP (a pantalla completa) ---------- */
.map-wrap {
  position: absolute;
  inset: 0;
}
#map {
  position: absolute;
  inset: 0;
  background: #E6E0D3;
}
.leaflet-control-zoom {
  display: none !important;
}
/* La atribución a OpenStreetMap es obligatoria por su política de uso (a
   diferencia de CARTO antes, que no la exigía con la misma firmeza): se
   mantiene visible pero discreta, en vez de ocultarla del todo como el
   control de zoom de arriba. */
.leaflet-control-attribution {
  font-size: 9px;
  background: rgba(255, 255, 255, 0.7);
  padding: 0 4px;
}
.leaflet-layer {
  filter: var(--map-filter);
}

/* ---------- GRUPO DE BOTONES FLOTANTES DEL MAPA ---------- */
/* Agrupa ubicación / bebederos / escaneo en una sola tarjeta flotante en
   vez de tres círculos sueltos: así se leen como un conjunto de
   herramientas del mapa y no como iconos ambiguos sin relación entre sí. */
.map-tools {
  position: absolute;
  right: 14px;
  bottom: max(20px, env(safe-area-inset-bottom));
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border-radius: 26px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
}
.locate-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--color-text);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.15s ease, color 0.2s ease, background 0.2s ease;
}
.locate-btn svg { width: 20px; height: 20px; }
.locate-btn img { width: 20px; height: 20px; object-fit: contain; }
.locate-btn:active { transform: scale(0.92); }
.locate-btn.-active { color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 14%, transparent); }
.locate-btn.-locating svg { animation: locateSpin 1s linear infinite; }
.locate-btn.-camera.-scanning {
  color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  animation: scanPulse 1.1s ease-in-out infinite;
}
.locate-btn.-fountains.-active { color: #38BDF8; background: rgba(56, 189, 248, 0.14); }
.locate-btn.-restrooms.-active { color: #A78BFA; background: rgba(167, 139, 250, 0.14); }

/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo).
   BORRAR este bloque (junto con .food-pin más abajo) si se retira el
   experimento. La forma de cada chip (ahora fila de menú, no círculo) vive
   en el bloque "MENÚ ÚNICO: FILTROS + CAPAS" de más arriba — aquí solo
   quedan los colores de activo por tipo de capa, más específicos que el
   ".layer-chip.-active" genérico de allí, así que siguen ganando. */
.layer-chip.-fountains.-active { color: #38BDF8; background: rgba(56, 189, 248, 0.14); }
.layer-chip.-restrooms.-active { color: #A78BFA; background: rgba(167, 139, 250, 0.14); }
.layer-chip.-food.-active { color: #F59E0B; background: rgba(245, 158, 11, 0.14); }
.layer-chip.-hotels.-active { color: #2DD4BF; background: rgba(45, 212, 191, 0.14); }

/* Pin de "comer y beber": mismo tamaño/lenguaje que fuentes/aseos (12px,
   por detrás de los POI turísticos), color ámbar para distinguirlo del
   resto de capas cuando conviven varias activas a la vez. */
.food-pin {
  width: 12px;
  height: 12px;
  background: #fff;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.12);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  display: grid;
  place-items: center;
  color: #C87F0A;
}
.food-pin svg { width: 7px; height: 7px; }

/* Pin de "hoteles": mismo tamaño/lenguaje que el resto de capas, color
   turquesa para distinguirlo de comer/beber (ámbar) cuando conviven
   varias capas activas a la vez. */
.hotel-pin {
  width: 12px;
  height: 12px;
  background: #fff;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.12);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  display: grid;
  place-items: center;
  color: #0D9488;
}
.hotel-pin svg { width: 7px; height: 7px; }

/* ---------- CAPA DE FUENTES DE AGUA POTABLE ---------- */
/* Icono deliberadamente pequeño (12px, frente a los 38px de un POI) y por
   detrás de los pines turísticos (ver zIndexOffset en renderFountains, app.js),
   para que se lean como una capa aparte sin competir con los pines de POI. */
.fountain-pin-wrap { background: transparent; border: none; }
.fountain-pin {
  width: 12px;
  height: 12px;
  background: #fff;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  display: grid;
  place-items: center;
  color: #38BDF8;
}
.fountain-pin svg { width: 8px; height: 8px; }
.fountain-pin.-off { color: #94A3B8; opacity: 0.75; }

/* ---------- CAPA DE ASEOS PÚBLICOS ---------- */
/* Mismo tamaño y comportamiento que .fountain-pin (ver arriba), con su
   propio color (violeta) para distinguirla de la capa de bebederos cuando
   ambas están activas a la vez. Los aseos "evento" (solo en actos
   puntuales, ver renderRestrooms en app.js) usan el estilo atenuado. */
.restroom-pin {
  width: 12px;
  height: 12px;
  background: #fff;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  display: grid;
  place-items: center;
  color: #A78BFA;
}
.restroom-pin img { width: 8px; height: 8px; object-fit: contain; display: block; }
.restroom-pin.-off { opacity: 0.6; }
@keyframes scanPulse {
  0%, 100% { box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45), 0 0 0 0 color-mix(in srgb, var(--color-primary) 45%, transparent); }
  50% { box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45), 0 0 0 7px transparent; }
}
@keyframes locateSpin { to { transform: rotate(360deg); } }

.scan-menu {
  position: absolute;
  right: 14px;
  bottom: 178px;
  z-index: 700;
  /* Mismo panel opaco que .map-tools, del que sale este menú. Antes dependía
     de un .dropdown-menu que ya no existía en este CSS y el menú salía sin
     fondo, con el texto flotando ilegible encima del mapa (tester Dionny). */
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border-radius: 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
}
.scan-menu[hidden] { display: none; }
.scan-menu .dropdown-option svg { flex-shrink: 0; color: var(--color-text-soft); }

/* ---------- MARCADOR "MI POSICIÓN" ---------- */
/* El wrap (ver iconSize en updateUserMarker) es más grande que el punto en
   sí (46x46 vs 16x16) para dejar sitio al cono de orientación alrededor;
   el punto se centra dentro por posición absoluta en vez de ocupar todo
   el wrap como antes. */
.user-location-wrap {
  position: relative;
  width: 46px;
  height: 46px;
}
.user-location-dot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  background: #3B82F6;
  border: 3px solid white;
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5), 0 2px 8px rgba(0, 0, 0, 0.4);
}
.user-location-dot::after {
  content: '';
  position: absolute;
  inset: -12px;
  border-radius: 50%;
  background: rgba(59, 130, 246, 0.35);
  animation: userPulse 2s ease-out infinite;
}
@keyframes userPulse {
  0% { transform: scale(0.4); opacity: 0.8; }
  100% { transform: scale(1.6); opacity: 0; }
}

/* Cono de orientación (rumbo del dispositivo): solo visible cuando hay
   lectura de brújula (ver updateUserHeadingUi en app.js). Apunta "hacia
   arriba" por defecto (0°=norte) y se rota vía transform según el rumbo
   real; el mapa en sí no gira (ver evaluación de la ruta 3), así que el
   norte del cono siempre coincide con el norte del mapa. */
.user-heading-cone {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  transform-origin: 50% 50%;
  background: conic-gradient(from -20deg at 50% 50%, rgba(59, 130, 246, 0.55) 0deg, rgba(59, 130, 246, 0.55) 40deg, transparent 41deg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.user-heading-cone.-visible {
  opacity: 1;
}

/* ---------- DISTANCIA EN LA FICHA ---------- */
.sheet-distance {
  font-size: 11px;
  font-weight: 700;
  color: var(--color-primary);
  white-space: nowrap;
}

/* ---------- CUSTOM PIN ---------- */
.custom-pin {
  position: relative;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--pin-color, var(--color-primary));
  border: 2px solid var(--color-bg);
  display: grid;
  place-items: center;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  transform: scale(var(--pin-scale, 1));
  transition: transform 0.15s ease, box-shadow 0.3s ease;
}
.custom-pin svg {
  width: 19px;
  height: 19px;
  overflow: visible;
}
/* EXPERIMENTO (rama experimento-diseno-editorial): mismo lenguaje de
   "burbuja de cristal" que las burbujas de cluster (ver .-cluster más
   abajo) pero como capa aparte (::after) en vez de fondo -- así funciona
   igual sobre un color plano (pin de categoría) que sobre una foto (pin
   -avatar, cuyo background-image va inline y pisaría cualquier gradiente
   puesto en el propio background). Se excluye .-cluster porque ese ya
   tiene su propio tratamiento de cristal más marcado. */
.custom-pin:not(.-cluster)::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 32% 26%,
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.14) 32%,
    rgba(255, 255, 255, 0.02) 55%,
    rgba(0, 0, 0, 0.22) 100%);
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, 0.4), inset 0 -4px 6px rgba(0, 0, 0, 0.32);
}
.custom-pin.-selected {
  transform: scale(calc(var(--pin-scale, 1) * 1.2));
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--pin-color, var(--color-primary)) 55%, transparent),
              0 10px 22px rgba(0, 0, 0, 0.5);
  z-index: 1000 !important;
}
.custom-pin.-dimmed {
  opacity: 0.45;
  filter: grayscale(0.4);
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
}
.custom-pin.-dimmed:not(.-selected) { z-index: 1 !important; }
.custom-pin.-route {
  font-family: inherit;
  font-size: 15px;
  font-weight: 800;
  color: #fff;
  line-height: 1;
}
.custom-pin.-cluster {
  width: var(--pin-size, 40px);
  height: var(--pin-size, 40px);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  color: #000;
  line-height: 1;
  /* Número en negro (a diferencia de las versiones anteriores, todas con
     número blanco): con un fondo tan transparente hace falta un halo claro
     en vez de la sombra oscura de antes para que se siga leyendo tanto
     sobre calles claras como sobre zonas verdes/oscuras del mapa. */
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.85), 0 0 4px rgba(255, 255, 255, 0.5);
  /* EXPERIMENTO (rama experimento-diseno-editorial): naranja (V46), gris
     pizarra (V47), gris metálico (V48), vidrio esmerilado gris (V49-V51),
     coral (V52-V53), gris metálico al 15% (V54) -- ahora blanco, casi del
     todo transparente (2% de opacidad / 98%). Con tan poco color, es
     básicamente el brillo + las sombras interiores (la propia "burbuja de
     cristal") lo que se ve; el blur de fondo sigue dando cuerpo. */
  background:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0.16) 28%, rgba(255, 255, 255, 0.03) 55%, rgba(0, 0, 0, 0.04) 100%),
    rgba(255, 255, 255, 0.02);
  border: 1.5px solid rgba(75, 85, 99, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  box-shadow:
    inset 0 2px 3px rgba(255, 255, 255, 0.45),
    inset 0 -5px 7px rgba(0, 0, 0, 0.18),
    0 6px 14px rgba(0, 0, 0, 0.25);
}
.custom-pin.-cluster:hover { transform: scale(calc(var(--pin-scale, 1) * 1.08)); }

/* EXPERIMENTO (rama experimento-diseno-editorial): pin de retrato/foto en
   vez del icono plano de categoría -- ver makePinIcon en app.js. El anillo
   de color (en vez del borde neutro del pin normal) es lo que sigue
   comunicando la categoría a simple vista, ahora que el icono ya no está. */
.custom-pin.-avatar {
  background-size: cover;
  background-position: center top;
  background-color: var(--pin-color, var(--color-primary));
  border: 3px solid var(--pin-color, var(--color-primary));
}
.custom-pin.-avatar.-selected {
  border-color: #fff;
}

/* ---------- DISTANCIA ENTRE PARADAS DE LA RUTA ---------- */
.route-distance-wrap { pointer-events: none; }
.route-distance {
  display: inline-block;
  transform: translate(-50%, -50%);
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  border: 1.5px solid var(--route-color, var(--color-primary));
  color: var(--color-text);
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 700;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* ---------- BOTTOM SHEET ---------- */
.sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 15, 0);
  pointer-events: none;
  transition: background 0.35s ease;
  z-index: 1500;
}
.sheet-backdrop.-open {
  background: rgba(2, 6, 15, 0.55);
  pointer-events: auto;
}
.bottom-sheet {
  /* La ficha es la superficie donde más tiempo se pasa leyendo (audioguía,
     chat), así que va en claro aunque el resto de la cabecera/mapa se
     quede oscuro por contraste: redeclarar aquí los mismos tokens que ya
     usan todos los elementos de dentro (chat, chips, audio, quiz) basta
     para retemátizarlos sin tocarlos uno a uno. */
  --color-surface: #FFFFFF;
  --color-surface-2: #F5F1E8;
  --color-text: #1E293B;
  --color-text-soft: #64748B;
  --color-border: #E7E2D4;
  --color-primary-soft: #B45309;
  /* EXPERIMENTO (rama experimento-diseno-editorial): dentro de la ficha, el
     color "de marca" para botones/progreso/cabecera pasa a ser el MISMO
     color que ya tiene el pin de esa categoría en el mapa (--sheet-accent-raw,
     fijado por populateSheetContent en app.js a partir de
     getCategoryPinColor) en vez del naranja saturado de siempre -- así la
     ficha ya no desentona en cuanto se abre. El respaldo (#8A6F52, por si
     alguna vez se abre sin categoría) es un tostado neutro, no el naranja
     de marca. */
  --color-primary: var(--sheet-accent-raw, #8A6F52);
  /* EXPERIMENTO: ficha "flotante" en vez de pegada a los tres bordes de la
     pantalla -- deja un margen a los lados y abajo (respetando el notch
     inferior con env(safe-area-inset-bottom), mismo patrón que el resto de
     botones flotantes del mapa) y redondea las 4 esquinas, no solo las de
     arriba. overflow:hidden en este mismo elemento ya recorta el fondo de
     .sheet-head a esas esquinas, así que no hace falta tocarlo aparte. */
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: max(10px, env(safe-area-inset-bottom));
  /* FIX (mobile, reportado: "hay espacio aun" -- la ficha dejaba una franja
     de mapa visible de sobra por encima). Subido de 76vh a 84vh: sigue
     dejando margen bajo el menú Inicio/Filtros/Capas de arriba (.header,
     con pointer-events:none salvo en sus filas -- ver el comentario ahí),
     que no llega a esa altura ni con un panel desplegado. */
  max-height: 84vh;
  max-height: 84dvh;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  border: 1px solid var(--color-border);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35), 0 2px 10px rgba(0, 0, 0, 0.25);
  z-index: 2200;
  transform: translateY(calc(100% + 24px));
  transition: transform 0.38s cubic-bezier(0.32, 0.72, 0, 1), visibility 0s linear 0.38s;
  display: flex;
  flex-direction: column;
  /* BUG REAL (reportado: "se recorta arriba o abajo"): con overflow:hidden
     a secas, cuando el contenido (cabecera + audio + chat + chips + campo
     de pregunta, a veces con la ficha de patrocinio de por medio) supera
     max-height, ese sobrante simplemente se recortaba sin más -- pero en
     WebKit/iOS un overflow:hidden con contenido de verdad desbordado
     TODAVÍA se puede arrastrar con el dedo, lo que scrollea TODA la ficha
     (cabecera y botón de cerrar incluidos) fuera de la pantalla, sin forma
     de volver salvo adivinar que hay que arrastrar hacia abajo nada más
     abrirla. overflow-y:auto lo convierte en un scroll de verdad y
     controlado (con su propia barra, sin rebote fuera de la ficha gracias
     a overscroll-behavior) + .sheet-head sticky (ver más abajo) para que la
     cabecera con el botón de cerrar NUNCA desaparezca, pase lo que pase
     con el resto del contenido. BORRAR este comentario si se retira el
     experimento (el fix se queda si el cambio se mantiene). */
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  visibility: hidden;
}
html[data-mode="kids"] .bottom-sheet {
  --color-surface: #FFFFFF;
  --color-surface-2: #F1EFFC;
  --color-text: #1E1B4B;
  --color-text-soft: #6B67A6;
  --color-border: #E4E1F7;
  --color-primary-soft: #4338CA;
}
.bottom-sheet.-open {
  transform: translateY(0);
  transition: transform 0.38s cubic-bezier(0.32, 0.72, 0, 1), visibility 0s linear 0s;
  visibility: visible;
}

.sheet-handle {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  padding: 14px 0 10px;
  cursor: pointer;
  /* FIX: sin esto, el navegador intenta hacer scroll/pan de la página
     mientras se arrastra el tirador (ver el pointermove en wireEvents,
     app.js), compitiendo con el gesto de cerrar. */
  touch-action: none;
  /* Va pegado (sticky) justo encima de .sheet-head -- ver el comentario de
     ese bloque sobre por qué. Necesita su propio fondo (antes lo heredaba
     gratis del propio .bottom-sheet al no moverse) para no dejar ver el
     contenido pasando por debajo al hacerse sticky. */
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--color-surface);
  border-top-left-radius: var(--radius-xl);
  border-top-right-radius: var(--radius-xl);
}
.sheet-handle-bar {
  width: 40px;
  height: 4px;
  border-radius: 4px;
  background: var(--color-border);
}

.sheet-head {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 16px 12px;
  flex-shrink: 0;
  /* BUG REAL (ver el comentario en .bottom-sheet sobre overflow-y:auto):
     esta es la fila con el título y el botón de cerrar -- la que NUNCA
     puede desaparecer al scrollear el resto de la ficha. Sticky justo
     debajo de .sheet-handle (28px de alto: 14+10 de padding + 4 de la
     barra), con su propio z-index para quedar por encima del audio/chat
     que pasa por debajo. */
  position: sticky;
  top: 28px;
  z-index: 5;
  /* EXPERIMENTO (rama experimento-diseno-editorial): cabecera teñida con el
     color de la categoría (ver --sheet-accent-raw, puesto por
     populateSheetContent en app.js), en vez de blanco liso -- inspirado en
     tarjetas de apps de historia con fondo de color por categoría. Se
     mezcla con blanco (45%) para quedar en un tono medio legible, no el
     acento saturado entero. Sin --sheet-accent-raw (fichas sin categoría
     conocida) cae a transparente, o sea el fondo blanco de siempre.
     border-top-radius igualado al de .bottom-sheet para que la esquina
     coincida al ras. */
  background: color-mix(in srgb, var(--sheet-accent-raw, transparent) 45%, white);
  border-top-left-radius: var(--radius-xl);
  border-top-right-radius: var(--radius-xl);
}
.sheet-head[data-tinted="true"] .sheet-cat-badge,
.sheet-head[data-tinted="true"] .sheet-title,
.sheet-head[data-tinted="true"] .sheet-sub,
.sheet-head[data-tinted="true"] .sheet-distance {
  color: #241C10;
}
.sheet-head[data-tinted="true"] .sheet-close,
.sheet-head[data-tinted="true"] .sheet-directions-btn {
  color: #241C10;
}
.sheet-thumb-wrap {
  position: relative;
  width: 56px;
  height: 56px;
  flex-shrink: 0;
}
.sheet-thumb {
  width: 56px;
  height: 56px;
  border-radius: 14px;
  object-fit: cover;
  border: 1px solid var(--color-border);
  flex-shrink: 0;
  background: var(--color-surface-2);
  cursor: pointer;
  transition: transform 0.15s ease, opacity 0.15s ease;
}
.sheet-thumb:hover { opacity: 0.9; }
.sheet-thumb:active { transform: scale(0.95); }
/* Si la miniatura falla al cargar (típico con cobertura floja en cascos
   históricos) y el reintento automático a 1s también falla, este botón
   se superpone a la imagen rota para reintentar a mano (ver
   setSheetThumbImage en app.js). */
.sheet-thumb-retry {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: 14px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface-2) 92%, transparent);
  color: var(--color-text-soft);
  cursor: pointer;
}
.sheet-thumb-retry svg { width: 22px; height: 22px; }
.sheet-thumb-retry:active { transform: scale(0.92); }
.sheet-thumb-retry[hidden] { display: none; }

/* ---------- CÁMARA: captura en vivo para "¿qué estoy viendo?" ---------- */
.camera-modal {
  position: fixed;
  inset: 0;
  z-index: 5800;
  background: #000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.camera-modal.-open {
  opacity: 1;
  visibility: visible;
}
.camera-video-wrap {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
#cameraVideo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: 50% 50%;
  /* El zoom en sí (transform: scale) lo aplica applyCameraZoom en app.js;
     esta transición solo suaviza los saltos de los botones +/-, no el
     pellizco en vivo (ahí interesa que responda al instante). */
  transition: transform 0.15s ease;
}
.camera-zoom-controls {
  position: absolute;
  left: 50%;
  bottom: max(120px, calc(env(safe-area-inset-bottom) + 110px));
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
}
.camera-zoom-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: transparent;
  color: #fff;
  font-size: 18px;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.camera-zoom-btn:active { background: rgba(255, 255, 255, 0.2); }
.camera-zoom-label {
  min-width: 34px;
  text-align: center;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.camera-modal-close {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  right: 16px;
  z-index: 1;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 0;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.camera-modal-close svg { width: 18px; height: 18px; }
.camera-modal-close:active { transform: scale(0.92); }
.camera-shutter {
  position: absolute;
  left: 50%;
  bottom: max(28px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 1;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: 4px solid #fff;
  background: transparent;
  cursor: pointer;
  transition: transform 0.12s ease;
}
.camera-shutter::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: #fff;
}
.camera-shutter:active { transform: translateX(-50%) scale(0.9); }
.camera-shutter:active::before { background: color-mix(in srgb, #fff 80%, black); }

/* ---------- LIGHTBOX: imagen ampliada ---------- */
.image-lightbox {
  position: fixed;
  inset: 0;
  z-index: 5500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.image-lightbox.-open {
  opacity: 1;
  visibility: visible;
}
.lightbox-img {
  max-width: min(92vw, 900px);
  max-height: 85vh;
  border-radius: 16px;
  object-fit: contain;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.lightbox-close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.lightbox-close svg { width: 20px; height: 20px; }
/* Si la imagen grande falla (cobertura floja) tras agotar el reintento
   automático a 1s, esta tarjeta sustituye a la imagen para reintentar a
   mano (ver openLightbox en app.js). */
.lightbox-retry {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 28px 36px;
  border-radius: 16px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
}
.lightbox-retry svg { width: 28px; height: 28px; }
.lightbox-retry:active { transform: scale(0.96); }
.lightbox-retry[hidden] { display: none; }

/* ---------- RESUMEN DE VISITA (modo niño, se abre desde la insignia de puntos) ---------- */
.visit-summary-modal {
  position: fixed;
  inset: 0;
  z-index: 5500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.visit-summary-modal.-open {
  opacity: 1;
  visibility: visible;
}
.visit-summary-card {
  position: relative;
  width: min(92vw, 380px);
  max-height: 82vh;
  overflow-y: auto;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 28px 22px 20px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.visit-summary-icon { font-size: 34px; line-height: 1; margin-bottom: 6px; }
.visit-summary-title { font-size: 17px; font-weight: 800; margin: 0 0 4px; }
.visit-summary-stat { font-size: 13px; color: var(--color-text-soft); margin: 0 0 12px; }
.visit-summary-bar {
  height: 8px;
  border-radius: 8px;
  background: var(--color-border);
  overflow: hidden;
  margin-bottom: 10px;
}
.visit-summary-fill {
  height: 100%;
  width: 0%;
  background: var(--color-primary);
  border-radius: 8px;
  transition: width 0.3s ease;
}
.visit-summary-points {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--explorer-color, #F5B942) 22%, transparent);
  color: var(--explorer-color, #F5B942);
  margin-bottom: 16px;
}
.city-badge-progress {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-bottom: 16px;
  border-radius: 12px;
  background: color-mix(in srgb, #F5B942 10%, var(--color-surface));
  border: 1px solid color-mix(in srgb, #F5B942 35%, var(--color-border));
}
.city-badge-progress-icon { flex-shrink: 0; }
.city-badge-progress-icon.-locked { filter: grayscale(1) brightness(0.7); opacity: 0.55; }
.city-badge-progress-text { flex: 1; text-align: left; }
.city-badge-progress-label { display: block; font-size: 11.5px; font-weight: 700; color: var(--color-text); margin-bottom: 4px; }
.city-badge-progress-bar { height: 6px; border-radius: 6px; background: var(--color-border); overflow: hidden; }
.city-badge-progress-fill { height: 100%; width: 0%; background: #F5B942; border-radius: 6px; transition: width 0.3s ease; }
.visit-summary-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}
.visit-summary-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  padding: 6px 10px;
  border-radius: 10px;
  background: var(--color-surface);
}
.visit-summary-list li[data-visited="true"] { color: var(--color-text); }
.visit-summary-list li[data-visited="false"] { color: var(--color-text-soft); }
.visit-summary-list .visit-summary-check { flex-shrink: 0; }

/* ---------- BUSCAR UN POI POR NOMBRE (roadmap) ---------- */
.poi-search-modal {
  position: fixed;
  inset: 0;
  z-index: 5500;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) 16px 16px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.poi-search-modal.-open {
  opacity: 1;
  visibility: visible;
}
.poi-search-card {
  width: min(92vw, 420px);
  max-height: min(80vh, 80dvh);
  display: flex;
  flex-direction: column;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 18px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}
.poi-search-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 14px 12px;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.poi-search-icon { width: 20px; height: 20px; color: var(--color-text-soft); flex-shrink: 0; }
.poi-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  color: var(--color-text);
  font-size: 15px;
  font-family: inherit;
  outline: none;
}
.poi-search-input::placeholder { color: var(--color-text-soft); }
.poi-search-close {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.poi-search-close svg { width: 14px; height: 14px; }
.poi-search-close:active { transform: scale(0.92); }
.poi-search-results {
  list-style: none;
  margin: 0;
  padding: 6px;
  overflow-y: auto;
}
.poi-search-result {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 10px;
  border: none;
  background: none;
  border-radius: 10px;
  color: var(--color-text);
  text-align: left;
  font-family: inherit;
  cursor: pointer;
}
.poi-search-result:hover, .poi-search-result:active { background: var(--color-surface); }
.poi-search-result-thumb {
  width: 36px;
  height: 36px;
  border-radius: 9px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}
.poi-search-result-text { flex: 1; min-width: 0; }
.poi-search-result-name {
  font-size: 13.5px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.poi-search-result-cat {
  font-size: 11px;
  color: var(--color-text-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.poi-search-empty {
  margin: 0;
  padding: 18px 16px 22px;
  text-align: center;
  font-size: 13px;
  color: var(--color-text-soft);
}

.sheet-head-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-top: 2px;
}
/* FIX (mobile, reportado: título/subtítulo cortados, "no se llega ni a leer
   la historia"): categoría y distancia ya no viven aquí (ver
   .sheet-head-meta, ahora su propia franja arriba de todo), y ya no se
   recorta el texto con line-clamp -- la ficha entera scrollea (ver
   overflow-y:auto en .bottom-sheet), así que no hay necesidad real de
   limitar las líneas del título/subtítulo: mejor dejar que ocupen las que
   hagan falta a que se corten a media frase con "…". */
.sheet-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--color-text);
  line-height: 1.3;
}
.sheet-sub {
  font-size: 12px;
  line-height: 1.35;
  color: var(--color-text-soft);
}
/* FIX (mobile, reportado: el subtítulo seguía en 4 líneas en vez de 2 --
   "Llévame"/cerrar se mudaron arriba de esta fila (ver .sheet-head-meta),
   así que aquí ya solo queda miniatura + texto, que ahora sí tiene casi
   todo el ancho de la ficha para él solo. */
.sheet-head-main {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.sheet-head-actions-row {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* FIX (mobile, feedback de seguimiento sobre el rediseño anterior: "eso
   puede ir arriba donde señalo" + "tiene 4 líneas, pudiera colocarse en
   dos"): categoría/distancia Y Llévame/cerrar viven ahora en esta misma
   franja de arriba, a todo el ancho -- el grupo categoría+distancia a la
   izquierda, Llévame+cerrar a la derecha. Antes Llévame/cerrar vivían en
   la fila de abajo, junto al texto, obligando a esa fila a repartir su
   ancho entre miniatura+texto+esos dos botones; sacarlos de ahí le
   devuelve al texto casi todo el ancho de la ficha (ver .sheet-head-main),
   así que el mismo subtítulo necesita bastantes menos líneas.
   FIX (reportado en producción/iPhone real: con una categoría larga +
   distancia tipo "A 9.5 km", el grupo de la izquierda ya no cabía junto a
   Llévame/cerrar en la misma línea, y flex-wrap los mandaba a SU PROPIA
   fila ancha y pegada a la izquierda -- "me gustaba arriba a la derecha").
   Ahora esta fila NO envuelve: Llévame/cerrar (flex-shrink:0) se quedan
   siempre fijos arriba a la derecha, y es el grupo de categoría/distancia
   el que envuelve puertas adentro si no le cabe todo en una línea (ver
   .sheet-head-meta-left). */
.sheet-head-meta {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.sheet-head-meta-left {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  flex: 1;
  min-width: 0;
}
.sheet-cat-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-primary) 20%, transparent);
  color: var(--color-primary-soft);
  white-space: nowrap;
}
.sheet-close {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.2s ease, color 0.2s ease;
}
.sheet-close:hover { color: var(--color-text); }
.sheet-close:active { transform: scale(0.92); }
.sheet-close svg { width: 16px; height: 16px; stroke-width: 2.4; }

/* EXPERIMENTO (rama experimento-diseno-editorial): "Cómo llegar" se muda
   de la fila de chips de abajo a este botón junto al de cerrar -- es la
   acción práctica más inmediata al abrir una ficha, así que gana un sitio
   fijo y siempre visible en vez de competir por espacio con el resto de
   chips (que ahora se desplazan por el scroll horizontal). Ya no es un
   círculo de solo icono: lleva también una etiqueta corta ("Llévame", ver
   sheetDirectionsLabel en app.js) en la misma píldora, para que se
   entienda de un vistazo qué hace sin depender solo del icono. El icono va
   en el color de categoría de la ficha (--color-primary, ver
   .bottom-sheet) para que se note que es una acción, no un botón neutro
   más. Oculto por defecto (algún POI suelto podría no traer coords) y se
   muestra en populateSheetContent. */
.sheet-directions-btn {
  flex-shrink: 0;
  height: 32px;
  padding: 0 12px 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease;
}
.sheet-directions-btn:hover { border-color: var(--color-primary); }
.sheet-directions-btn:active { transform: scale(0.96); }
.sheet-directions-btn svg { width: 14px; height: 14px; color: var(--color-primary); flex-shrink: 0; }
.sheet-directions-btn[hidden] { display: none; }

/* ---------- AI CHAT ---------- */
.ai-messages {
  flex: 1;
  /* BUG REAL (ligado al de arriba: "se recorta la ficha"): con este
     min-height en 120px, cuando el resto de filas fijas de la ficha
     (cabecera, audio, chips, campo de pregunta) ya ocupaban casi todo el
     max-height de .bottom-sheet (76vh), el conjunto se pasaba de ese
     límite -- .bottom-sheet no tiene overflow:auto propio así que en
     teoría se recortaría sin más, pero algunos navegadores (WebKit/iOS)
     igual dejan arrastrar ese sobrante con el dedo, lo que scrollea TODA
     la ficha (cabecera incluida) fuera de vista en vez de solo este chat.
     Bajarlo a 64px deja sitio de sobra en la mayoría de casos para que
     nunca haga falta ese desbordamiento. */
  min-height: 64px;
  max-height: 38vh;
  overflow-y: auto;
  margin: 0 16px 10px;
  padding: 12px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  scrollbar-width: thin;
}
.ai-messages::-webkit-scrollbar { width: 4px; }
.ai-messages::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 4px; }

.ai-msg {
  max-width: 92%;
  display: flex;
  gap: 8px;
  align-items: flex-start;
  animation: msgIn 0.32s cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes msgIn {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.ai-msg.-user { align-self: flex-end; flex-direction: row-reverse; }
.ai-msg-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  font-size: 12px;
  background: var(--color-primary);
  color: #0B1120;
  box-shadow: 0 3px 8px color-mix(in srgb, var(--color-primary) 35%, transparent);
}
.ai-msg.-user .ai-msg-avatar { background: var(--color-border); color: var(--color-text); }
.ai-msg-avatar-icon { width: 62%; height: 62%; object-fit: contain; }
/* Icono de IA "en limpio": sin el círculo de fondo, el propio dibujo (ya
   lleva su borde blanco) se apoya en una sombra para no perderse sobre
   fondos claros. */
.ai-msg-avatar.-plain { background: transparent; box-shadow: none; }
.ai-msg-avatar.-plain .ai-msg-avatar-icon {
  width: 92%;
  height: 92%;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
}
.ai-msg-bubble {
  padding: 9px 13px;
  border-radius: 15px;
  line-height: 1.5;
  font-size: 13.2px;
  border-top-left-radius: 4px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  white-space: pre-wrap;
  word-break: break-word;
}
.ai-msg-link {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--color-primary);
  color: #0B1120;
  font-weight: 600;
  font-size: 12.5px;
  text-decoration: none;
}
.ai-msg.-user .ai-msg-bubble {
  background: var(--color-primary);
  color: #0B1120;
  border: 0;
  border-top-left-radius: 15px;
  border-top-right-radius: 4px;
  font-weight: 500;
}

.ai-msg.-typing .ai-msg-bubble {
  padding: 11px 13px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.ai-msg.-typing .ai-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary);
  opacity: 0.7;
  animation: typingBounce 1s ease-in-out infinite;
}
.ai-msg.-typing .ai-dot:nth-child(2) { animation-delay: 0.18s; }
.ai-msg.-typing .ai-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes typingBounce {
  0%, 80%, 100% { transform: translateY(0); opacity: 0.45; }
  40% { transform: translateY(-5px); opacity: 1; }
}

.ai-suggestions {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 0 16px 12px;
  flex-shrink: 0;
  scrollbar-width: none;
}
.ai-suggestions::-webkit-scrollbar { display: none; }
/* Difuminado a la derecha cuando de verdad sobra contenido por desplazar
   (clase añadida/quitada por updateFadeHint en renderAiSuggestions, app.js):
   sin esto el último chip se veía cortado en seco en el borde de pantallas
   estrechas y parecía un fallo, no una fila deslizable. */
.ai-suggestions.-scrollable {
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent);
}
.suggest-chip {
  flex-shrink: 0;
  white-space: nowrap;
  height: 34px;
  padding: 0 13px;
  border-radius: 12px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: all 0.2s ease;
}
.suggest-chip-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-primary);
}
.suggest-chip-icon svg { width: 100%; height: 100%; }
.suggest-chip:hover {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface-2));
}
.suggest-chip:active { transform: translateY(1px); }
.suggest-chip[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

/* ---------- PREGUNTA LIBRE ---------- */
.ai-input-wrap {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: 8px;
  padding: 0 16px 12px;
  flex-shrink: 0;
}
.ai-input {
  height: 42px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: inherit;
  font-size: 13.5px;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.ai-input::placeholder { color: var(--color-text-soft); }
.ai-input:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 25%, transparent);
}
.ai-send {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 0;
  background: var(--color-primary);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.15s ease, opacity 0.2s ease;
}
.ai-send svg { width: 17px; height: 17px; }
.ai-send:active { transform: scale(0.92); }
.ai-send[disabled] { opacity: 0.45; pointer-events: none; }

.ai-mic {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  color: var(--color-text-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.15s ease, color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.ai-mic[hidden] { display: none; }
.ai-mic svg { width: 18px; height: 18px; }
.ai-mic:active { transform: scale(0.92); }
.ai-mic:hover { color: var(--color-primary); border-color: var(--color-primary); }
.ai-mic[aria-pressed="true"] {
  color: #fff;
  border-color: #EF4444;
  background: #EF4444;
  animation: aiMicPulse 1.4s ease-in-out infinite;
}
@keyframes aiMicPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.45); }
  50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}

/* Botón de "hablar con la IA" (abre el modal de conversación, ver
   .ai-call-modal): a diferencia de .ai-mic (dictado puntual dentro del
   propio input), este abre una conversación completa que se queda activa
   hasta que el usuario la cierra. Se destaca con el color primario relleno,
   como una tercera opción clara junto a escribir/dictar. */
.ai-call-open {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 0;
  background: var(--color-primary);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.ai-call-open svg { width: 18px; height: 18px; }
.ai-call-open:active { transform: scale(0.92); }

/* ---------- CONVERSACIÓN POR VOZ (modal de "llamada") ---------- */
.ai-call-modal {
  position: fixed;
  inset: 0;
  z-index: 6200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(5, 7, 13, 0.94);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.ai-call-modal.-open {
  opacity: 1;
  visibility: visible;
}
.ai-call-card {
  position: relative;
  width: min(92vw, 380px);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 24px;
  padding: 44px 20px 20px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.ai-call-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 30px;
  height: 30px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  font-size: 13px;
}
.ai-call-avatar {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--color-primary);
  color: #0B1120;
  display: grid;
  place-items: center;
  font-size: 30px;
  flex-shrink: 0;
  transition: box-shadow 0.2s ease;
}
.ai-call-avatar-icon { width: 60%; height: 60%; object-fit: contain; }
.ai-call-avatar.-plain {
  background: transparent;
}
.ai-call-avatar.-plain .ai-call-avatar-icon {
  width: 88%;
  height: 88%;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.4));
}
.ai-call-avatar.-listening {
  animation: aiCallPulse 1.3s ease-in-out infinite;
}
.ai-call-avatar.-speaking {
  animation: aiCallPulse 0.8s ease-in-out infinite;
}
@keyframes aiCallPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary) 45%, transparent); }
  50% { box-shadow: 0 0 0 14px color-mix(in srgb, var(--color-primary) 0%, transparent); }
}
.ai-call-status {
  margin-top: 14px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--color-text);
  text-align: center;
  min-height: 18px;
}
.ai-call-transcript {
  width: 100%;
  max-height: 32vh;
  overflow-y: auto;
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ai-call-transcript:empty { display: none; }
.ai-call-bubble {
  max-width: 88%;
  padding: 8px 12px;
  border-radius: 14px;
  font-size: 12.5px;
  line-height: 1.4;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  align-self: flex-start;
}
.ai-call-bubble.-user {
  align-self: flex-end;
  background: var(--color-primary);
  color: #0B1120;
  border-color: transparent;
}
.ai-call-textrow {
  width: 100%;
  display: flex;
  gap: 8px;
  margin-top: 16px;
}
.ai-call-textrow[hidden] { display: none; }
.ai-call-input {
  flex: 1;
  height: 42px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  font-size: 13.5px;
  outline: none;
}
.ai-call-input:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 25%, transparent);
}
.ai-call-send {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 0;
  background: var(--color-primary);
  color: #0B1120;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
}
.ai-call-send svg { width: 17px; height: 17px; }
.ai-call-end {
  margin-top: 18px;
  height: 40px;
  padding: 0 24px;
  border-radius: 999px;
  border: 0;
  background: #DC2626;
  color: #fff;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.ai-call-end:active { transform: scale(0.96); }

/* ---------- AUDIO PLAYER ---------- */
.audio-player {
  flex-shrink: 0;
  padding: 0 16px 16px;
}
.audio-card {
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 10px 14px 12px;
  display: grid;
  grid-template-columns: 44px 1fr;
  grid-template-rows: auto auto;
  column-gap: 12px;
  row-gap: 8px;
  align-items: center;
}
.play-btn {
  grid-row: 1 / 3;
  grid-column: 1 / 2;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 0;
  background: var(--color-primary);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 3px 10px color-mix(in srgb, var(--color-primary) 30%, transparent);
  transition: transform 0.15s ease;
}
.play-btn:active { transform: scale(0.92); }
.play-btn svg { width: 18px; height: 18px; }
.audio-title {
  grid-row: 1 / 2;
  grid-column: 2 / 3;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--color-text-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.audio-progress {
  grid-row: 2 / 3;
  grid-column: 2 / 3;
  display: flex;
  align-items: center;
  gap: 10px;
}
.progress-wrap {
  flex: 1;
  height: 5px;
  background: var(--color-border);
  border-radius: 5px;
  overflow: hidden;
  position: relative;
}
.progress-fill {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 0%;
  background: var(--color-primary);
  border-radius: 5px;
}
.audio-time {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  font-weight: 700;
  color: var(--color-text-soft);
  min-width: 66px;
  text-align: right;
}

/* ---------- INTRO DE RUTA (audioguía corta al elegir un circuito) ---------- */
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   segunda prueba de posición -- ahora flota "al piso" sobre el mapa, como
   un mini-reproductor anclado justo encima de .map-tools (cámara/
   ubicación, bottom:20px + ~102px de alto), en vez de vivir dentro de
   .header y quedar pegado arriba (primera prueba). El offset vertical dEja
   sitio de sobra para .map-tools sin que se solapen. BORRAR este
   comentario si se retira el experimento (la posición elegida se queda si
   el resultado convence). */
.route-intro[hidden] { display: none; }
.route-intro {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: calc(max(20px, env(safe-area-inset-bottom)) + 112px);
  z-index: 600;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 10px 12px;
  display: grid;
  grid-template-columns: 40px 1fr auto;
  column-gap: 10px;
  align-items: center;
  box-shadow: var(--shadow-md);
}
.route-intro-play {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 0;
  background: var(--route-intro-color, var(--color-primary));
  color: #0B1120;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.route-intro-play:active { transform: scale(0.92); }
.route-intro-play svg { width: 16px; height: 16px; }
.route-intro-body { min-width: 0; }
.route-intro-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 4px;
}
.route-intro-progress {
  display: flex;
  align-items: center;
  gap: 8px;
}
.route-intro-bar {
  flex: 1;
  height: 4px;
  background: var(--color-border);
  border-radius: 4px;
  overflow: hidden;
  position: relative;
}
.route-intro-fill {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 0%;
  background: var(--route-intro-color, var(--color-primary));
  border-radius: 4px;
}
.route-intro-time {
  font-variant-numeric: tabular-nums;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--color-text-soft);
  min-width: 58px;
  text-align: right;
}
.route-intro-close {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 0;
  background: var(--color-surface);
  color: var(--color-text-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.route-intro-close svg { width: 15px; height: 15px; }

/* ---------- MODO NIÑO: sin chat de texto, solo audioguía + pregunta al terminar ---------- */
html[data-mode="kids"] .ai-messages,
html[data-mode="kids"] .ai-suggestions,
html[data-mode="kids"] .ai-input-wrap {
  display: none;
}

.kids-quiz-card {
  flex-shrink: 1;
  min-height: 0;
  max-height: 42vh;
  max-height: 42dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 16px 16px;
  padding: 14px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  animation: msgIn 0.32s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.quiz-done {
  font-weight: 700;
  text-align: center;
  margin: 0;
  color: var(--color-text);
}
.quiz-reveal {
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--color-border);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--color-text);
}
.quiz-next-btn {
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 11px 14px;
  border-radius: var(--radius-md, 12px);
  border: 0;
  background: var(--color-primary);
  color: #0B1120;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.1s ease;
}
.quiz-next-btn:active { transform: scale(0.98); }

/* ---------- TOAST ---------- */
.toast {
  position: absolute;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(40px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-size: 13px;
  font-weight: 500;
  padding: 10px 16px;
  border-radius: var(--radius-pill);
  z-index: 9999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
  box-shadow: var(--shadow-md);
}
.toast.-show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------- AVISO DE VERSIÓN NUEVA ---------- */
/* A diferencia del toast, no se autooculta: se queda hasta que el usuario
   pulsa "Actualizar" (ver el script de registro del Service Worker en
   index.html), así que necesita su propio botón en vez de solo texto. */
.update-banner {
  position: fixed;
  left: 50%;
  /* env(safe-area-inset-bottom) solo compensa el indicador de inicio del
     iPhone, no la barra de herramientas de Safari (dirección/pestañas) que
     se queda encima de esa zona cuando el móvil abre la web sin instalarla
     como PWA — sin el +44px extra, el botón "Actualizar" queda tapado por
     esa barra y es imposible de pulsar (bug real reportado por un usuario). */
  bottom: max(60px, calc(env(safe-area-inset-bottom) + 44px));
  /* OJO: un translateY en % se calcula sobre la altura DEL PROPIO elemento
     (~55px), así que 140% solo baja ~77px — no basta para sacarlo del todo
     de la pantalla y se quedaba un trozo asomando siempre por el borde
     inferior, estuviera "mostrado" o no (bug real, confirmado con
     getBoundingClientRect: con viewport de 567px de alto, el aviso
     "oculto" llegaba hasta arriba de los 528px, bien dentro de lo
     visible). visibility+opacity+pointer-events lo ocultan de verdad,
     pase lo que pase con el offset del transform.*/
  transform: translateX(-50%) translateY(140%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 9500;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(92vw, 420px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  padding: 10px 10px 10px 16px;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-lg);
  transition: transform 0.3s ease, opacity 0.3s ease, visibility 0.3s;
}
.update-banner.-show {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.update-banner-text { font-size: 13px; font-weight: 600; white-space: nowrap; }
.update-banner-btn {
  flex-shrink: 0;
  border: 0;
  background: #F5B942;
  color: #1a1300;
  font-weight: 800;
  font-size: 12.5px;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.update-banner-btn:active { transform: scale(0.96); }

/* ---------- MARCO DE TELÉFONO (tablet+) ---------- */
@media (min-width: 768px) {
  body { display: flex; align-items: center; justify-content: center; }
  .app {
    max-width: 480px;
    height: min(880px, 92vh);
    border-radius: 32px;
    border: 1px solid var(--color-border);
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
  }
  .bottom-sheet {
    border-top-left-radius: 32px;
    border-top-right-radius: 32px;
  }
}

/* ---------- TUTORIAL GUIADO (modo niño, primera vez) ---------- */
.tutorial-overlay {
  position: fixed;
  inset: 0;
  z-index: 6300;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease;
}
.tutorial-overlay.-open {
  opacity: 1;
  visibility: visible;
}
/* El "agujero" iluminado: un elemento invisible posicionado justo sobre el
   objetivo de cada paso (ver positionTutorialSpotlight en app.js), cuyo
   propio box-shadow con un spread enorme oscurece el resto de la pantalla
   a su alrededor. No hace falta overlay + clip-path aparte: este único
   truco CSS resuelve el "todo opaco menos el icono que se explica". */
.tutorial-spotlight {
  position: fixed;
  border-radius: 18px;
  box-shadow: 0 0 0 9999px rgba(5, 7, 13, 0.8);
  transition: top 0.35s ease, left 0.35s ease, width 0.35s ease, height 0.35s ease, border-radius 0.35s ease;
  pointer-events: none;
}
/* Anillo pulsante alrededor del propio agujero iluminado: sin esto, en
   pruebas de usuario varias personas leían el texto del tooltip (siempre
   anclado abajo) y pasaban a "Siguiente" sin que su vista llegara a subir
   hasta el elemento iluminado, que podía estar lejos en la pantalla — el
   oscurecido del fondo por sí solo no atraía la mirada lo bastante rápido.
   Solo se activa con la clase "-highlighting" (ver positionTutorialSpotlight
   en app.js): el paso informativo sin objetivo real (spotlight de 0x0) no
   la lleva, para no pulsar un anillo alrededor de la nada. */
.tutorial-spotlight.-highlighting {
  animation: tutorialPulse 1.4s ease-out infinite;
}
@keyframes tutorialPulse {
  0% { box-shadow: 0 0 0 9999px rgba(5, 7, 13, 0.8), 0 0 0 0 color-mix(in srgb, var(--color-primary) 70%, transparent); }
  70% { box-shadow: 0 0 0 9999px rgba(5, 7, 13, 0.8), 0 0 0 14px color-mix(in srgb, var(--color-primary) 0%, transparent); }
  100% { box-shadow: 0 0 0 9999px rgba(5, 7, 13, 0.8), 0 0 0 0 color-mix(in srgb, var(--color-primary) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .tutorial-spotlight.-highlighting { animation: none; }
}
.tutorial-tooltip {
  position: fixed;
  left: 50%;
  bottom: max(24px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(92vw, 420px);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 16px 18px 14px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
/* Cuando el elemento iluminado está pegado a la parte baja de la pantalla
   (herramientas del mapa, ficha de ejemplo…), el tooltip se ancla arriba
   en su lugar para no taparlo (ver positionTutorialSpotlight en app.js). */
.tutorial-tooltip.-top {
  bottom: auto;
  top: max(20px, env(safe-area-inset-top));
}
.tutorial-skip {
  display: block;
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--color-text-soft);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 2px 0 8px;
}
.tutorial-title { font-size: 16px; font-weight: 800; margin: 0 0 6px; }
.tutorial-text { font-size: 13px; line-height: 1.45; color: var(--color-text-soft); margin: 0; }
.tutorial-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 14px;
}
.tutorial-back {
  flex-shrink: 0;
  border: 0;
  background: transparent;
  color: var(--color-text-soft);
  font-weight: 700;
  font-size: 12.5px;
  padding: 9px 4px;
  cursor: pointer;
}
.tutorial-back:active { transform: scale(0.96); }
/* En el primer paso no hay a dónde volver: se oculta con visibility (no
   display) para que los puntos y "Siguiente" no salten de sitio al
   aparecer/desaparecer entre pasos (ver showTutorialStep en app.js). */
.tutorial-back[data-hidden="true"] { visibility: hidden; }
/* FIX (reportado en iPhone real: "Siguiente" se salía de la pantalla):
   con 20 pasos (tutorial de adultos, desde que se añadió el bloque de
   Filtros/Capas) 20 puntos ya no caben en esta fila junto a Atrás/
   Siguiente en un móvil estrecho. flex:1+min-width:0+overflow:hidden es
   la red de seguridad para la versión de puntos (tutorial de niños, solo
   4 pasos); la versión .-counter (ver showTutorialStep en app.js) es la
   que realmente evita el problema, con un ancho fijo pequeño pase lo que
   pase cuántos pasos haya. */
.tutorial-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
.tutorial-dots span {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-border);
}
.tutorial-dots span.-active { background: #F5B942; }
.tutorial-dots.-counter {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-text-soft);
  font-variant-numeric: tabular-nums;
}
.tutorial-next {
  flex-shrink: 0;
  border: 0;
  background: #F5B942;
  color: #1a1300;
  font-weight: 800;
  font-size: 13px;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.tutorial-next:active { transform: scale(0.96); }

/* ============================================================
   Patrocinios: pin de mapa, tarjeta en la ficha del POI y modal de
   carta/precios. Los datos de cada patrocinador se gestionan desde
   admin/dashboard.html (pestaña "Gestión"), ver PATROCINIOS en app.js.
   ============================================================ */
.sponsor-demo-pin {
  width: 28px;
  height: 28px;
  border-radius: 50% 50% 50% 0;
  background: #B9791F;
  transform: rotate(-45deg);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35);
  border: 2px solid #fff;
}
.sponsor-demo-pin img {
  transform: rotate(45deg);
  width: 20px;
  height: 20px;
  object-fit: cover;
  border-radius: 50%;
}
.sponsor-demo-pin.-pulse { animation: sponsorDemoPulse 900ms ease 3; }
@keyframes sponsorDemoPulse {
  0%, 100% { transform: rotate(-45deg) scale(1); }
  50% { transform: rotate(-45deg) scale(1.35); }
}

.sheet-sponsor-demo {
  margin: 8px 18px 0;
  padding-top: 6px;
  border-top: 1px dashed var(--color-border);
}
.sheet-sponsor-demo .label {
  display: block;
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #8F5C14;
  margin-bottom: 4px;
}
.sheet-sponsor-demo p { margin: 0 0 6px; font-size: 12px; color: var(--color-text); }
/* Botón de las tarjetas bronce/plata (ej.: "Ver en el mapa") -- mismo
   tamaño reducido que los de la tarjeta oro (.cta-col button), "se me hace
   muy grande" aplicaba igual aquí.
   BUG REAL encontrado al verificar por qué no bajaba de tamaño: el atajo
   "font: 600 9px inherit" NO es válido -- "inherit" ahí se intenta usar
   como font-family, y al mezclarlo con un peso/tamaño explícitos el
   navegador descarta la declaración ENTERA en silencio (sin error visible),
   dejando el botón con el tamaño heredado de siempre (16px) pase lo que
   pase con el número que se le ponga. Por eso ningún cambio de tamaño
   anterior se veía -- ni siquiera en el botón "oro", que tenía el mismo
   fallo. Con font-size/font-weight por separado sí se aplica de verdad. */
.sheet-sponsor-demo button {
  font-family: inherit;
  font-weight: 600;
  font-size: 9px;
  border: 1px solid #B9791F;
  color: #8F5C14;
  background: #F6E8CE;
  padding: 5px 8px;
  border-radius: 7px;
  cursor: pointer;
}
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   tarjeta oro más pequeña -- foto, textos y botones reducidos ("se me hace
   muy grande"). BORRAR este comentario si se retira el experimento (el
   tamaño se queda si el cambio se mantiene). */
.sheet-sponsor-demo.-oro {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: #F6E8CE;
  border: 1px solid #B9791F;
  border-radius: 10px;
  padding: 8px;
}
/* FIX (feedback: la etiqueta "Contenido patrocinado" quedaba indentada a la
   derecha de la foto en vez de pegada al borde izquierdo de la tarjeta, y
   la distancia iba suelta en su propia línea bajo el texto): cabecera a
   todo el ancho, label a la izquierda y distancia a la derecha en la misma
   línea -- ver renderSponsorDemoInsert en app.js. */
.sheet-sponsor-demo.-oro .sponsor-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.sheet-sponsor-demo.-oro .sponsor-head .label { margin: 0; }
.sheet-sponsor-demo.-oro .sponsor-body-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
.sheet-sponsor-demo.-oro .photo {
  width: 40px;
  height: 40px;
  border-radius: 7px;
  flex: none;
  background: linear-gradient(135deg, #B9791F, #8F5C14);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.sheet-sponsor-demo.-oro .photo img { width: 100%; height: 100%; object-fit: cover; }
.sheet-sponsor-demo .inline-icon {
  width: 16px;
  height: 16px;
  object-fit: cover;
  border-radius: 50%;
  vertical-align: -3px;
  margin-right: 2px;
}
.sheet-sponsor-demo.-oro .body { flex: 1; min-width: 0; }
.sheet-sponsor-demo.-oro .body p { margin: 0; }
.sheet-sponsor-demo.-oro .dist {
  font-size: 10px;
  font-weight: 600;
  color: #8F5C14;
  flex-shrink: 0;
  white-space: nowrap;
}
/* EXPERIMENTO TEMPORAL — CAPA "COMER Y BEBER" (rama experimento-patrocinios-demo):
   tercera columna junto a la foto y el texto (antes .cta-row iba DEBAJO
   del texto, en su propia fila ancha) -- así los botones aprovechan el
   hueco que quedaba vacío a la derecha en vez de alargar la tarjeta hacia
   abajo. flex:none + width fija para que quepan cómodos en una sola línea
   cada uno sin robarle demasiado ancho al texto. BORRAR este comentario si
   se retira el experimento (el layout se queda si el cambio se mantiene). */
.sheet-sponsor-demo.-oro .cta-col {
  flex: none;
  width: 92px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.sheet-sponsor-demo.-oro .cta-col button {
  width: 100%;
  /* BUG REAL: ver el comentario en .sheet-sponsor-demo button más arriba
     sobre "font: 600 9px inherit" -- mismo fallo aquí, ahora con
     font-family/font-weight/font-size por separado para que de verdad
     tome el tamaño reducido. */
  font-family: inherit;
  font-weight: 600;
  font-size: 9px;
  border: 1px solid #B9791F;
  color: #8F5C14;
  background: #FFFDF8;
  padding: 5px 4px;
  border-radius: 7px;
  cursor: pointer;
  white-space: nowrap;
}
.sheet-sponsor-demo.-oro .cta-col button:last-child {
  color: #fff;
  background: #8F5C14;
  border-color: #8F5C14;
}

.sponsor-demo-menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: rgba(35, 41, 31, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
/* BUG REAL: sin esto, display:flex de arriba gana siempre a la propiedad
   nativa [hidden] del navegador -- el modal quedaba con hidden="true" pero
   seguía pintándose en pantalla (mismo tipo de bug ya visto con
   .app-menu-level[hidden]). */
.sponsor-demo-menu-overlay[hidden] { display: none; }
@media (min-width: 620px) {
  .sponsor-demo-menu-overlay { align-items: center; }
}
.sponsor-demo-menu-card {
  position: relative;
  width: 100%;
  max-width: 380px;
  max-height: 80vh;
  overflow-y: auto;
  background: #FFFDF8;
  border-radius: 18px 18px 0 0;
  padding: 22px 20px 20px;
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.3);
}
@media (min-width: 620px) {
  .sponsor-demo-menu-card { border-radius: 18px; }
}
.sponsor-demo-menu-card.-pdf { max-width: 480px; max-height: 88vh; }
.sponsor-demo-menu-body iframe {
  width: 100%;
  height: 60vh;
  min-height: 360px;
  border: 1px solid #EFEADA;
  border-radius: 8px;
  margin-bottom: 12px;
  background: #fff;
}
.sponsor-demo-menu-close {
  position: absolute;
  top: 12px;
  right: 12px;
  border: 0;
  background: #F6E8CE;
  color: #8F5C14;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 14px;
  cursor: pointer;
}
.sponsor-demo-menu-head {
  display: flex;
  gap: 12px;
  align-items: center;
  margin: 0 30px 16px 0;
  padding-bottom: 14px;
  border-bottom: 1px dashed #DAD4BE;
}
.sponsor-demo-menu-head .emoji {
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: 10px;
  background: linear-gradient(135deg, #B9791F, #8F5C14);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.sponsor-demo-menu-head .emoji img { width: 100%; height: 100%; object-fit: cover; }
.sponsor-demo-menu-head h3 {
  font-size: 16px;
  margin: 0 0 3px;
  color: #23291F;
}
.sponsor-demo-menu-head p { margin: 0; font-size: 12.5px; color: #5B6156; }
.sponsor-demo-menu-list { list-style: none; margin: 0 0 16px; padding: 0; }
.sponsor-demo-menu-list li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid #EFEADA;
  font-size: 14px;
  color: #23291F;
}
.sponsor-demo-menu-list li:last-child { border-bottom: 0; }
.sponsor-demo-menu-list .price {
  font-weight: 700;
  color: #8F5C14;
  white-space: nowrap;
}
.sponsor-demo-menu-foot {
  margin: 0;
  text-align: center;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #B0A98E;
}

/* Enlace discreto bajo la tarjeta de patrocinio (ver renderSponsorDemoInsert
   en app.js) -- solo se pinta dentro de la app nativa Android, que es la
   única plataforma donde la compra funciona (política de Google Play). */
.sponsor-remove-ads-link {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 10px;
  text-decoration: underline;
  color: var(--color-text-soft);
  text-align: left;
  cursor: pointer;
}

/* Modal "Quitar publicidad" (ver PREMIUM_PURCHASE en app.js) -- mismo
   patrón de overlay que .poi-search-modal, centrado en vez de pegado
   arriba porque aquí no hay teclado que dejar sitio debajo. */
.premium-modal {
  position: fixed;
  inset: 0;
  z-index: 5600;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(5, 7, 13, 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
}
.premium-modal.-open { opacity: 1; visibility: visible; }
.premium-card {
  width: min(92vw, 420px);
  max-height: min(85vh, 85dvh);
  display: flex;
  flex-direction: column;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 18px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  padding: 18px;
  overflow-y: auto;
}
.premium-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.premium-head h3 { margin: 0; font-size: 16px; color: var(--color-text); }
.premium-close {
  border: none;
  background: none;
  color: var(--color-text-soft);
  cursor: pointer;
  padding: 4px;
  line-height: 0;
}
.premium-hint { margin: 0 0 14px; font-size: 12.5px; color: var(--color-text-soft); line-height: 1.4; }
.premium-city-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.premium-city-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--color-surface);
  font-size: 13px;
  color: var(--color-text);
}
.premium-buy-btn, .premium-all-btn, .premium-restore-btn {
  font-family: inherit;
  font-weight: 600;
  font-size: 12px;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
  background: transparent;
  padding: 6px 10px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.premium-all-btn {
  display: block;
  width: 100%;
  background: var(--color-primary);
  color: #1C1300;
  margin-bottom: 8px;
  padding: 10px;
  font-size: 13px;
}
.premium-restore-btn {
  display: block;
  width: 100%;
  border-color: var(--color-border);
  color: var(--color-text-soft);
}
.premium-active-badge { font-size: 11px; font-weight: 700; color: #4ADE80; }
.premium-error { margin: 10px 0 0; font-size: 12px; color: #F87171; text-align: center; }
.premium-unavailable { font-size: 12.5px; color: var(--color-text-soft); text-align: center; padding: 20px 0; }
.device-code-value {
  margin: 4px 0 16px;
  padding: 14px;
  border-radius: 10px;
  background: var(--color-surface);
  border: 1px dashed var(--color-primary);
  color: var(--color-primary);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 4px;
  text-align: center;
}

/* reduce motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
