* {
  box-sizing: border-box;
  margin: 0;
}

:root {
  /* Alto de la barra de secciones. Lo usan el hueco del final de `main` y el
     toast: son dos sitios que TIENEN que moverse juntos, y con dos números
     sueltos uno se queda atrás — pasó, y el aviso salía debajo de la barra. */
  --alto-barra: 70px;
  --fondo: #1b1c1e;
  --superficie: #212225;
  --texto: #e8eaec;
  --texto-suave: #8d9297;
  --primario: #a5ff2e;
  --error: #f09595;
  /* Ámbar de aviso. Se usa en un solo sitio: el número de caídas de un aparato
     que se cae seguido. No es un fallo —vuelve solo— pero hay que mirarlo, y
     el rojo ya está diciendo otra cosa en la misma ficha. */
  --aviso: #f0c674;
  --fuego: #ff8c3a;
  --logo: #a5ff2e;
  --sombra-oscura: rgba(0, 0, 0, 0.55);
  --sombra-clara: rgba(255, 255, 255, 0.05);
}

body {
  font-family: "Josefin Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--fondo);
  color: var(--texto);
  min-height: 100vh;
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Con viewport-fit=cover el contenido llega a los bordes; se respeta el área
     segura de arriba (notch) y los lados. */
  padding: calc(18px + env(safe-area-inset-top)) calc(22px + env(safe-area-inset-right)) 18px calc(22px + env(safe-area-inset-left));
  background: transparent;
  max-width: 640px;
  margin: 0 auto;
}

header h1 {
  font-family: "Fredoka", sans-serif;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.1;
  color: var(--logo);
}

.logo {
  color: var(--texto);
}

.marca {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.marca:active {
  opacity: 0.6;
}

.logo {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
}

.subtitulo-marca {
  display: block;
  font-size: 12px;
  color: var(--texto-suave);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40vw;
}

#info-usuario {
  font-size: 13px;
  color: var(--texto-suave);
  min-width: 0;
  flex-shrink: 1;
  text-align: right;
}

#nombre-usuario {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 42vw;
  display: inline-block;
  vertical-align: bottom;
}

main {
  max-width: 640px;
  margin: 0 auto;
  /* Sin zona muerta grande: el dimmer ahora es horizontal y no pelea con el
     gesto de inicio de iOS. Solo se respeta el área segura del home bar. */
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.centrado {
  text-align: center;
  color: var(--texto-suave);
  padding: 32px 0;
}

/* "Tómate la foto", arriba del todo en el panel. Con el filo verde de la marca:
   no es un error ni un problema, es algo que le conviene hacer. Se va solo en
   cuanto la manda, así que puede permitirse ocupar sitio mientras esté. */
.aviso-cara {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 14px 16px;
  margin-bottom: 16px;
  border-radius: 14px;
  background: var(--fondo);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara),
    inset 3px 0 0 var(--primario);
}

.aviso-cara-titulo {
  font-family: "Fredoka", sans-serif;
  font-size: 16px;
  font-weight: 600;
  color: var(--texto);
}

.aviso-cara-texto {
  font-size: 13px;
  color: var(--texto-suave);
}

/* El texto alineado a la izquierda —se lee— y la acción centrada, que es lo
   que la convierte en la acción de la tarjeta y no en una nota más de la
   columna. */
.aviso-cara .btn-secundario {
  align-self: center;
  margin-top: 6px;
  padding: 9px 18px;
  font-size: 14px;
}

/* Lo que ve quien no tiene ningún botón. Centrado y con aire: es toda la
   pantalla que tiene esa persona, no una nota al pie. */
.sin-controles {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  color: var(--texto-suave);
  padding: 48px 12px;
}

.sin-controles-titulo {
  font-family: "Fredoka", sans-serif;
  font-size: 22px;
  font-weight: 600;
  color: var(--texto);
}

.sin-controles .btn-primario {
  margin-top: 10px;
  min-width: 190px;
}

.tarjeta {
  background: var(--superficie);
  border: none;
  border-radius: 22px;
  padding: 26px 22px;
  margin-bottom: 16px;
  box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara);
}

.aviso h2 {
  font-size: 17px;
  margin-bottom: 8px;
}

.aviso p {
  color: var(--texto-suave);
  line-height: 1.6;
  margin-bottom: 12px;
}

code {
  background: var(--fondo);
  border-radius: 6px;
  padding: 1px 6px;
  font-size: 13px;
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
}

#form-login {
  max-width: 380px;
  margin: 6vh auto 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Con la lista de invitados visible, separar la de dispositivos: pegadas se
   leen como un solo grupo de casillas. */
#pase-invitados-lista:not(.oculto) + #pase-dispositivos {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

/* Cómo entra cada vecino (Google / clave), en la lista de gestión. */
.como-entra {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--texto-suave);
  font-size: 11px;
  font-style: normal;
  white-space: nowrap;
}

/* Punto de conexión de cada dispositivo en la lista de gestión. */
.punto-con {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 8px;
  vertical-align: middle;
  flex: 0 0 auto;
}

.punto-con.con-ok { background: var(--primario); }
.punto-con.con-mal { background: var(--error); }

/* Apila los botones: aquí conviven "Continuar" y el opcional de Google. */
#form-email {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cabecera-login {
  text-align: center;
  margin-bottom: 14px;
}

#form-login h2 {
  font-family: "Fredoka", sans-serif;
  font-size: 34px;
  font-weight: 600;
  letter-spacing: 0;
  margin: 12px 0 2px;
  color: var(--logo);
}

.logo-login {
  width: 68px;
  height: 68px;
  color: var(--texto);
  /* El ícono es un squircle sobre negro puro: al recortarlo cuadrado quedaban
     las esquinas negras marcadas contra la tarjeta (más clara). Se redondean
     para que se funda con el fondo. */
  border-radius: 23%;
}

.subtitulo-login {
  font-size: 13px;
  color: var(--texto-suave);
}

/* Aviso del evento al invitado (pantallas del flujo de pase). */
.pase-evento-info {
  text-align: center;
  color: var(--texto-suave);
  font-size: 14px;
  margin: -4px 0 14px;
}
.pase-evento-info strong {
  color: var(--primario);
  font-weight: 600;
}


#form-login input::placeholder,
#form-registro input::placeholder,
#form-email input::placeholder {
  color: var(--texto-suave);
}

.campo-clave {
  position: relative;
}

.campo-clave input {
  padding-right: 50px;
}

.btn-ver-clave {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 8px;
  width: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--texto-suave);
  -webkit-tap-highlight-color: transparent;
}

.btn-ver-clave.viendo {
  color: var(--primario);
}

.enlace-clave {
  background: transparent;
  color: var(--texto-suave);
  font-size: 13px;
  padding: 6px;
  text-align: center;
  -webkit-tap-highlight-color: transparent;
}

.enlace-clave:active {
  color: var(--primario);
}

#form-login input,
#form-registro input,
#form-email input,
#form-perfil input,
#form-perfil select,
#form-clave input,
#form-skin input,
#form-skin select,
#form-skin textarea,
.campo input,
.campo select,
.campo textarea,
/* Por CLASE y no por el id del formulario que lo contenga: esta lista de ids
   hay que acordarse de ampliarla, y no acordarse no falla ruidosamente — el
   campo sale con el estilo de fábrica del navegador, blanco y cuadrado en medio
   de una app oscura. Pasó al sacar el editor de diseños del taller y ponerlo en
   la galería: el mismo `.campo-perfil`, un contenedor distinto, y los campos se
   volvieron blancos. Con la clase, el estilo viaja con el componente.
   (Ojo: `.campo-perfil` no envuelve casillas ni radios en ningún sitio; si
   algún día lo hace, hay que excluirlas como se hizo con `.casilla input`.) */
.campo-perfil input,
.campo-perfil select,
.campo-perfil textarea {
  padding: 13px 14px;
  border: none;
  border-radius: 12px;
  background: var(--fondo);
  color: var(--texto);
  font-size: 16px;
  /* Los campos de formulario NO heredan la fuente: el navegador les pone la
     suya. Estaba puesto en `.lote-fila input` y en ningún otro sitio, así que
     lo que escribía el vecino salía en la fuente del sistema mientras la
     etiqueta de encima iba en la de ViYi. Se ve de lejos en un desplegable,
     que es texto ya escrito y no un hueco vacío. */
  font-family: inherit;
  margin-bottom: 8px;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  outline: none;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
}

#form-login input:focus,
#form-registro input:focus,
#form-email input:focus,
#form-perfil input:focus,
#form-perfil select:focus,
#form-clave input:focus,
#form-skin input:focus,
#form-skin select:focus,
#form-skin textarea:focus,
.campo input:focus,
.campo select:focus {
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara), 0 0 0 1.5px var(--primario);
}

#form-perfil input::placeholder,
#form-clave input::placeholder {
  color: var(--texto-suave);
}

/* ---- Mi perfil ---- */
#info-usuario {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
#info-usuario:active #nombre-usuario {
  color: var(--primario);
}

#form-perfil, #form-clave {
  display: flex;
  flex-direction: column;
}
.perfil-seccion h3 {
  font-size: 15px;
  font-weight: 600;
  color: var(--texto);
  margin-bottom: 12px;
}

/* Botón que despliega una sección (p. ej. cambiar clave), con chevron. */
.btn-desplegar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: transparent;
  color: var(--texto);
  font-size: 15px;
  font-weight: 600;
  padding: 2px 0;
  -webkit-tap-highlight-color: transparent;
}
.btn-desplegar::after {
  content: '';
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--texto-suave);
  border-bottom: 2px solid var(--texto-suave);
  transform: rotate(-45deg);
  transition: transform 0.2s ease;
}
.btn-desplegar[aria-expanded="true"]::after {
  transform: rotate(45deg);
}
.form-desplegable {
  margin-top: 14px;
}

.campo-perfil {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12.5px;
  color: var(--texto-suave);
  margin-bottom: 6px;
}
#perfil-email:disabled {
  opacity: 0.6;
}

.perfil-seccion {
  margin: 10px 0 6px;
}
/* Inmuebles del perfil: solo lectura (los asigna el admin). Lista vertical
   limpia: nombre + tipo por ítem. */
.lista-inmuebles {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 4px;
  /* 14px = mismo sangrado que el texto dentro de los inputs, para que los
     inmuebles alineen con los valores (Eduardo, Jimenez…), no con los labels. */
  padding: 0 0 0 14px;
  list-style: none;
}
.inmueble-ro {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.inmueble-ro strong {
  color: var(--texto);
  font-size: 14px;
  font-weight: 600;
}

.campo input:disabled {
  opacity: 0.5;
}

button {
  font-size: 16px;
  border: none;
  cursor: pointer;
  color: var(--texto);
  background: var(--superficie);
}

button:disabled {
  opacity: 0.55;
  cursor: wait;
}

.btn-primario {
  border-radius: 14px;
  padding: 14px 18px;
  min-height: 50px;
  font-weight: 600;
  color: var(--primario);
  box-shadow: 6px 6px 14px var(--sombra-oscura), -6px -6px 14px var(--sombra-clara);
}

.btn-primario:not(:disabled):active {
  box-shadow: inset 5px 5px 10px var(--sombra-oscura), inset -5px -5px 10px var(--sombra-clara);
}

.btn-secundario {
  border-radius: 12px;
  padding: 8px 16px;
  min-height: 38px;
  font-size: 14px;
  color: var(--texto-suave);
  box-shadow: 4px 4px 10px var(--sombra-oscura), -4px -4px 10px var(--sombra-clara);
}

.btn-secundario:not(:disabled):active {
  box-shadow: inset 4px 4px 8px var(--sombra-oscura), inset -4px -4px 8px var(--sombra-clara);
}

/* Botón solo-icono (p. ej. refrescar): cuadrado, sin texto. */
.btn-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  line-height: 0;
}

.mensaje-error {
  color: var(--error);
  font-size: 14px;
}

.mensaje-ok {
  color: var(--primario);
  font-size: 14px;
}

.izq {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.btn-menu {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 12px;
  color: var(--texto);
  background: var(--superficie);
  box-shadow: 4px 4px 10px var(--sombra-oscura), -4px -4px 10px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}

.btn-menu:active {
  box-shadow: inset 4px 4px 8px var(--sombra-oscura), inset -4px -4px 8px var(--sombra-clara);
}

.backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0, 0, 0, 0.55);
}

/* Barra de secciones abajo: la navegación de diario, siempre a la vista.
   Estaba en un menú hamburguesa y de ahí salió el problema del Locker —nadie
   lo encontraba porque la navegación estaba escondida—. Lo que se usa todos los
   días vive aquí; el resto sigue en el lateral, detrás de "Más".
   `env(safe-area-inset-bottom)` para que en un iPhone no quede bajo la barra
   del sistema. */
.barra-abajo {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  display: flex;
  background: var(--superficie);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.35);
}

.item-barra {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px 9px;
  border: none;
  background: none;
  color: var(--texto-suave);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* Los botones de la barra llevan también `item-menu` —así heredan el cambio de
   pestaña y la marca de activo sin duplicar código— pero NO su aspecto: esa
   clase pinta un bloque en relieve pensado para el menú lateral, y dentro de la
   barra salía un recuadro hundido alrededor de la sección abierta. Aquí lo
   único que marca es el color. */
.barra-abajo .item-menu {
  text-align: center;
  border-radius: 0;
  padding: 10px 4px 9px;
  font-size: 11px;
  background: none;
  box-shadow: none;
}
.item-barra.activa,
.barra-abajo .item-menu.activa { color: var(--primario); box-shadow: none; }
.item-barra:active { opacity: 0.6; }

/* Sitio para la barra: sin esto lo último de cada pantalla queda debajo. */
main { padding-bottom: calc(var(--alto-barra) + 6px + env(safe-area-inset-bottom)); }

.menu-lateral {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 250px;
  max-width: 80vw;
  z-index: 50;
  background: var(--fondo);
  padding: calc(34px + env(safe-area-inset-top)) 16px calc(22px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
  gap: 10px;
  transform: translateX(-105%);
  transition: transform 0.28s ease;
}

.menu-lateral.abierto {
  transform: translateX(0);
  box-shadow: 12px 0 28px var(--sombra-oscura);
}

.item-menu {
  text-align: left;
  border-radius: 14px;
  padding: 14px 18px;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto-suave);
  background: var(--superficie);
  box-shadow: 5px 5px 12px var(--sombra-oscura), -5px -5px 12px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}

.item-menu.activa {
  color: var(--primario);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}

.item-salir {
  color: var(--error);
}

.dps-detectados {
  margin-top: 8px;
  font-size: 12px;
  color: var(--texto-suave);
  line-height: 1.55;
  word-break: break-word;
}

.titulo-grupo {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--texto-suave);
  text-align: center;
  margin: 34px 4px 22px;
}

/* Acceso del invitado: una fila por dispositivo con su tiempo restante. */
/* Encabezado por pase en la tarjeta de invitado: evento + quién invitó. */
.acceso-cab {
  padding: 4px 2px 6px;
}
.acceso-cab:not(:first-child) {
  margin-top: 10px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.acceso-cab strong {
  display: block;
  color: var(--primario);
  font-size: 16px;
  font-weight: 600;
}
.acceso-invitador {
  display: block;
  color: var(--texto-suave);
  font-size: 13px;
  margin-top: 2px;
}

.acceso-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.acceso-fila:last-child {
  border-bottom: none;
}

.acceso-nombre {
  font-size: 15px;
  font-weight: 500;
  color: var(--texto);
}

.acceso-reloj {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--primario);
  white-space: nowrap;
}

.acceso-reloj svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
}

.acceso-reloj.urgente {
  color: var(--fuego);
}

.acceso-reloj.vencido {
  color: var(--error);
}

/* Perilla giratoria (dimmer) */
.perilla {
  position: relative;
  width: 132px;
  height: 132px;
  /* pan-x deja que el swipe horizontal deslice el carrusel; el resto del gesto
     (vertical/rotatorio) y los toques operan el dial. */
  touch-action: pan-x;
  -webkit-tap-highlight-color: transparent;
  cursor: grab;
}

.perilla:active {
  cursor: grabbing;
}

.perilla-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.perilla-track {
  fill: none;
  stroke: #2c2e33;
  stroke-width: 11;
  stroke-linecap: round;
}

.perilla-nivel {
  fill: none;
  stroke: var(--primario);
  stroke-width: 11;
  stroke-linecap: round;
  filter: drop-shadow(0 0 5px rgba(165, 255, 46, 0.5));
}

.perilla:not(.encendido) .perilla-nivel {
  opacity: 0;
}

.perilla-centro {
  position: absolute;
  inset: 22px;
  border-radius: 50%;
  background: var(--superficie);
  box-shadow: 7px 7px 16px var(--sombra-oscura), -7px -7px 16px var(--sombra-clara);
  display: flex;
  align-items: center;
  justify-content: center;
}

.perilla-indicador {
  position: absolute;
  inset: 0;
  transform-origin: 50% 50%;
  transform: rotate(-135deg);
  pointer-events: none;
}

.perilla-indicador::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  width: 7px;
  height: 7px;
  margin-left: -3.5px;
  border-radius: 50%;
  background: var(--texto-suave);
}

.perilla.encendido .perilla-indicador::before {
  background: var(--primario);
  box-shadow: 0 0 6px rgba(165, 255, 46, 0.7);
}

.perilla-valor {
  font-size: 23px;
  font-weight: 600;
  color: var(--texto-suave);
}

.perilla.encendido .perilla-valor {
  color: var(--primario);
}

.perilla-accion {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--texto-suave);
}

.perilla.encendido .perilla-accion {
  color: var(--primario);
}

.perilla-enviando {
  animation: pulso-envio 0.9s ease-in-out infinite;
}

/* Termostato: perilla de temperatura + actual + modos. */
.termostato {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.termo-objetivo {
  font-size: 24px;
  font-weight: 600;
  color: var(--primario);
}

/* Temperatura actual al lado del nombre del termostato. */
.termo-temp {
  color: var(--texto-suave);
  font-weight: 400;
}

.termo-modos {
  display: flex;
  gap: 8px;
}

.termo-modo {
  border: none;
  border-radius: 10px;
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--texto-suave);
  background: var(--fondo);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
  cursor: pointer;
}

.termo-modo[data-modo="off"].activa {
  color: var(--texto);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara), inset 0 0 0 1.5px var(--texto-suave);
}

.termo-modo[data-modo="heat"].activa {
  color: var(--fuego);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara), inset 0 0 0 1.5px var(--fuego);
}

.termo-modo[data-modo="cool"].activa {
  color: #5ec8ff;
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara), inset 0 0 0 1.5px #5ec8ff;
}

/* El arco, el número y el indicador se tiñen según el modo activo. */
.perilla-termo.modo-heat .perilla-nivel { stroke: var(--fuego); filter: drop-shadow(0 0 5px rgba(255, 140, 58, 0.5)); }
.perilla-termo.modo-heat .termo-objetivo { color: var(--fuego); }
.perilla-termo.modo-heat .perilla-indicador::before { background: var(--fuego); box-shadow: 0 0 6px rgba(255, 140, 58, 0.7); }

.perilla-termo.modo-cool .perilla-nivel { stroke: #5ec8ff; filter: drop-shadow(0 0 5px rgba(94, 200, 255, 0.5)); }
.perilla-termo.modo-cool .termo-objetivo { color: #5ec8ff; }
.perilla-termo.modo-cool .perilla-indicador::before { background: #5ec8ff; box-shadow: 0 0 6px rgba(94, 200, 255, 0.7); }

.perilla-termo.modo-off .perilla-nivel { stroke: var(--texto-suave); filter: none; opacity: 0.45; }
.perilla-termo.modo-off .termo-objetivo { color: var(--texto-suave); }
.perilla-termo.modo-off .perilla-indicador::before { background: var(--texto-suave); box-shadow: none; }

.grupo-controles {
  display: flex;
  flex-wrap: wrap;
  gap: 30px 34px;
  justify-content: center;
  align-items: flex-start;
}

.control {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.etiqueta-control {
  font-size: 14px;
  color: var(--texto-suave);
  text-align: center;
  max-width: 150px;
}

/* Dimmer a lo ancho con slider horizontal (fuera del carrusel). */
.control-dimmer {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--superficie);
  border-radius: 18px;
  padding: 16px 18px;
  margin: 0 auto 14px;
  max-width: 380px;
  box-shadow: 6px 6px 14px var(--sombra-oscura), -6px -6px 14px var(--sombra-clara);
}

.dimmer-cab {
  display: flex;
  align-items: center;
  gap: 10px;
}

.dimmer-icono {
  display: flex;
  padding: 2px;
  background: transparent;
  border: none;
  color: var(--texto-suave);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.2s;
}

.dimmer-icono svg {
  width: 26px;
  height: 26px;
}

.dimmer-nombre {
  flex: 1;
  font-size: 15px;
  color: var(--texto);
}

.dimmer-valor {
  font-size: 14px;
  color: var(--texto-suave);
  font-variant-numeric: tabular-nums;
}

.control-dimmer.encendido .dimmer-icono,
.control-dimmer.encendido .dimmer-valor {
  color: var(--primario);
}

.dimmer-pista {
  position: relative;
  height: 44px;
  border-radius: 14px;
  background: var(--fondo);
  overflow: hidden;
  cursor: pointer;
  touch-action: pan-y;
  -webkit-tap-highlight-color: transparent;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}

.dimmer-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-radius: 14px;
  background: var(--primario);
}

/* --- Pilder: tablero industrial con palanca -------------------------------
   Dos fotos del mismo panel cruzándose por opacidad: apagado (palanca abajo,
   piloto y barra oscuros) y encendido (palanca arriba, todo prendido).
   El fondo negro del render está transparentado, así que el panel se apoya en
   el fondo de la app en vez de traer su propio recuadro negro. */
.pilder {
  /* Escala calculada para que el panel mida lo MISMO de alto que un botón
     redondo con su aro (168 + 26×2 = 220). En una fila la altura la manda el
     control más alto, así que un Pilder de 249 dejaba 29px de hueco debajo de
     todos los demás — y con el Pilder fuera de pantalla en un carrusel, ese
     hueco aparecía sin culpable a la vista. 220/442 = 0.4977. */
  --pilder-s: 0.4977;
  position: relative;
  width: calc(280px * var(--pilder-s));
  height: calc(442px * var(--pilder-s));
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.pilder-capa {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  /* CHASQUEA, no se funde. Medido: entre las dos fotos cambia el 57% del panel
     y repartido por TODO él, no solo la palanca y los pilotos — son dos renders
     distintos y en el encendido la luz roja salpica la plancha entera. Con un
     fundido de 0.18s las dos texturas conviven durante ese rato y el panel
     parece vibrar. Un interruptor de verdad tampoco se funde: salta.
     Quedan 70ms para que el salto no sea un corte seco. */
  transition: opacity 0.07s linear;
}
.pilder-off { background-image: url('pilder-off.webp?v=2'); }
.pilder-on  { background-image: url('pilder-on.webp?v=2'); opacity: 0; }

/* Encendido = la palanca arriba, pero por motivos distintos según el control.
   En un INTERRUPTOR manda solo `activo`: es el estado del aparato.
   En una PUERTA mandan `enviando` y `exito` — sube al tocar y baja cuando pasan
   los segundos de apertura.
   ⚠️ `enviando` iba antes en la regla común y valía para los dos, y eso rompía
   el apagado de un interruptor: al tocar se quitaba `activo` pero `enviando`
   mantenía la palanca arriba hasta que el aparato contestaba, así que el
   chasquido sonaba y la palanca bajaba un segundo después. Solo se notaba al
   bajar, porque al subir las dos clases dicen lo mismo. */
/* ⚠️ En modo pulso la palanca la manda `alzada`, NO `exito`.
   `exito` dura lo que dure la puerta abierta, y con un portón puesto en 15
   segundos la palanca se quedaba arriba los 15 — una palanca es momentánea:
   la empujas y vuelve sola. Que la puerta siga abierta lo dice el botón, no
   la palanca. `alzada` la baja al chasquido, sin esperar a la puerta. */
.pilder.activo .pilder-on,
.pilder-pulso.alzada .pilder-on { opacity: 1; }

/* Sin encoger al pulsar. Lo tenía y era el otro candidato a "vibra": un
   `transform` sin transición que iOS enciende y apaga con los micromovimientos
   del dedo apoyado. Y no hace falta — la palanca que sube y el chasquido ya son
   toda la respuesta al toque. */

/* La muestra del Locker: el panel entero no cabe en un círculo de 58px, así
   que se encuadra en la palanca, que es lo que lo identifica. */
.skin-muestra.muestra-pilder {
  background-image: url('pilder-on.webp?v=2');
  background-size: 190%;
  background-position: 50% 46%;
}

.boton-circular {
  /* Por lo mismo de `.skin-muestra`: este botón hospeda absolutos (la foto, el
     aro de Hal, el nombre en el anillo). Hoy funcionan porque van siempre con
     `.boton-imagen`, que sí se posiciona — o sea, por acompañamiento. Aquí se
     asegura solo. */
  position: relative;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #a9afb5;
  background: var(--superficie);
  box-shadow: 9px 9px 18px var(--sombra-oscura), -9px -9px 18px var(--sombra-clara);
  transition: color 0.25s, box-shadow 0.15s;
  -webkit-tap-highlight-color: transparent;
  /* Mantener pulsado un botón lo viste, y sin esto ese gesto no existe en el
     iPhone: el botón lleva un <img> dentro, así que iOS responde a la pulsación
     larga con su propio menú ("Guardar imagen", "Copiar") y se lleva el gesto.
     `user-select` va aparte porque en los botones sin foto lo que sale es la
     lupa de seleccionar texto. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.boton-circular img { -webkit-touch-callout: none; pointer-events: none; }

.boton-circular:not(:disabled):active {
  box-shadow: inset 7px 7px 14px var(--sombra-oscura), inset -7px -7px 14px var(--sombra-clara);
}

.boton-circular.con-nombre {
  flex-direction: column;
  gap: 8px;
}

.nombre-boton {
  font-size: 13px;
  color: var(--texto-suave);
  text-align: center;
  line-height: 1.15;
  max-width: 82%;
  padding: 0 4px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.boton-circular.medio.con-nombre {
  gap: 5px;
}

.boton-circular.medio.con-nombre .nombre-boton {
  font-size: 10.5px;
}

.anillo {
  position: relative;
  border-radius: 50%;
  padding: 26px;
  background: var(--superficie);
  box-shadow: inset 7px 7px 16px var(--sombra-oscura), inset -7px -7px 16px var(--sombra-clara);
}

.boton-circular.grande {
  width: 168px;
  height: 168px;
}

/* --- Botón de un acceso con evento --------------------------------------
   El pase traía un título ("Cumpleaños de Ana"), y ese título es la identidad
   del botón mientras el pase viva: cinta con el nombre del evento encima, color
   propio del título (mismo evento, mismo color) en el icono y en un aro suave.
   Sin imagen y sin costo. Se va solo cuando el acceso vence, porque el
   dispositivo entero desaparece del panel. */
.control.evento { position: relative; }
.evento-titulo {
  font-size: 13px;
  font-weight: 600;
  color: var(--evento, var(--primario));
  text-align: center;
  max-width: 150px;
  line-height: 1.2;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* El icono NO se tiñe: se queda con su gris. Teñirlo sería inconsistente
   —los iconos con color propio (portón, ascensor) no obedecen y el candado sí—
   y además chocaría con el verde que toma el icono al abrirse. El color del
   evento lo cargan el aro y el título, que basta y sobra. */
/* Los dos tipos de pase como acciones. El de una persona es el habitual, así
   que se lleva el relieve principal; el de varias va secundario al lado. */
.pase-acciones {
  display: flex;
  align-items: center;
  gap: 10px;
}
.pase-acciones .btn-secundario { flex: 1; margin: 0; }
/* El tipo elegido se hunde y se pone verde, como todo lo elegido en la app.
   `.btn-secundario` no traía estado de activo —nunca lo había necesitado— y sin
   esto elegías y no se notaba. */
.pase-acciones [data-tipo].activa {
  color: var(--primario);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}
/* El que de verdad crea el enlace: a lo ancho y debajo de los dos, y no está
   hasta que eliges el tipo. Con los dos tipos generando directo, un roce creaba
   un acceso a tu puerta — preocupación del usuario, y con razón. */
.pase-generar {
  display: block;
  width: 100%;
  margin: 14px 0 0;
}

/* "Opciones" no es un encabezado: es una puerta discreta a lo que casi nunca
   se toca. En negrita competía con Generar, que sí es la acción, y sin aire
   quedaba pegada a él. */
#btn-pase-opciones {
  font-weight: 500;
  color: var(--texto-suave);
  padding: 6px 0;
  margin-bottom: 16px;
}

/* "Indefinido" pasa a ser el símbolo de infinito: una sola palabra no le
   bastaba y ninguna cabía sin partirse. El símbolo pide más cuerpo que el texto
   de al lado para leerse a la misma distancia, y `aria-label` conserva la
   palabra para quien no ve el glifo. */
.chip-infinito {
  font-size: 22px;
  line-height: 1;
  padding-top: 6px;
  padding-bottom: 6px;
}

/* --- El billete de acceso ------------------------------------------------
   El mismo para el que lo genera y para el invitado. Formal a propósito: es un
   documento de acceso, no un adorno — rótulos en mayúsculas espaciadas, el
   evento grande, y un troquel entre lo que ES y sus condiciones. */
.billete {
  position: relative;
  border-radius: 18px;
  padding: 20px 20px 18px;
  background: var(--fondo);
  box-shadow: inset 4px 4px 10px var(--sombra-oscura), inset -4px -4px 10px var(--sombra-clara);
  margin-bottom: 14px;
}

.billete-rotulo {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--texto-suave);
}

.billete-evento {
  display: block;
  margin-top: 6px;
  font-size: 22px;
  line-height: 1.2;
  color: var(--texto);
  overflow-wrap: anywhere;
}

/* El troquel: la línea de puntos y las dos muescas laterales. Es lo que hace
   que se lea como un billete y no como otra tarjeta más. */
.billete-cuerpo {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px dashed rgba(255, 255, 255, 0.14);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 14px 18px;
}
.billete-cuerpo::before,
.billete-cuerpo::after {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--superficie);
  margin-top: -28px;
}
.billete-cuerpo::before { left: -9px; }
.billete-cuerpo::after { right: -9px; }

.billete-valor {
  display: block;
  margin-top: 3px;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto);
  overflow-wrap: anywhere;
}

.billete-pie {
  margin: 14px 0 0;
  font-size: 12px;
  color: var(--texto-suave);
}

/* Los aparatos como TARJETAS cuando son pocos: icono arriba y nombre debajo,
   con una ya puesta. Se reconocen sin leer —el icono es el mismo del botón
   real— y elegir es un toque en algo que se ve, no marcar una casilla dentro de
   un desplegable. */
/* Rejilla, no fila envuelta: con tres o cuatro aparatos daba igual, pero al
   pasar de seis la última fila quedaba con una ficha estirada al doble de
   ancho que las de arriba. En rejilla todas miden lo mismo aunque la última
   fila vaya sola. `grid-auto-rows: 1fr` iguala el alto de TODAS las filas a la
   más alta, así un nombre que parte en dos líneas no deja un escalón. */
.pase-fichas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  grid-auto-rows: 1fr;
  gap: 10px;
  margin-bottom: 16px;
}

.ficha-disp {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 10px;
  border: none;
  border-radius: 16px;
  background: var(--superficie);
  color: var(--texto-suave);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 5px 5px 12px var(--sombra-oscura), -5px -5px 12px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}
/* La elegida se HUNDE, como el resto de la app: en un lenguaje de relieve,
   "pulsado" es hacia dentro, no un color de fondo. */
.ficha-disp.activa {
  color: var(--primario);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}
.ficha-icono svg { width: 26px; height: 26px; display: block; }
.ficha-nom {
  text-align: center;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* Duración: fichas envueltas, todas a la vista. */
.dur-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}
/* Etiqueta de bloque: nombra el grupo cuando su contenido solo no lo dice.
   "3 h" suelto no dice si es lo que dura o cuándo empieza. */
.pase-etiqueta {
  display: block;
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--texto-suave);
}

/* Los accesos de administración dentro del perfil: mismo bloque en relieve del
   menú de antes, pero apilados y a lo ancho de la tarjeta. */
#seccion-admin {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#seccion-admin .item-menu { width: 100%; }

/* Los accesos de administración dentro del perfil: mismo bloque en relieve del
   menú de antes, pero apilados y a lo ancho de la tarjeta. */
#seccion-admin {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#seccion-admin .item-menu { width: 100%; }

/* Pase programado que todavía no empieza: el control se ve, pero apagado y con
   su hora debajo. Enseñarlo apagado y no esconderlo es a propósito — el
   invitado tiene que saber que ese acceso EXISTE y cuándo servirá; un botón que
   aparece de la nada a las ocho es peor que uno que espera a la vista. */
.control.aun-no .boton-circular,
.control.aun-no .perilla,
.control.aun-no .pilder,
.control.aun-no .rueda-ctrl {
  opacity: 0.38;
  filter: grayscale(0.7);
  pointer-events: none;
}
.aun-no-texto {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--primario);
  text-align: center;
}

/* Aparato caído: se atenúa para que se vea que no está fino, pero MENOS que
   `aun-no` y SIN quitarle el toque —el dato puede estar equivocado y la puerta
   igual abrir, así que el botón sigue vivo—. El aviso va debajo, como el de
   "aún no", pero en rojo: no es una espera, es una avería. */
.control.sin-conexion .boton-circular,
.control.sin-conexion .perilla,
.control.sin-conexion .pilder,
.control.sin-conexion .rueda-ctrl {
  opacity: 0.55;
  filter: grayscale(0.55);
}
.sin-conexion-texto {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--error);
  text-align: center;
}

/* Pila baja del sensor de una puerta: bajo el botón, en ámbar. Es un aviso a
   tiempo, no una avería —de ahí el ámbar y no el rojo—: cámbiala antes de que
   el sensor se muera y el indicador parezca roto. */
.pila-baja {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--aviso);
  text-align: center;
}

/* Aviso personal del admin, arriba de los botones del vecino. Una tarjeta con
   el filo verde de la marca: es un recado, no una alarma. */
.aviso-personal {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 16px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--fondo);
  color: var(--texto);
  font-size: 15px;
  line-height: 1.4;
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara),
    inset 3px 0 0 var(--primario);
}
.aviso-personal-foto {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 2px 2px 5px var(--sombra-oscura), -2px -2px 5px var(--sombra-clara);
}
.aviso-personal-texto { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.aviso-personal-linea { display: block; }
/* La firma: el nombre de quien manda el recado, en su propia línea, más grande
   y en el verde de la marca. */
.aviso-personal-firma {
  display: block;
  margin-top: 4px;
  color: var(--primario);
  font-size: 1.15em;
  font-weight: 700;
}

/* Con foto: tarjeta grande y apilada, la cara en grande arriba y el recado
   debajo, centrado. Es la variante que pide una bienvenida o un aviso con
   peso; la de solo texto sigue compacta. */
.aviso-personal.con-foto {
  flex-direction: column;
  text-align: center;
  gap: 18px;
  padding: 26px 20px;
  font-size: 19px;
  font-weight: 600;
}
.aviso-personal.con-foto .aviso-personal-foto {
  width: min(66vw, 260px);
  height: min(66vw, 260px);
}

/* Aro del color del evento, pegado al borde interior del anillo. */
.control.evento .anillo::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  border: 2px solid var(--evento);
  opacity: 0.45;
  pointer-events: none;
}
/* El nombre del dispositivo cede protagonismo: el evento manda. */
.control.evento .etiqueta-control { opacity: 0.7; }

/* Botón con imagen de logo (p. ej. escudo de Argentina) en vez de icono SVG:
   la imagen llena el círculo del botón y el nombre va encima, abajo, con el
   mismo gris y fuente que el resto de botones (sin negrita). */
.boton-imagen {
  position: relative;
  overflow: hidden;
  padding: 0;
}

.boton-logo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: 50%;
  display: block;
  pointer-events: none;
  z-index: 0;
}

/* Aspecto Argentina "camiseta": dos caras que se cruzan. Frente = escudo AFA
   (por defecto); al presionar se revela la cara de atrás (estampita de Messi)
   nítida por un momento y vuelve. La cara de atrás se encuadra hacia la cara. */
.boton-camiseta .boton-logo {
  transition: opacity 0.18s ease;
}
.boton-camiseta .cara-atras {
  object-position: 50% 8%;
  opacity: 0;
}
.boton-camiseta.mostrar-atras .cara-frente {
  opacity: 0;
}
.boton-camiseta.mostrar-atras .cara-atras {
  opacity: 1;
}

/* Aspecto Bordado: el parche gira en sentido horario mientras el botón está
   activo (enviando / éxito). En reposo, quieto. Como el parche está centrado y
   el botón recorta al círculo, gira en su sitio sin mostrar el negro. */
.boton-bordado.enviando .boton-logo,
.boton-bordado.exito .boton-logo {
  animation: girar-bordado 2.5s linear infinite;
}
@keyframes girar-bordado {
  to { transform: rotate(360deg); }
}
/* Aspecto Hal: el ojo cobra vida al activarse. Un resplandor rojo palpita
   alrededor del botón y el propio ojo se enciende más fuerte (brightness),
   como el HAL de la película. En reposo, apagado y quieto. */
.boton-hal .boton-logo {
  transition: filter 0.4s ease;
}
/* Aro tenue en el canto: el lente de HAL es negro sobre fondo casi negro, así
   que sin él el borde se pierde y el botón se lee más chico que los otros
   aspectos. Va como pseudo-elemento para no pelear con el box-shadow animado
   del latido. */
.boton-hal::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.2);
  pointer-events: none;
  z-index: 1;
}
.boton-hal.enviando,
.boton-hal.exito {
  animation: latir-hal 1.5s ease-in-out infinite;
}
.boton-hal.enviando .boton-logo,
.boton-hal.exito .boton-logo {
  animation: brillo-hal 1.5s ease-in-out infinite;
}
@keyframes latir-hal {
  0%, 100% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 14px 2px rgba(255, 40, 0, 0.45);
  }
  50% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 34px 9px rgba(255, 45, 5, 0.9);
  }
}
@keyframes brillo-hal {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.55) saturate(1.15); }
}

/* Animaciones de los skins de galería. Las de Bordado y Hal están atadas a
   ellos (una gira un parche, la otra palpita en rojo de HAL); estas son
   genéricas, para que publicar un skin nuevo no exija CSS nuevo. */
.skin-gira.enviando .boton-logo,
.skin-gira.exito .boton-logo {
  animation: girar-bordado 2.5s linear infinite;
}
/* Estas van con `.boton-circular` delante a propósito. `.boton-circular.enviando`
   (el pulso genérico) pesa lo mismo que `.skin-X.enviando` y está más abajo en
   el archivo, así que ganaba por orden y la animación del skin no salía nunca.
   Le pasaba a Palpita desde que se creó: el halo no se veía, solo el brillo de
   la imagen. Con la clase de más ganan estas, vivan donde vivan. */
.boton-circular.skin-late.enviando,
.boton-circular.skin-late.exito {
  animation: latir-skin 1.5s ease-in-out infinite;
}
.skin-late.enviando .boton-logo,
.skin-late.exito .boton-logo {
  animation: brillo-hal 1.5s ease-in-out infinite;
}
@keyframes latir-skin {
  0%, 100% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 14px 2px rgba(165, 255, 46, 0.4);
  }
  50% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 32px 8px rgba(165, 255, 46, 0.85);
  }
}

/* Animaciones nuevas del catálogo (7 ago 2026).
   Todas corren durante `enviando` y `exito` —el rato en que la puerta se está
   abriendo—, así que ninguna es decoración: son la señal de que algo está
   pasando al otro lado. Por eso van contenidas: nada que se salga del botón ni
   que dure tanto como para leerse como un fallo.

   ⚠️ El botón recorta la imagen en círculo (`overflow: hidden`). Mover la
   imagen dentro DESTAPA el borde: asoma una medialuna del relieve por detrás y
   parece que la foto se despegó. Girar sobre el centro es la única transformada
   que no destapa nada (la distancia del centro a cada lado no cambia); todo lo
   que sea desplazar mueve el BOTÓN entero, que no tiene nada que lo recorte. */

/* Se balancea: mece la imagen sobre su centro. */
@keyframes balancear-skin {
  0%, 100% { transform: rotate(-10deg); }
  50%      { transform: rotate(10deg); }
}
.skin-balancea.enviando .boton-logo,
.skin-balancea.exito .boton-logo {
  animation: balancear-skin 1.6s ease-in-out infinite;
}

/* Rebota: pega un brinco y cae con un rebotico. Salta el botón entero —con su
   sombra— porque un salto que deja la sombra quieta no se lee como salto. */
@keyframes rebotar-skin {
  0%      { transform: translateY(0) scale(1, 1); }
  8%      { transform: translateY(2%) scale(1.06, 0.94); }      /* se agacha: anticipación */
  18%     { transform: translateY(-6%) scale(0.94, 1.08); filter: blur(0.6px); }  /* se estira al despegar */
  32%     { transform: translateY(-11%) scale(1, 1); filter: none; }              /* flota arriba */
  46%     { transform: translateY(-6%) scale(0.95, 1.07); filter: blur(0.6px); }
  54%     { transform: translateY(0) scale(1.1, 0.9); filter: none; }             /* aplasta al caer */
  62%     { transform: translateY(0) scale(0.98, 1.02); }                          /* rebote del propio cuerpo */
  70%     { transform: translateY(-3.5%) scale(1, 1); }
  80%     { transform: translateY(0) scale(1.05, 0.95); }
  88%     { transform: translateY(0) scale(0.99, 1.01); }
  100%    { transform: translateY(0) scale(1, 1); }
}
.boton-circular.skin-rebota.enviando,
.boton-circular.skin-rebota.exito {
  animation: rebotar-skin 1.2s ease-out infinite;
}

/* Vibra: el zumbido de un portero. Corto, nervioso y chiquito — con más
   amplitud marea, y con menos no se nota. */
@keyframes vibrar-skin {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-2%); }
  75%      { transform: translateX(2%); }
}
.boton-circular.skin-vibra.enviando,
.boton-circular.skin-vibra.exito {
  animation: vibrar-skin 0.12s linear infinite;
}

/* Destella: la luz que va y viene. Nada de opacidad —bajarla deja ver el fondo
   oscuro del botón y en vez de destellar, se apaga— y nada de bajar el brillo
   por debajo de 1: el verde se vuelve oliva y la imagen se ve sucia, no
   apagada. Sube y vuelve, con un resplandor que acompaña. */
@keyframes destellar-skin {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.7) drop-shadow(0 0 10px rgba(165, 255, 46, 0.7)); }
}
.skin-destella.enviando .boton-logo,
.skin-destella.exito .boton-logo {
  animation: destellar-skin 0.9s ease-in-out infinite;
}
/* Y el botón se calla. `pulso-envio` baja la opacidad a 0.45 con el MISMO ciclo
   de 0.9s y arranca en el mismo instante, así que el botón se apagaba justo
   cuando la imagen destellaba y no se veía casi nada. Aquí el destello es la
   señal; no hacen falta dos. */
/* Las que juegan con la LUZ callan el pulso genérico. `pulso-envio` baja la
   opacidad del botón al 45%, y un botón que se apaga a la vez que su destello,
   su barrido o su parpadeo deja un resultado turbio: dos cosas peleando por
   decir lo mismo. Las que MUEVEN el botón no necesitan esto —ahí el pulso ni se
   nota— y las que ya animan el botón entero lo sustituyen solas. */
.boton-circular.skin-destella.enviando,
.boton-circular.skin-destella.exito,
.boton-circular.skin-barre.enviando,
.boton-circular.skin-barre.exito,
.boton-circular.skin-titila.enviando,
.boton-circular.skin-titila.exito {
  animation: none;
}

/* Late fuerte: el latido de siempre pero con cuerpo, creciendo además de
   brillar. Va en el botón y no en la imagen, porque el aro es parte del latido. */
@keyframes latir-fuerte-skin {
  0%   { transform: scale(1, 1); }
  14%  { transform: scale(1.07, 0.95); }     /* sístole: se ensancha y se achata */
  28%  { transform: scale(0.97, 1.04); }
  42%  { transform: scale(1.04, 0.98); }     /* el segundo golpe, más flojo */
  58%  { transform: scale(0.99, 1.01); }
  100% { transform: scale(1, 1); }
}
.boton-circular.skin-late-fuerte.enviando,
.boton-circular.skin-late-fuerte.exito {
  animation: latir-fuerte-skin 0.9s ease-in-out infinite,
             latir-skin 0.9s ease-in-out infinite;
}

/* --- Animaciones "de verdad" (7 ago 2026) --------------------------------
   Las primeras cinco movían el botón; estas juegan con la LUZ, que es lo que
   se ve desde lejos. Siguen corriendo solo durante `enviando` y `exito` —el
   rato en que la puerta se está abriendo—, así que ninguna es adorno.
   Todas llevan `.boton-circular` delante: `pulso-envio` pesa lo mismo y está
   más abajo en el archivo, así que sin eso gana él por orden.
   La sombra neumórfica se repite dentro de cada keyframe que toca `box-shadow`:
   animar esa propiedad la reemplaza entera, y sin repetirla el botón pierde el
   relieve mientras dura la animación. */

/* Reluce: un destello barrido cruza la cara, como metal pulido girando bajo una
   luz. La banda va inclinada y más alta que el botón para que entre y salga
   limpia; el `overflow` del recorte circular la mantiene dentro. */
@keyframes barrer-skin {
  0%      { transform: translateX(-190%) rotate(20deg); }
  60%,
  100%    { transform: translateX(190%) rotate(20deg); }
}
.boton-circular.skin-barre.enviando::before,
.boton-circular.skin-barre.exito::before {
  content: '';
  position: absolute;
  top: -40%;
  bottom: -40%;
  left: 0;
  width: 38%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.9), transparent);
  /* `screen` y no `overlay`: overlay respeta los oscuros, y sobre una foto
     oscura —que son casi todas aquí— el barrido no se veía. Screen siempre
     aclara, así que la luz se lee igual sobre negro que sobre blanco. */
  mix-blend-mode: screen;
  filter: blur(3px);
  animation: barrer-skin 1.6s cubic-bezier(0.6, 0, 0.25, 1) infinite;
  pointer-events: none;
  z-index: 1;
}

/* Irradia: una onda que sale del botón y se pierde. Va en `box-shadow` y no en
   un elemento, porque el botón recorta en círculo y cualquier hijo se quedaría
   dentro — la sombra propia no la recorta nada. */
@keyframes irradiar-skin {
  0% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 0 0 rgba(165, 255, 46, 0.6), 0 0 0 0 rgba(165, 255, 46, 0);
  }
  50% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 0 17px rgba(165, 255, 46, 0.14), 0 0 0 0 rgba(165, 255, 46, 0.5);
  }
  100% {
    box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara),
                0 0 0 30px rgba(165, 255, 46, 0), 0 0 0 17px rgba(165, 255, 46, 0);
  }
}
.boton-circular.skin-irradia.enviando,
.boton-circular.skin-irradia.exito {
  animation: irradiar-skin 1.5s ease-out infinite;
}

/* Orbita: una luz da vueltas alrededor. Son ocho posiciones de la misma sombra
   moviéndose en círculo — con cuatro se ve el salto en las diagonales. */
@keyframes orbitar-skin {
  0%,
  100% { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), 0 -15px 20px 1px rgba(165, 255, 46, 0.85); }
  12.5% { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), 11px -11px 20px 1px rgba(165, 255, 46, 0.85); }
  25%  { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), 15px 0 20px 1px rgba(165, 255, 46, 0.85); }
  37.5% { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), 11px 11px 20px 1px rgba(165, 255, 46, 0.85); }
  50%  { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), 0 15px 20px 1px rgba(165, 255, 46, 0.85); }
  62.5% { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), -11px 11px 20px 1px rgba(165, 255, 46, 0.85); }
  75%  { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), -15px 0 20px 1px rgba(165, 255, 46, 0.85); }
  87.5% { box-shadow: 9px 9px 20px var(--sombra-oscura), -9px -9px 20px var(--sombra-clara), -11px -11px 20px 1px rgba(165, 255, 46, 0.85); }
}
.boton-circular.skin-orbita.enviando,
.boton-circular.skin-orbita.exito {
  animation: orbitar-skin 1.8s linear infinite;
}

/* Voltea: el botón gira sobre sí mismo como una moneda. La perspectiva va en la
   propia transformada —el botón no siempre tiene un padre que se la dé— y a
   mitad de vuelta se ve el dorso, que es la misma foto en espejo: para un
   motivo centrado no se nota y evita tener que pintar una cara de atrás. */
@keyframes voltear-skin {
  0%    { transform: perspective(620px) rotateY(0deg); }
  14%   { transform: perspective(620px) rotateY(-26deg); }        /* toma impulso hacia atrás */
  38%   { transform: perspective(620px) rotateY(150deg); filter: blur(1.4px); }   /* lo rápido va borroso */
  62%   { transform: perspective(620px) rotateY(300deg); filter: blur(1.1px); }
  82%   { transform: perspective(620px) rotateY(378deg); filter: none; }          /* se pasa de largo */
  92%   { transform: perspective(620px) rotateY(352deg); }                         /* y vuelve */
  100%  { transform: perspective(620px) rotateY(360deg); }
}
.boton-circular.skin-voltea.enviando,
.boton-circular.skin-voltea.exito {
  animation: voltear-skin 2.4s linear infinite;   /* el ritmo va en los keyframes, no en la curva */
}

/* Titila: el arranque de un tubo de neón. Los tiempos son IRREGULARES a
   propósito: un parpadeo regular se lee como un fallo de la pantalla, y uno
   irregular como algo encendiéndose. Acaba fijo y brillando. */
@keyframes titilar-skin {
  0%, 8%    { filter: brightness(0.45); }
  10%, 13%  { filter: brightness(1.5); }
  15%, 19%  { filter: brightness(0.45); }
  21%       { filter: brightness(1.6); }
  24%, 27%  { filter: brightness(0.6); }
  30%, 100% { filter: brightness(1.35) drop-shadow(0 0 8px rgba(165, 255, 46, 0.55)); }
}
.boton-circular.skin-titila.enviando .boton-logo,
.boton-circular.skin-titila.exito .boton-logo {
  animation: titilar-skin 2.4s linear infinite;
}

/* --- Y las que cuentan algo -----------------------------------------------
   Hasta aquí las animaciones movían el botón o lo iluminaban. Estas usan lo que
   el botón SIGNIFICA: se abre, rastrea, se acerca. Siguen corriendo solo
   mientras la puerta está abierta. */

/* Se abre: dos hojas tapan la foto y se retiran, como la puerta que estás
   abriendo. Va con `forwards` y UNA sola pasada a propósito: abrir y cerrar en
   bucle diría que la puerta se abre y se cierra sola. Se abren, se quedan
   abiertas mientras dure, y desaparecen al quitarse la clase. */
@keyframes abrir-izquierda {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-101%); }
}
@keyframes abrir-derecha {
  0%   { transform: translateX(0); }
  100% { transform: translateX(101%); }
}
.boton-circular.skin-compuerta.enviando::before,
.boton-circular.skin-compuerta.exito::before,
.boton-circular.skin-compuerta.enviando::after,
.boton-circular.skin-compuerta.exito::after {
  content: '';
  position: absolute;
  top: -2%;
  bottom: -2%;
  width: 50.5%;
  background: linear-gradient(180deg, #43474d, #24262a);
  z-index: 2;
  pointer-events: none;
}
.boton-circular.skin-compuerta.enviando::before,
.boton-circular.skin-compuerta.exito::before {
  left: 0;
  border-right: 1px solid rgba(255, 255, 255, 0.14);
  animation: abrir-izquierda 0.85s cubic-bezier(0.62, -0.24, 0.27, 1.32) forwards;
}
.boton-circular.skin-compuerta.enviando::after,
.boton-circular.skin-compuerta.exito::after {
  right: 0;
  border-left: 1px solid rgba(0, 0, 0, 0.45);
  animation: abrir-derecha 0.85s cubic-bezier(0.62, -0.24, 0.27, 1.32) forwards;
}

/* Rastrea: el brazo de un radar. Es un `conic-gradient` girando —un solo
   elemento, sin trucos— con la estela por delante para que se vea hacia dónde
   barre. */
@keyframes rastrear-skin {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.boton-circular.skin-rastrea.enviando::after,
.boton-circular.skin-rastrea.exito::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    rgba(165, 255, 46, 0.55) 0deg,
    rgba(165, 255, 46, 0.16) 42deg,
    transparent 96deg,
    transparent 360deg);
  filter: blur(4px);
  animation: rastrear-skin 1.9s linear infinite;
  z-index: 1;
  pointer-events: none;
}

/* Chispea: la interferencia de una señal. Corta, con un silencio largo detrás:
   un glitch continuo cansa a los diez segundos y este dura lo que dure la
   puerta abierta. El desdoble rojo/cian es lo que lo hace leer como avería de
   pantalla y no como parpadeo. */
@keyframes chispear-skin {
  0%, 66%, 100% { transform: translate(0); filter: none; }
  68%  { transform: translate(-3px, 1px); filter: drop-shadow(3px 0 rgba(255, 0, 60, 0.85)) drop-shadow(-3px 0 rgba(0, 225, 255, 0.85)); }
  71%  { transform: translate(3px, -1px); filter: drop-shadow(-4px 0 rgba(255, 0, 60, 0.85)) drop-shadow(4px 0 rgba(0, 225, 255, 0.85)); }
  74%  { transform: translate(-2px, -2px); filter: drop-shadow(2px 0 rgba(255, 0, 60, 0.7)) drop-shadow(-2px 0 rgba(0, 225, 255, 0.7)); }
  77%  { transform: translate(0); filter: none; }
  84%  { transform: translate(4px, 0); filter: drop-shadow(-3px 0 rgba(255, 0, 60, 0.8)) drop-shadow(3px 0 rgba(0, 225, 255, 0.8)); }
  87%  { transform: translate(0); filter: none; }
}
.boton-circular.skin-chispea.enviando .boton-logo,
.boton-circular.skin-chispea.exito .boton-logo {
  animation: chispear-skin 2.6s steps(1, end) infinite;
}

/* Se acerca: la cámara entra despacio en la foto. Nunca baja de 1 —si
   encogiera, el círculo dejaría ver el fondo por los bordes— y el
   desplazamiento es mínimo para que no se salga el motivo. */
@keyframes acercar-skin {
  0%   { transform: scale(1) translate(0, 0); }
  100% { transform: scale(1.16) translate(-1.5%, -1%); }
}
.boton-circular.skin-acerca.enviando .boton-logo,
.boton-circular.skin-acerca.exito .boton-logo {
  animation: acercar-skin 6s ease-out forwards;
}

/* Holo: lámina holográfica, como una carta de colección al inclinarla.
   Aquí no hay nada moviéndose: lo que cambia es CÓMO se mezcla una capa de
   color con la foto (`color-dodge` quema los claros y respeta los oscuros, que
   es justo lo que hace el nacarado real). Dos capas a distinta velocidad y en
   ángulos cruzados — con una sola se ve la trama y parece un fondo de pantalla.
   `background-size` grande y `background-position` animada en vez de mover el
   elemento: así la lámina "corre" por dentro sin desplazar nada. */
@keyframes holo-uno {
  0%   { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}
@keyframes holo-dos {
  0%   { background-position: 100% 50%; opacity: 0.55; }
  50%  { opacity: 0.85; }
  100% { background-position: -100% 50%; opacity: 0.55; }
}
.boton-circular.skin-holo.enviando::before,
.boton-circular.skin-holo.exito::before,
.boton-circular.skin-holo.enviando::after,
.boton-circular.skin-holo.exito::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  background-size: 200% 100%;
  mix-blend-mode: color-dodge;
}
.boton-circular.skin-holo.enviando::before,
.boton-circular.skin-holo.exito::before {
  background-image: linear-gradient(72deg,
    transparent 12%, rgba(255, 60, 172, 0.55) 26%, rgba(90, 220, 255, 0.55) 38%,
    rgba(180, 255, 90, 0.5) 50%, transparent 66%);
  animation: holo-uno 2.6s linear infinite;
}
.boton-circular.skin-holo.enviando::after,
.boton-circular.skin-holo.exito::after {
  background-image: linear-gradient(-58deg,
    transparent 20%, rgba(255, 225, 90, 0.45) 34%, rgba(160, 90, 255, 0.5) 48%, transparent 64%);
  animation: holo-dos 3.7s ease-in-out infinite;
}
/* El pulso genérico se calla: baja la opacidad al 45% y apagaría la lámina. */
.boton-circular.skin-holo.enviando,
.boton-circular.skin-holo.exito { animation: none; }

/* Se envuelve: dos telas oscuras entran por los lados, tapan el botón y se
   retiran. En un skin de Batman se lee como la capa cerrándose; en cualquier
   otro, como algo que se cubre y se descubre.
   Es la inversa de "Se abre" pero con oficio distinto: el borde interior va
   REDONDEADO —una tela no tiene el canto recto de una hoja de puerta—, entran
   con inercia (rebasan un poco al juntarse) y se van desfasadas, primero una y
   luego la otra, que es lo que impide que parezca un telón de PowerPoint. */
@keyframes envolver-izquierda {
  0%          { transform: translateX(-104%) rotate(-6deg); }
  26%, 58%    { transform: translateX(0) rotate(0deg); }
  88%, 100%   { transform: translateX(-104%) rotate(-6deg); }
}
@keyframes envolver-derecha {
  0%          { transform: translateX(104%) rotate(6deg); }
  32%, 64%    { transform: translateX(0) rotate(0deg); }
  94%, 100%   { transform: translateX(104%) rotate(6deg); }
}
.boton-circular.skin-envuelve.enviando::before,
.boton-circular.skin-envuelve.exito::before,
.boton-circular.skin-envuelve.enviando::after,
.boton-circular.skin-envuelve.exito::after {
  content: '';
  position: absolute;
  top: -6%;
  bottom: -6%;
  width: 56%;
  z-index: 2;
  pointer-events: none;
}
.boton-circular.skin-envuelve.enviando::before,
.boton-circular.skin-envuelve.exito::before {
  left: 0;
  background: linear-gradient(105deg, #0e0f12 60%, #23262c 100%);
  border-radius: 0 60% 55% 0 / 0 50% 50% 0;
  animation: envolver-izquierda 2.8s cubic-bezier(0.5, -0.2, 0.3, 1.2) infinite;
}
.boton-circular.skin-envuelve.enviando::after,
.boton-circular.skin-envuelve.exito::after {
  right: 0;
  background: linear-gradient(255deg, #0e0f12 60%, #23262c 100%);
  border-radius: 60% 0 0 55% / 50% 0 0 50%;
  animation: envolver-derecha 2.8s cubic-bezier(0.5, -0.2, 0.3, 1.2) infinite;
}

/* Colorea: el dibujo se enciende en colores.
   El truco es que `hue-rotate` solo mueve el TONO, y un píxel sin saturación
   —negros, grises, el fondo oscuro de casi todos estos botones— no tiene tono
   que mover. Así que lo único que cambia de color son los trazos del dibujo:
   en la mandala se lee como el contorno iluminándose, sin haber recortado nada.
   El `saturate` va antes para que hasta un dorado apagado tenga color que girar,
   y el brillo late medio ciclo por detrás del tono — si subieran a la vez se
   vería un parpadeo en lugar de luz recorriendo el dibujo. */
@keyframes colorear-skin {
  0%   { filter: saturate(1.9) hue-rotate(0deg) brightness(1.1); }
  25%  { filter: saturate(2.3) hue-rotate(90deg) brightness(1.28); }
  50%  { filter: saturate(1.9) hue-rotate(180deg) brightness(1.1); }
  75%  { filter: saturate(2.3) hue-rotate(270deg) brightness(1.28); }
  100% { filter: saturate(1.9) hue-rotate(360deg) brightness(1.1); }
}
.boton-circular.skin-colorea.enviando .boton-logo,
.boton-circular.skin-colorea.exito .boton-logo {
  animation: colorear-skin 3.4s linear infinite;
}
/* Y calla el pulso genérico: apagar el botón al 45% mientras el dibujo se
   enciende deja el color turbio. */
.boton-circular.skin-colorea.enviando,
.boton-circular.skin-colorea.exito { animation: none; }

/* Nombre abajo DENTRO del anillo (sobre la franja oscura, no sobre la foto)
   para los botones con imagen (Argentina, Bordado). Una sola línea; el largo
   se limita en el campo de nombre del editor. Se desvanece mientras el botón
   está activo. La sombra lo hace legible por si roza el borde de la foto. */
.nombre-anillo {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 5px;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--texto-suave);
  white-space: nowrap;
  z-index: 2;
  pointer-events: none;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  transition: opacity 0.25s ease;
}
.boton-circular.enviando ~ .nombre-anillo,
.boton-circular.exito ~ .nombre-anillo {
  opacity: 0;
}

.boton-imagen .nombre-boton {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;
  /* El .nombre-boton base trae max-width: 82%, que con left/right absolutos
     empuja la caja a la izquierda y descentra el texto. Lo anulamos para que
     ocupe todo el ancho y quede centrado. */
  max-width: none;
  z-index: 1;
  /* Sombra sutil para despegar el gris del escudo, sin cambiar color ni peso. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.boton-circular.grande svg {
  width: 108px;
  height: auto;
}

.boton-circular.grande svg.icono-candado {
  width: auto;
  height: 66px;
}

.boton-circular.grande.cuadrado svg {
  width: 82px;
  height: 82px;
}

.boton-circular.medio {
  width: 92px;
  height: 92px;
}

.boton-circular.chico {
  width: 72px;
  height: 72px;
}

.fila-cortina {
  display: flex;
  gap: 18px;
}

.boton-circular.activo {
  color: var(--primario);
  box-shadow: inset 6px 6px 13px var(--sombra-oscura), inset -6px -6px 13px var(--sombra-clara),
              inset 0 0 0 2px rgba(165, 255, 46, 0.55);
}

.boton-circular.activo svg {
  filter: drop-shadow(0 0 9px rgba(165, 255, 46, 0.8));
}

.boton-circular.exito {
  color: var(--primario);
}

/* Ascensor: el icono se mantiene neutro; solo la flecha de subir se
   enciende (se rellena de verde) cuando el botón está enviando o activo. */
.icono-ascensor {
  color: var(--texto-suave);
}

.boton-circular.enviando .flecha-subir,
.boton-circular.exito .flecha-subir,
.boton-circular.activo .flecha-subir {
  stroke: var(--primario);
  fill: var(--primario);
}

/* Búnker: la bomba se mantiene neutra; la mecha se enciende (verde con
   brillo) al pulsar/activar, como si prendiera. */
.icono-bunker {
  color: var(--texto-suave);
}

.boton-circular.enviando .mecha {
  stroke: var(--fuego);
  filter: drop-shadow(0 0 3px rgba(255, 140, 58, 0.95));
}

/* Al encenderse (éxito/activo) la mecha titila 3 veces, como una chispa. */
.boton-circular.exito .mecha,
.boton-circular.activo .mecha {
  animation: titilar-mecha 0.35s ease-in-out 3 both;
}

@keyframes titilar-mecha {
  0%, 100% { stroke: var(--fuego); opacity: 1; filter: drop-shadow(0 0 4px rgba(255, 140, 58, 0.95)); }
  50%      { stroke: var(--texto-suave); opacity: 0.2; filter: none; }
}

/* Portón: el garaje y el carro se mantienen neutros. Al abrir (éxito) una
   sola luz recorre las persianas de abajo hacia arriba, apagándose cada una
   al encender la siguiente, como si la puerta se estuviera abriendo. */
.icono-porton {
  color: var(--texto-suave);
}

.icono-porton .persianas line {
  transition: stroke 0.3s ease;
}

/* Al abrir (éxito): una sola luz recorre las persianas de abajo hacia arriba
   — cada persiana se apaga justo cuando enciende la siguiente. */
.boton-circular.exito .icono-porton .persianas line {
  animation-duration: 1.6s;
  animation-timing-function: ease-in-out;
  animation-fill-mode: both;
  /* Se repite mientras el botón esté activo: el portón tarda ~15s en abrir y
     con un número fijo de vueltas la animación se apagaba mucho antes. */
  animation-iteration-count: infinite;
}
.boton-circular.exito .icono-porton .persianas line:nth-of-type(4) { animation-name: porton-persiana-4; }
.boton-circular.exito .icono-porton .persianas line:nth-of-type(3) { animation-name: porton-persiana-3; }
.boton-circular.exito .icono-porton .persianas line:nth-of-type(2) { animation-name: porton-persiana-2; }
.boton-circular.exito .icono-porton .persianas line:nth-of-type(1) { animation-name: porton-persiana-1; }

@keyframes porton-persiana-4 { /* abajo: 1ª en encender */
  0%   { stroke: var(--texto-suave); filter: none; }
  6%   { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  22%  { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  28%  { stroke: var(--texto-suave); filter: none; }
  100% { stroke: var(--texto-suave); filter: none; }
}
@keyframes porton-persiana-3 {
  0%, 22% { stroke: var(--texto-suave); filter: none; }
  28%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  47%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  53%     { stroke: var(--texto-suave); filter: none; }
  100%    { stroke: var(--texto-suave); filter: none; }
}
@keyframes porton-persiana-2 {
  0%, 47% { stroke: var(--texto-suave); filter: none; }
  53%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  72%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  78%     { stroke: var(--texto-suave); filter: none; }
  100%    { stroke: var(--texto-suave); filter: none; }
}
@keyframes porton-persiana-1 { /* arriba: última en encender, luego se apaga */
  0%, 72% { stroke: var(--texto-suave); filter: none; }
  78%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  94%     { stroke: var(--primario); filter: drop-shadow(0 0 2.5px rgba(165, 255, 46, 0.9)); }
  100%    { stroke: var(--texto-suave); filter: none; }
}

.boton-circular.enviando {
  animation: pulso-envio 0.9s ease-in-out infinite;
}

@keyframes pulso-envio {
  50% { opacity: 0.45; }
}

/* ===== Skins del botón (estilo elegible tipo "vestuario") =====
   El default (neumórfico) es el base sin clase. Cada skin reestiliza
   .boton-circular cuando lleva la clase piel-X (una por dispositivo); los mismos
   para las muestras del selector (.skin-muestra.piel-X). Los iconos usan
   currentColor, así que se themean con `color`. El pulido fino de estados
   (éxito/activo por skin) va después. */
.boton-circular[class*="piel-"]:not(:disabled):active {
  transform: scale(0.96);
}

/* Muestra (swatch) del selector de skin: mini botón circular. */
.skin-muestra {
  /* ⚠️ Esto NO es decoración: sin `position` los adornos de los aspectos se
     escapan. Las miniaturas llevan la clase del aspecto (`boton-hal`,
     `boton-bordado`…) y varias de esas clases traen un `::before` absoluto con
     `inset: 0` — un aro fino. Un absoluto busca el primer ancestro posicionado,
     y si no hay ninguno se ancla a la PANTALLA: el aro de Hal salía como un
     óvalo de 2000×1100 cruzando toda la app.
     Estaba tapado por casualidad: el carrusel le ponía `transform: scale(1)` a
     cada ficha, y un `transform` también crea contexto para los absolutos. Al
     cambiar a rejilla se fue el transform y el aro se soltó. Regla: la caja que
     hospeda adornos absolutos se posiciona ella, no se confía en el vecino. */
  position: relative;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #a9afb5;
  background: var(--superficie);
  box-shadow: 5px 5px 12px var(--sombra-oscura), -5px -5px 12px var(--sombra-clara);
}
.skin-muestra svg { width: 26px; height: 26px; }
.skin-muestra { overflow: hidden; }
.skin-muestra img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

/* "Hacer uno", la última ficha del carrusel.
   Va HUNDIDA mientras las demás van en relieve, y esa es toda la explicación
   que necesita: en este lenguaje el relieve es una cosa que puedes ponerte y el
   hueco es que ahí falta algo. Un aspecto más, con su foto y su nombre, se
   leería como un skin llamado "Hacer uno". */
.skin-muestra-crear {
  font-size: 30px;
  font-weight: 300;
  line-height: 1;
  color: var(--texto-suave);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura),
              inset -4px -4px 9px var(--sombra-clara);
}

/* Neón */
.boton-circular.piel-neon,
.skin-muestra.piel-neon {
  color: var(--primario);
  background: #0c0c0f;
  box-shadow: 0 0 0 2px var(--primario), 0 0 20px rgba(165, 255, 46, 0.5), inset 0 0 16px rgba(165, 255, 46, 0.12);
}
.boton-circular.piel-neon svg,
.skin-muestra.piel-neon svg { filter: drop-shadow(0 0 6px rgba(165, 255, 46, 0.85)); }

/* Acero */
.boton-circular.piel-acero,
.skin-muestra.piel-acero {
  color: #dfe3e8;
  background: linear-gradient(145deg, #4a4e54, #23262b);
  box-shadow: 6px 6px 14px rgba(0, 0, 0, 0.55), -4px -4px 10px rgba(255, 255, 255, 0.07), inset 0 2px 3px rgba(255, 255, 255, 0.22), inset 0 -3px 6px rgba(0, 0, 0, 0.45);
}

/* Cristal */
.boton-circular.piel-cristal,
.skin-muestra.piel-cristal {
  color: #eef2f5;
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 8px rgba(255, 255, 255, 0.16), inset 0 0 0 1px rgba(255, 255, 255, 0.22), 0 6px 16px rgba(0, 0, 0, 0.38);
}

/* Pop (verde sólido) */
.boton-circular.piel-pop,
.skin-muestra.piel-pop {
  color: #14210a;
  background: var(--primario);
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4), inset 0 -4px 8px rgba(0, 0, 0, 0.16), inset 0 3px 6px rgba(255, 255, 255, 0.3);
}

/* ===== Las mismas pieles, vestidas sobre PERILLA (cortina y termostato) =====
   Aquí se viste el aro (track), el nivel y el centro; el valor y el indicador
   siguen a la piel para que el dial se lea como una pieza. */
.perilla.piel-neon .perilla-track { stroke: #16181c; }
.perilla.piel-neon .perilla-nivel { filter: drop-shadow(0 0 8px rgba(165, 255, 46, 0.95)); }
.perilla.piel-neon .perilla-centro {
  background: #0c0c0f;
  box-shadow: 0 0 0 2px var(--primario), 0 0 18px rgba(165, 255, 46, 0.45), inset 0 0 14px rgba(165, 255, 46, 0.12);
}

.perilla.piel-acero .perilla-track { stroke: #43474d; }
.perilla.piel-acero .perilla-nivel { stroke: #dfe3e8; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); }
.perilla.piel-acero .perilla-centro {
  background: linear-gradient(145deg, #4a4e54, #23262b);
  box-shadow: 6px 6px 14px rgba(0, 0, 0, 0.55), -4px -4px 10px rgba(255, 255, 255, 0.07), inset 0 2px 3px rgba(255, 255, 255, 0.22), inset 0 -3px 6px rgba(0, 0, 0, 0.45);
}
.perilla.piel-acero .perilla-valor,
.perilla.piel-acero .perilla-accion { color: #dfe3e8; }
.perilla.piel-acero .perilla-indicador::before { background: #dfe3e8; box-shadow: none; }

.perilla.piel-cristal .perilla-track { stroke: rgba(255, 255, 255, 0.12); }
.perilla.piel-cristal .perilla-nivel { stroke: #eef2f5; filter: none; }
.perilla.piel-cristal .perilla-centro {
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 8px rgba(255, 255, 255, 0.16), inset 0 0 0 1px rgba(255, 255, 255, 0.22), 0 6px 16px rgba(0, 0, 0, 0.38);
}
.perilla.piel-cristal .perilla-valor,
.perilla.piel-cristal .perilla-accion { color: #eef2f5; }
.perilla.piel-cristal .perilla-indicador::before { background: #eef2f5; box-shadow: none; }

.perilla.piel-pop .perilla-track { stroke: #2a2d31; }
.perilla.piel-pop .perilla-nivel { filter: none; stroke-width: 13; }
.perilla.piel-pop .perilla-centro {
  background: var(--primario);
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4), inset 0 -4px 8px rgba(0, 0, 0, 0.16), inset 0 3px 6px rgba(255, 255, 255, 0.3);
}
.perilla.piel-pop .perilla-valor,
.perilla.piel-pop .perilla-accion { color: #14210a; }
.perilla.piel-pop.encendido .perilla-valor,
.perilla.piel-pop.encendido .perilla-accion { color: #14210a; }

/* ===== Aspecto RUEDA: rodillo moleteado con detente y arco de luces =====
   Reemplaza el control (no es una piel). El color sigue al dispositivo:
   cortina en verde, termostato en azul frío, dimmer en ámbar cálido (~2500K,
   la luz de un incandescente). */
.control-rueda {
  --rueda-luz: var(--primario);
  --rueda-halo: rgba(165, 255, 46, 0.9);
}
.control-rueda.frio {
  --rueda-luz: #5ec8ff;
  --rueda-halo: rgba(94, 200, 255, 0.9);
}
.control-rueda.calido {
  --rueda-luz: #ffbe6b;
  --rueda-halo: rgba(255, 190, 107, 0.95);
}

.rueda-ctrl {
  position: relative;
  width: 150px;
  height: 150px;
  /* pan-x deja el swipe horizontal al carrusel; lo vertical es de la rueda. */
  touch-action: pan-x;
  /* Encierra los repintados de las luces: que encender una no invalide la
     tarjeta ni la fila del carrusel. */
  contain: layout paint;
  -webkit-tap-highlight-color: transparent;
  cursor: grab;
}
.rueda-ctrl:active { cursor: grabbing; }

.rueda-arco { position: absolute; inset: 0; pointer-events: none; }
.rueda-led {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #2c2e33;
  transform: translate(-50%, -50%);
  /* Sin transición: interpolar el box-shadow de 13 luces obliga a repintar el
     halo en cada frame, y mientras se arrastra la rueda eso es justo lo que la
     hace temblar. Un detente enciende de golpe, además, que es lo correcto. */
}
.rueda-led.on {
  background: var(--rueda-luz);
  box-shadow: 0 0 8px var(--rueda-halo);
}

/* El rodillo va en su cavidad: la foto trae bisel, collares metálicos y
   sombras, y solo se anima la ventana del moleteado. Un único bitmap. */
.rueda-marco {
  position: absolute;
  left: 6px;
  top: 17px;
  width: 116px;
  height: 116px;
  border-radius: 24px;
  background: url("rueda-marco.jpg?v=1") center/100% 100% no-repeat;
}
/* Ventana entre los dos collares, medida sobre la foto. */
.rueda-moleteado-caja {
  position: absolute;
  left: 25.4%;
  right: 25.2%;
  top: 10.4%;
  bottom: 10.4%;
  overflow: hidden;
}
/* Franja alta que se desplaza con transform (compositor, sin repintar). La
   textura repite cada 155px, así que el giro se toma módulo 155. */
.rueda-moleteado {
  position: absolute;
  left: 0;
  right: 0;
  top: -155px;
  height: 465px;
  background: url("rueda-textura2.jpg?v=1") center top/100% 155px repeat-y;
  will-change: transform;
}
/* Curvatura del cilindro: la superficie se apaga al alejarse hacia los cantos.
   Va dentro de la ventana para no ensuciar el bisel. */
.rueda-brillo {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, 0.88) 0%,
    rgba(0, 0, 0, 0.35) 10%,
    rgba(255, 255, 255, 0.06) 45%,
    rgba(255, 255, 255, 0.04) 55%,
    rgba(0, 0, 0, 0.35) 90%,
    rgba(0, 0, 0, 0.88) 100%);
}

.rueda-valor {
  font-size: 22px;
  font-weight: 600;
  color: var(--rueda-luz);
  font-variant-numeric: tabular-nums;
  /* Ancho fijo: si el número cambia de ancho (9 → 10 → 100) rehace el layout
     de la tarjeta en pleno arrastre. */
  min-width: 3.4em;
  text-align: center;
}
.control-rueda.rueda-enviando { animation: pulso-envio 0.9s ease-in-out infinite; }

/* Tocar el rodillo (sin girarlo) apaga/enciende, y en la cortina pausa. Sin
   iconos: lo dicen las luces y el número. Apagado = todo oscuro; en pausa las
   luces laten, como algo detenido a medio camino. */
.control-rueda.rueda-apagado .rueda-valor { color: var(--texto-suave); }
.control-rueda.rueda-apagado .rueda-marco { filter: brightness(0.72); }
@keyframes rueda-latido {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}
.control-rueda.rueda-pausa .rueda-led.on { animation: rueda-latido 1.3s ease-in-out infinite; }
.control-rueda.rueda-pausa .rueda-valor { animation: rueda-latido 1.3s ease-in-out infinite; }

/* ===== Piel COBRE: perilla de audio (foto) con corona de LEDs ámbar =====
   La foto va sobre .perilla-indicador, que es justo lo que el JS rota con el
   valor: así el knob gira de verdad y su línea marca la posición, sin tocar el
   JS. El punto indicador de siempre sobra (la foto ya trae la línea).
   El arco se enmascara con un gradiente cónico para que track y nivel se lean
   como una corona de LEDs: 20 puntos repartidos en los 270° del recorrido. */
.perilla.piel-cobre .perilla-centro {
  background: #0b0b0c;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.65);
  overflow: hidden;
}
.perilla.piel-cobre .perilla-indicador {
  background: url("perilla-cobre.jpg?v=1") center/cover no-repeat;
  border-radius: 50%;
}
.perilla.piel-cobre .perilla-indicador::before { display: none; }

.perilla.piel-cobre .perilla-svg {
  -webkit-mask: repeating-conic-gradient(from -135deg at 50% 50%, #000 0 4deg, transparent 4deg 13.5deg);
  mask: repeating-conic-gradient(from -135deg at 50% 50%, #000 0 4deg, transparent 4deg 13.5deg);
}
.perilla.piel-cobre .perilla-track { stroke: #4a3a28; stroke-width: 7; }
.perilla.piel-cobre .perilla-nivel {
  stroke: #ffb545;
  stroke-width: 7;
  filter: drop-shadow(0 0 6px rgba(255, 170, 60, 0.95));
}
/* El valor va sobre el cobre: oscuro, con un realce claro para que se lea.
   Necesita position+z-index porque el indicador (que ahora lleva la foto) está
   posicionado y, sin esto, se pintaría encima del número. */
.perilla.piel-cobre .perilla-valor,
.perilla.piel-cobre .perilla-accion,
.perilla.piel-cobre .termo-objetivo {
  position: relative;
  z-index: 1;
  color: #2b1a0c;
  text-shadow: 0 1px 2px rgba(255, 225, 190, 0.7);
}

/* Cobre en el slider del dimmer: mismo material, sin knob. */
.control-dimmer.piel-cobre { background: #101012; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.55); }
.control-dimmer.piel-cobre .dimmer-pista { background: #0b0b0c; box-shadow: inset 0 0 0 1px rgba(255, 170, 60, 0.22); }
.control-dimmer.piel-cobre .dimmer-fill {
  background: linear-gradient(180deg, #d9a071, #a86a3c);
  box-shadow: 0 0 14px rgba(255, 170, 60, 0.5);
}
.control-dimmer.piel-cobre.encendido .dimmer-icono,
.control-dimmer.piel-cobre.encendido .dimmer-valor { color: #ffb545; }

/* ===== Las mismas pieles, vestidas sobre el SLIDER del dimmer ===== */
.control-dimmer.piel-neon { background: #0c0c0f; box-shadow: 0 0 0 1.5px rgba(165, 255, 46, 0.5), 0 0 18px rgba(165, 255, 46, 0.15); }
.control-dimmer.piel-neon .dimmer-pista { background: #08080a; box-shadow: inset 0 0 0 1.5px rgba(165, 255, 46, 0.35); }
.control-dimmer.piel-neon .dimmer-fill { box-shadow: 0 0 16px rgba(165, 255, 46, 0.85); }

.control-dimmer.piel-acero { background: linear-gradient(145deg, #4a4e54, #23262b); }
.control-dimmer.piel-acero .dimmer-pista { background: #1c1e21; }
.control-dimmer.piel-acero .dimmer-fill { background: linear-gradient(180deg, #eef1f4, #9aa1a9); }
.control-dimmer.piel-acero.encendido .dimmer-icono,
.control-dimmer.piel-acero.encendido .dimmer-valor { color: #dfe3e8; }

.control-dimmer.piel-cristal { background: rgba(255, 255, 255, 0.07); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22), 0 6px 16px rgba(0, 0, 0, 0.38); }
.control-dimmer.piel-cristal .dimmer-pista { background: rgba(0, 0, 0, 0.28); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14); }
.control-dimmer.piel-cristal .dimmer-fill { background: rgba(255, 255, 255, 0.85); }
.control-dimmer.piel-cristal.encendido .dimmer-icono,
.control-dimmer.piel-cristal.encendido .dimmer-valor { color: #eef2f5; }

.control-dimmer.piel-pop { background: var(--primario); }
.control-dimmer.piel-pop .dimmer-nombre { color: #14210a; font-weight: 600; }
.control-dimmer.piel-pop .dimmer-icono,
.control-dimmer.piel-pop .dimmer-valor { color: #14210a; }
.control-dimmer.piel-pop.encendido .dimmer-icono,
.control-dimmer.piel-pop.encendido .dimmer-valor { color: #14210a; }
.control-dimmer.piel-pop .dimmer-pista { background: rgba(0, 0, 0, 0.22); box-shadow: none; }
.control-dimmer.piel-pop .dimmer-fill { background: #14210a; }

/* Vestuario: un bloque por dispositivo, cada uno con su galería de aspectos. */
.vest-ayuda {
  font-size: 13px;
  color: var(--texto-suave);
  margin: -6px 0 16px;
}

/* Dropdown del dispositivo que se está vistiendo. */
.vest-select {
  width: 100%;
  padding: 12px 14px;
  border: none;
  border-radius: 12px;
  background: var(--fondo);
  color: var(--texto);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  /* Aire arriba y abajo: ahora va ENTRE la muestra y las acciones, no encabezando
     la tarjeta, así que necesita separarse de las dos. */
  margin: 18px 0;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  /* Con appearance:none se pierde la flecha nativa: se dibuja una. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238d9297' stroke-width='2.4' stroke-linecap='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 18px;
  /* El nombre va CENTRADO, para que caiga a plomo bajo el botón de arriba. Los
     dos rellenos tienen que medir lo mismo: la flecha se come 38px a la derecha
     y con 14 a la izquierda el texto quedaría 12px corrido, que es justo lo que
     se nota cuando algo pretende estar centrado. */
  text-align: center;
  padding-left: 38px;
  padding-right: 38px;
}

/* Demo: el control de verdad, para verlo y tocarlo antes de decidir. */
.vest-demo {
  display: flex;
  justify-content: center;
  /* Simétrico: el botón va centrado en su espacio. Los 4/18 de antes venían de
     cuando la muestra llevaba su nombre debajo —el hueco de abajo era para esa
     etiqueta—; al quitarla, el botón quedó descolgado hacia arriba. */
  padding: 12px 0;
}

/* Carrusel de opciones: la centrada es la elegida (coverflow como el de los
   dispositivos). El padding lateral deja que la primera y la última se
   centren. */
/* La galería del Locker: rejilla, no carrusel.
   Era una fila horizontal con coverflow. Con cuatro opciones lucía; con veinte
   se volvía una tira sin orden en la que había que arrastrar a ciegas para ver
   si quedaba algo. En rejilla se ven todas de golpe.
   `auto-fill` en vez de tres columnas fijas: en el teléfono salen tres y en una
   pantalla ancha las que quepan, sin un segundo juego de medidas. */
.vest-opciones {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 16px 4px;
  padding: 6px 0;
  max-height: 46vh;
  overflow-y: auto;
  scrollbar-width: none;
}
.vest-opciones::-webkit-scrollbar { display: none; }

/* Los encabezados de grupo ocupan la fila entera; si no, se colocarían como
   una ficha más y el grupo empezaría a media línea. */
.vest-grupo {
  grid-column: 1 / -1;
  margin: 4px 0 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--texto-suave);
}
.vest-grupo:first-child { margin-top: 0; }

/* La elegida se marca en verde. Antes esto colgaba de `.enfoque` —la que el
   coverflow tenía centrada—, que en una rejilla no significa nada: ahora es
   `.activa`, que la pone quien pinta. */
.vest-opciones .skin-op.activa .skin-muestra {
  outline: 2px solid var(--primario);
  outline-offset: 3px;
}
.vest-opciones .skin-op.activa .skin-nom { color: var(--primario); }

.vest-nota {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

/* "Estilo guardado": confirma que la elección quedó (se guarda sola al centrarla).
   NO se oculta con `oculto` sino con opacidad, y la línea se reserva siempre: si
   apareciera y desapareciera del flujo, cada elección empujaría el resto de la
   pantalla hacia abajo y de vuelta. */
/* "Editar diseño": el segundo escalón, y por eso secundario y más pequeño que
   el Guardar. Solo sale con un diseño tuyo elegido. */
.vest-editar {
  display: block;
  width: 100%;
  /* Aire por los dos lados: debajo viene el primer encabezado de la galería
     ("Destacados"), y sin margen quedaban pegados. */
  margin: 8px 0 18px;
}
#vest-editor { margin-top: 12px; }
/* El Guardar del diseño, en el hueco que deja "Editar diseño". Mismo sitio y
   mismo ancho: editando, ese renglón es la acción principal. */
.vest-guardar-skin { display: block; width: 100%; margin: 8px 0 0; }
/* En el Locker la muestra no lleva nombre: el selector que está justo debajo
   ya dice de qué dispositivo es, y verlo dos veces seguidas se lee como un
   error. Va por CSS y sobre `.etiqueta-control`, que es la clase que usan TODOS
   los controles —botón redondo, Jet, Pilder, Sabiem, Mando—, así que ninguno se
   queda con su nombre por su cuenta.
   Ojo: fuera del Locker esa etiqueta se queda. Ahí es lo único que identifica
   cada botón. */
#vest-demo .etiqueta-control { display: none; }

/* Editando: fuera la galería y el Guardar del aspecto. Lo que queda es la
   muestra —el botón de verdad, a tamaño real— y el formulario justo debajo, que
   es lo único que hace falta para ver lo que estás cambiando. */
#vestuario.editando #vest-opciones,
#vestuario.editando .vest-guardar { display: none; }

/* El Guardar del Locker. Apagado dice "esto ya es lo tuyo"; encendido, que hay
   algo probándose sin grabar. */
/* Guardar se queda A LA VISTA mientras eliges.
   Al final de la sección quedaba fuera de pantalla: había que probarse un
   estilo, bajar a buscar el botón y volver a mirar arriba.
   `sticky` no servía —siendo lo último de su sección no tiene contra qué
   pegarse— así que va FIJO al borde de abajo, justo encima de la barra de
   pestañas. Solo existe cuando el Locker está a la vista, porque su sección
   entera se esconde con la pestaña. */
.vest-guardar {
  display: block;
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: calc(62px + env(safe-area-inset-bottom));
  width: auto;
  margin: 0;
  z-index: 40;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
}

/* Y su sitio en el flujo, para que el último estilo de la galería no quede
   debajo del botón. */
#vestuario { padding-bottom: 76px; }

.vest-guardado {
  min-height: 16px;
  margin: 8px 0 0;
  text-align: center;
  font-size: 13px;
  color: var(--primario);
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;
}

.vest-guardado.visible {
  opacity: 1;
}

/* Muestra de Cobre en el dimmer: ahí el control es un slider, no una perilla,
   así que la miniatura es el material y no el knob (que no aparecería). */
.muestra-cobre {
  background: linear-gradient(160deg, #d9a071, #8a5730) !important;
  box-shadow: 0 0 12px rgba(255, 170, 60, 0.35) !important;
}

/* Selector de skin en el perfil. */
.skin-galeria {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding: 4px 2px 6px;
  scrollbar-width: none;
}
.skin-galeria::-webkit-scrollbar { display: none; }
.skin-op {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  border: none;
  background: none;
  cursor: pointer;
  padding: 4px;
  border-radius: 14px;
}
.skin-op .skin-nom {
  font-size: 12px;
  color: var(--texto-suave);
  font-weight: 600;
  /* El nombre se queda dentro de su ficha. Sin tope se sale por los lados y
     pisa al vecino — le pasa a "Diseñar botón" y le pasaba ya a cualquier skin
     de nombre largo (se admiten hasta 24 caracteres). Parte en dos líneas en
     vez de recortar con puntos suspensivos: el nombre es lo único que dice qué
     es cada ficha, y medio nombre no lo dice. */
  max-width: 84px;
  text-align: center;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.skin-op.activa .skin-muestra {
  outline: 2px solid var(--primario);
  outline-offset: 3px;
}
.skin-op.activa .skin-nom { color: var(--primario); }

/* Fila de acciones de cada sección del panel. Los títulos se fueron cuando
   las secciones pasaron a pestañas: el chip de arriba ya dice en cuál estás, y
   repetirlo debajo era nombrar lo evidente. Sin título, los botones se van a
   la derecha solos (`flex-end`) y el margen de arriba baja: 44px separaban una
   sección de la anterior, y ya no hay anterior. */
.encabezado-admin {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin: 14px 4px;
}
/* Los títulos de las tres secciones solo salen MIENTRAS BUSCAS. Con una
   pestaña a la vista el chip de arriba ya dice cuál es y repetirlo debajo
   sobra; pero el buscador enseña las tres a la vez, y ahí sin título no se
   sabe si "Lobby" es un aparato o un inmueble. */
#tab-gestion .encabezado-admin > h2 { display: none; }
#tab-gestion.buscando .encabezado-admin > h2 { display: block; }
#tab-gestion.buscando .encabezado-admin { justify-content: space-between; }
#seccion-tuya > h2 { display: block !important; }
.encabezado-admin:has(h2) { justify-content: space-between; }
/* El único encabezado que conserva título es el del servicio de Tuya, y ese sí
   necesita separarse: no es otra sección de la pestaña, es un bloque distinto
   debajo de la lista. */
#seccion-tuya { margin-top: 40px; }

.encabezado-admin h2,
#pase-generador h2 {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--texto-suave);
}
#pase-generador h2 {
  margin-bottom: 12px;
}

#lista-registros {
  list-style: none;
  padding: 0;
}

#lista-registros li {
  background: var(--fondo);
  border-radius: 12px;
  padding: 11px 15px;
  margin-bottom: 9px;
  font-size: 13px;
  color: var(--texto-suave);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
}

#lista-registros li.registro-error {
  color: var(--error);
}

/* Una fila con foto deja de ser una línea de texto: la miniatura a la
   izquierda y el texto centrado con ella. */
#lista-registros li.con-cara {
  display: flex;
  align-items: center;
  gap: 11px;
}

.registro-cara {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  border-radius: 10px;
  object-fit: cover;
  cursor: pointer;
  transition: width 0.18s ease, height 0.18s ease;
}

/* Tocada, crece lo suficiente para reconocer una cara. Sigue dentro de la
   fila: no hay visor que abrir ni que cerrar. */
.registro-cara.grande {
  width: 150px;
  height: 190px;
}

#toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--alto-barra) + 18px + env(safe-area-inset-bottom));
  z-index: 60;   /* por encima de la barra (45) */
  transform: translateX(-50%);
  padding: 13px 22px;
  border-radius: 16px;
  font-size: 15px;
  max-width: 90vw;
  background: var(--superficie);
  box-shadow: 8px 8px 18px var(--sombra-oscura), -8px -8px 18px var(--sombra-clara);
}

#toast.toast-ok {
  color: var(--primario);
}

#toast.toast-error {
  color: var(--error);
}

.lista-gestion {
  list-style: none;
  padding: 0;
}

.lista-gestion li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--fondo);
  border-radius: 12px;
  padding: 10px 10px 10px 15px;
  margin-bottom: 9px;
  font-size: 14px;
  color: var(--texto);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
}

.lista-gestion li.inactivo span:first-child {
  opacity: 0.45;
  text-decoration: line-through;
}

/* Encabezado de grupo por proveedor en la lista de gestión. */
.lista-gestion li.grupo-gestion {
  display: block;
  background: none;
  box-shadow: none;
  padding: 16px 4px 4px;
  margin-bottom: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--texto-suave);
}

.lista-gestion li.grupo-gestion:first-child {
  padding-top: 2px;
}

.titulo-editor {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 16px;
}

.campo {
  display: block;
  font-size: 13px;
  color: var(--texto-suave);
  margin-bottom: 4px;
}

.campo > span {
  display: block;
  margin-bottom: 5px;
}

/* Un campo sin etiqueta no debe dejar el hueco de la etiqueta. */
.campo > span:empty {
  display: none;
}

/* Selección múltiple (inmuebles, dispositivos): lista vertical con scroll. */
.casillas {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 2px;
  margin: 6px 0 12px;
  max-height: 220px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.casilla {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--texto);
  background: var(--fondo);
  border-radius: 10px;
  padding: 9px 13px;
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara);
  cursor: pointer;
}

/* El checkbox no debe heredar el estilo de input de texto de `.campo input`
   (fondo + sombra + appearance:none lo convertían en un círculo gris). */
.casilla input {
  accent-color: var(--primario);
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  padding: 0;
  margin: 0;
  border-radius: 4px;
  background: none;
  box-shadow: none;
  -webkit-appearance: auto;
  appearance: auto;
}

.casilla.marcada {
  color: var(--primario);
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara), inset 0 0 0 1.5px var(--primario);
}

/* Dentro de las listas: solo check + texto (sin recuadro ni borde, que se
   confundía con un botón). Los checkbox sueltos (Cuenta activa) no cambian. */
.casillas .casilla {
  display: flex;
  gap: 11px;
  padding: 8px 6px;
  border-radius: 8px;
  background: transparent;
  box-shadow: none;
  font-size: 15px;
}
.casillas .casilla.marcada {
  color: var(--primario);
  background: transparent;
  box-shadow: none;
}

.barra-editor {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
}

.barra-editor .btn-primario {
  flex: 1;
  min-width: 130px;
}

.btn-peligro {
  border-radius: 12px;
  padding: 8px 16px;
  min-height: 38px;
  font-size: 14px;
  color: var(--error);
  background: var(--superficie);
  box-shadow: 4px 4px 10px var(--sombra-oscura), -4px -4px 10px var(--sombra-clara);
}

.btn-peligro:not(:disabled):active {
  box-shadow: inset 4px 4px 8px var(--sombra-oscura), inset -4px -4px 8px var(--sombra-clara);
}

.oculto {
  display: none !important;
}

/* ---- Pases (acceso compartido por enlace) ---- */
.ayuda-pase {
  color: var(--texto-suave);
  font-size: 14px;
  line-height: 1.45;
  margin: 0 0 14px;
}

.pase-disp {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
}

.pase-casilla {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 4px;
  border-radius: 8px;
  background: transparent;
  color: var(--texto);
  cursor: pointer;
}

/* El checkbox real queda oculto (accesible) y el label lo alterna; el toggle
   visual es una pieza aparte (.pase-tgl) — en iOS Safari desliza más confiable
   que un pseudo-elemento sobre el <input>. Toggle a la izquierda, nombre a la
   derecha, sin icono. */
.pase-casilla input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.pase-tgl {
  flex: 0 0 auto;
  width: 44px;
  height: 25px;
  border-radius: 999px;
  position: relative;
  background: var(--superficie);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
  transition: background 0.15s ease;
}

.pase-tgl::before {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: var(--texto-suave);
  transition: left 0.15s ease, background 0.15s ease;
}

input:checked ~ .pase-tgl {
  background: var(--primario);
}

input:checked ~ .pase-tgl::before {
  left: 22px;
  background: #14210a;
}

.pase-nom {
  font-size: 16px;
  font-weight: 600;
  color: var(--texto);
}

/* Grupos desplegables por tipo (Puertas, Ascensores…) en Compartir acceso. */
.pase-grupo {
  border-radius: 12px;
  background: var(--superficie);
  box-shadow: 3px 3px 8px var(--sombra-oscura), -3px -3px 8px var(--sombra-clara);
  overflow: hidden;
}

.pase-grupo-cab {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 14px;
  cursor: pointer;
  list-style: none;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto);
  -webkit-user-select: none;
  user-select: none;
}
.pase-grupo-cab::-webkit-details-marker { display: none; }

.pase-grupo-flecha {
  flex: 0 0 auto;
  color: var(--texto-suave);
  transition: transform 0.18s ease;
}
.pase-grupo[open] .pase-grupo-flecha {
  transform: rotate(90deg);
}

.pase-grupo-tit {
  flex: 1;
}

.pase-grupo-conteo {
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  border-radius: 999px;
  background: var(--primario);
  color: #14210a;
  font-size: 13px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.pase-grupo-conteo[hidden] { display: none; }

.pase-grupo-cuerpo {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 12px 8px;
}

.pase-duracion {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.chip-dur {
  flex: 1 1 30%;
  padding: 10px 12px;
  border: none;
  border-radius: 12px;
  background: var(--superficie);
  color: var(--texto-suave);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 3px 3px 7px var(--sombra-oscura), -3px -3px 7px var(--sombra-clara);
}

.chip-dur.activa {
  color: var(--primario);
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara), inset 0 0 0 1.5px var(--primario);
}

/* Rueda de duración: una sola fila con scroll horizontal y snap. Se ve un
   valor faded a cada lado (3 visibles) y el del centro es el elegido. Los
   ítems miden 1/3 del ancho y el padding lateral es de 1/3 para que el primero
   y el último puedan centrarse. */
.dur-rueda-wrap {
  flex: 1 1 100%;
  position: relative;
  border-radius: 12px;
  background: var(--superficie);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
  overflow: hidden;
}

/* Ventana central que enmarca el valor elegido. */
.dur-rueda-wrap::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 30%;
  height: 40px;
  transform: translate(-50%, -50%);
  border-radius: 9px;
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.06);
  pointer-events: none;
  z-index: 2;
}

.dur-rueda-wrap.activa::before {
  box-shadow: inset 0 0 0 1.5px var(--primario);
}

.dur-rueda {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding: 9px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 34%, #000 66%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 34%, #000 66%, transparent);
}

.dur-rueda::-webkit-scrollbar {
  display: none;
}

/* Espaciadores de 1/3 al inicio y al final: permiten centrar el primer y el
   último valor. Van sin padding en la rueda para que el 33.33% de cada ítem
   se mida sobre el ancho completo (si no, el padding % encoge el basis %). */
.dur-esp {
  flex: 0 0 33.3333%;
}

.dur-op {
  flex: 0 0 33.3333%;
  scroll-snap-align: center;
  border: none;
  background: none;
  color: var(--texto-suave);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 6px 0;
  cursor: pointer;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: color 0.12s ease, font-size 0.12s ease;
}

.dur-op.centro {
  color: var(--texto);
  font-size: 16px;
}

.dur-rueda-wrap.activa .dur-op.centro {
  color: var(--primario);
}

/* Fila de chips excluyentes: "Míos / Todos" en Mis pases y el filtro de
   Actividad. El nombre se quedó de donde nació. */
.pase-scope {
  display: flex;
  gap: 8px;
  margin: 0 0 12px;
}
.chip-scope {
  padding: 6px 16px;
  border: none;
  border-radius: 10px;
  background: var(--superficie);
  color: var(--texto-suave);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 3px 3px 7px var(--sombra-oscura), -3px -3px 7px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}
.chip-scope.activa {
  color: var(--primario);
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara), inset 0 0 0 1.5px var(--primario);
}

/* Tipo de enlace: Simple / Multiuso, con una "i" que despliega la ayuda. */
.pase-multi {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

/* Sin el margen inferior que .pase-scope trae por defecto: sumaba alto al
   bloque de chips y align-items:center dejaba la "i" corrida hacia abajo. */
.pase-multi .pase-scope {
  margin: 0;
}

.btn-info {
  flex: 0 0 auto;
  /* Flex para centrar la "i" ópticamente dentro del círculo: con line-height
     sola la itálica queda descuadrada. */
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 13px;
  height: 13px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--superficie);
  color: var(--texto-suave);
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  box-shadow: 1px 1px 3px var(--sombra-oscura), -1px -1px 3px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}

/* El círculo es pequeño a propósito, pero el área que se toca se extiende
   con un pseudo-elemento invisible para que siga siendo cómoda en el móvil. */
.btn-info::after {
  content: '';
  position: absolute;
  inset: -9px;
}

.btn-info[aria-expanded="true"] {
  color: var(--primario);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
}

.pase-ayuda {
  /* Aire arriba: el margen negativo venía de cuando esta ayuda colgaba de un
     par de fichas pegado a ella. Ahora cuelga de los botones de acción y
     quedaba encima de ellos. */
  margin: 14px 0 4px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--texto-suave);
}

.pase-ayuda strong {
  color: var(--texto);
  font-weight: 600;
}

.pase-resultado {
  margin-top: 18px;
}

.pase-ok {
  color: var(--primario);
  font-size: 14px;
  margin: 0 0 10px;
}

.pase-url {
  width: 100%;
  padding: 11px 12px;
  border: none;
  border-radius: 10px;
  background: var(--fondo);
  color: var(--texto);
  font-size: 13px;
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
  margin-bottom: 10px;
}

.pase-acciones {
  display: flex;
  gap: 10px;
}

.pase-acciones .btn-secundario {
  flex: 1;
}

.pase-info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.pase-info strong {
  color: var(--texto);
  font-size: 15px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pase-meta {
  color: var(--texto-suave);
  font-size: 12.5px;
}

/* Estado del vencimiento: activo (verde) / vencido (rojo). */
.vence-ok { color: var(--primario); font-weight: 500; }
.vence-mal { color: var(--error); font-weight: 500; }

/* Campo Evento del generador de pases + su etiqueta en "Mis pases". */
.pase-evento {
  width: 100%;
  padding: 12px 14px;
  margin: 10px 0;
  border: none;
  border-radius: 12px;
  background: var(--fondo);
  color: var(--texto);
  font-size: 15px;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
.pase-evento::placeholder { color: var(--texto-suave); }
.pase-evento:focus {
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara), 0 0 0 1.5px var(--primario);
}
.pase-evento-lbl {
  color: var(--texto);
  font-size: 13px;
  font-weight: 500;
}

.pase-fila-acciones {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

/* El detalle de canjes se despliega en una fila completa debajo. */
.fila-pase { flex-wrap: wrap; }
.pase-detalle {
  flex-basis: 100%;
  width: 100%;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(141, 146, 151, 0.18);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pase-detalle-item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
}
.pase-detalle-item > span:first-child {
  color: var(--texto);
  font-size: 13px;
}

.pase-estado {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 3px 8px;
  border-radius: 8px;
}

.estado-activo { color: var(--primario); background: rgba(165, 255, 46, 0.12); }
.estado-usado { color: var(--texto-suave); background: rgba(141, 146, 151, 0.16); }
.estado-vencido { color: var(--texto-suave); background: rgba(141, 146, 151, 0.16); }
.estado-revocado { color: var(--error); background: rgba(240, 149, 149, 0.14); }

.btn-mini {
  border: none;
  border-radius: 9px;
  padding: 7px 11px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  background: var(--superficie);
  color: var(--texto-suave);
  box-shadow: 2px 2px 5px var(--sombra-oscura), -2px -2px 5px var(--sombra-clara);
}

.btn-mini:not(:disabled):active {
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
}

.btn-mini-peligro {
  color: var(--error);
}

.lista-gestion li.vacio {
  justify-content: center;
  color: var(--texto-suave);
  font-size: 14px;
  box-shadow: none;
  background: transparent;
}

/* ===== Crear un skin con IA (solo admin, dentro del Locker) ===== */
/* La previa usa el MISMO anillo y botón que la app de verdad, para que lo que
   ves antes de publicar sea exactamente lo que verá el vecino. */
.skin-previa {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.skin-previa-anillo {
  align-self: center;
  margin: 4px 0 8px;
}

.skin-previa .boton-circular.grande {
  width: 168px;
  height: 168px;
  cursor: default;
}

.skin-lista {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

.skin-fila {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border-radius: 14px;
  background: var(--fondo);
}

.skin-fila img {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}

.skin-fila span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}

/* "esperando" = hecho por un vecino y todavía no publicado en la galería. Va
   como pastilla al final de la fila; sin el `flex: 0 0 auto` heredaría el
   `flex: 1` del nombre y se llevaría media línea. */
.skin-estado {
  flex: 0 0 auto;
  font-size: 11px;
  letter-spacing: .04em;
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--texto-suave);
  background: var(--superficie);
}

.btn-borrar-skin {
  background: none;
  border: none;
  color: var(--texto-suave);
  font-size: 13px;
  padding: 6px 8px;
  cursor: pointer;
}

.btn-borrar-skin:active { color: var(--error); }

/* El textarea hereda el estilo de los inputs, pero no su fuente ni su altura:
   sin esto sale en monospace del navegador y se puede estirar a lo ancho. */
#form-skin textarea,
.campo textarea {
  font-family: inherit;
  line-height: 1.4;
  resize: vertical;
  min-height: 62px;
}

/* Interruptor con foto de la galería (una luz, un relé). El aro verde de
   .activo es un box-shadow interior, y la imagen lo tapa: el encendido se lee
   en la propia foto (apagada se atenúa) más un aro dibujado encima. Se acota a
   .medio para no tocar los botones de pulso con foto (Bordado, Hal, Argentina),
   que nunca llevan .activo y quedarían apagados para siempre. */
.boton-circular.medio.boton-imagen .boton-logo {
  filter: brightness(0.4) saturate(0.5);
  transition: filter 0.25s ease;
}

.boton-circular.medio.boton-imagen.activo .boton-logo {
  filter: none;
}

.boton-circular.medio.boton-imagen.activo::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: inset 0 0 0 2px rgba(165, 255, 46, 0.85);
  pointer-events: none;
  z-index: 2;
}

/* Chips de "para qué tipos sirve este botón", al crear y al editar. */
.skin-tipos {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

.chip-tipo {
  border: none;
  border-radius: 999px;
  padding: 7px 13px;
  font-size: 12.5px;
  font-family: inherit;
  color: var(--texto-suave);
  background: var(--fondo);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
  cursor: pointer;
}

.chip-tipo.activa {
  color: var(--primario);
  box-shadow: inset 0 0 0 1.5px rgba(165, 255, 46, 0.6);
}

/* La fila del skin abre su editor al tocarla. */
.skin-item { border-radius: 14px; overflow: hidden; }
.skin-fila { cursor: pointer; }

.skin-editor {
  padding: 4px 10px 12px;
  background: var(--fondo);
  border-radius: 0 0 14px 14px;
  margin-top: -4px;
}

/* Una sola acción con cuerpo y el resto en voz baja. Antes las tres iban en
   fila con el mismo peso —guardar, borrar y quitar de la galería— y había que
   leerlas todas para saber cuál era la normal. */
.skin-acciones { margin-top: 8px; }

.skin-acciones .btn-secundario {
  display: block;
  width: 100%;
  margin: 0;
}

.skin-acciones-menores {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 18px;
  margin-top: 12px;
}

/* Acciones habladas, no botones: salir, borrar, curar. Ninguna es lo que vienes
   a hacer aquí, así que ninguna compite con Guardar cambios. */
.btn-quieto {
  background: none;
  border: none;
  color: var(--texto-suave);
  font-size: 13px;
  padding: 6px 8px;
  cursor: pointer;
}
.btn-quieto:active { color: var(--primario); }

/* El nombre dentro de un interruptor con foto: el estilo base está pensado
   para el botón grande (168px) y en uno de 92 queda alto y se pierde sobre la
   imagen. Se baja y se le pone un velo degradado que lo despega del fondo. */
.boton-circular.medio.boton-imagen .nombre-boton {
  bottom: 9px;
}

/* El velo se va: oscurecía media ilustración para hacerle sitio a un texto que
   ya no existe.
   Estaba para que `.nombre-boton` se leyera DENTRO del botón, sobre la foto.
   Pero ese nombre ya no se pinta en ningún lado —no aparece ni en el HTML ni en
   el JavaScript— y los rótulos que se ven ("Sala", "Pared") van debajo del
   círculo, sobre el fondo de la app, donde no hace falta ningún velo.
   Mismo caso que la ilustración de la cámara, y misma decisión: se queda la
   regla con el degradado a cero en vez de borrarla, porque el día que un nombre
   vuelva adentro esto es cambiar un número. */
.boton-circular.medio.boton-imagen::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 52%;
  background: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
  pointer-events: none;
  z-index: 1;
}

/* ===== Aspecto Ascensor: botón de llamada de acero =====
   La foto viene con el aro y la flecha YA encendidos, así que el reposo se
   consigue apagándolos con filter (queda como un difusor sin luz) y al pulsar
   se quita el filtro y el ámbar sale además ALREDEDOR del botón, como una
   llamada registrada. Un solo bitmap para los dos estados. */
.boton-ascensor {
  transition: box-shadow 0.35s ease;
}

.boton-ascensor .boton-logo {
  filter: saturate(0.12) brightness(0.68);
  transition: filter 0.35s ease;
}

.boton-ascensor.enviando .boton-logo,
.boton-ascensor.exito .boton-logo {
  filter: none;
}

.boton-ascensor.enviando,
.boton-ascensor.exito {
  box-shadow: 9px 9px 18px var(--sombra-oscura), -9px -9px 18px var(--sombra-clara),
              0 0 30px 7px rgba(255, 176, 62, 0.7);
}

/* La miniatura del Locker no usa .boton-logo sino .skin-muestra img, así que
   el reposo del ascensor hay que apagárselo también aquí. */
.skin-muestra.boton-ascensor img {
  filter: saturate(0.12) brightness(0.68);
}

/* ===== Aspecto Sabiem: placa de llamada de ascensor a la antigua =====
   Una placa vertical, no un botón redondo: control propio, como el Jet Switch.
   La foto trae los rótulos apagados; encenderlos son dos capas transparentes
   del MISMO tamaño que la placa (las letras ya vienen en su sitio), así no hay
   posiciones que calcular ni que se descuadren al cambiar el tamaño. */
.control-sabiem {
  gap: 10px;
}

.sabiem-placa {
  position: relative;
  width: 150px;
  height: 302px;      /* proporción de la placa original (605x1216) */
  border-radius: 7px;
  background: url("sabiem.webp?v=1") center/100% 100% no-repeat;
  box-shadow: 6px 6px 16px var(--sombra-oscura), -6px -6px 16px var(--sombra-clara);
}

.sabiem-luz {
  position: absolute;
  inset: 0;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

/* Tres halos apilados: el fino define la letra, el medio la enciende y el
   ancho tiñe el vidrio verde alrededor, que es lo que hace que parezca luz de
   verdad y no letras pintadas de amarillo. */
.sabiem-luz.on {
  opacity: 1;
  filter: drop-shadow(0 0 3px rgba(255, 225, 140, 1))
          drop-shadow(0 0 9px rgba(255, 180, 40, 0.95))
          drop-shadow(0 0 22px rgba(255, 150, 20, 0.6));
}

.sabiem-ocupado { background-image: url("sabiem-ocupado.png?v=2"); }
.sabiem-llegando { background-image: url("sabiem-llegando.png?v=2"); }

/* Zona de toque sobre el botón negro de la foto (medido: centro 49.7% / 65.4%,
   diámetro 22% del ancho). Se deja algo más grande que el botón para que no
   haya que apuntar. */
.sabiem-boton {
  position: absolute;
  left: 49.7%;
  top: 65.4%;
  width: 34%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: none;
  border-radius: 50%;
  background: none;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.sabiem-boton:active {
  box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.55);
}

/* Miniatura del Locker: la placa es vertical y el hueco es redondo, así que se
   muestra entera con `contain` en vez de recortarla. */
.muestra-sabiem {
  background: url("sabiem.webp?v=1") center/auto 100% no-repeat;
}

/* ===== Aspecto Bronce: botón de ascensor industrial =====
   Mismo patrón que el de acero, pero el filtro del reposo conserva algo de
   saturación: el bisel ES de bronce y desaturarlo del todo lo volvería gris,
   perdiendo justo lo que lo distingue. */
.boton-bronce {
  transition: box-shadow 0.35s ease;
}

.boton-bronce .boton-logo {
  filter: saturate(0.4) brightness(0.52);
  transition: filter 0.35s ease;
}

.boton-bronce.enviando .boton-logo,
.boton-bronce.exito .boton-logo {
  filter: none;
}

.boton-bronce.enviando,
.boton-bronce.exito {
  box-shadow: 9px 9px 18px var(--sombra-oscura), -9px -9px 18px var(--sombra-clara),
              0 0 30px 7px rgba(255, 176, 62, 0.7);
}

.skin-muestra.boton-bronce img {
  filter: saturate(0.4) brightness(0.52);
}

/* ===== Aspecto Lobby: pulsador de puerta de personas =====
   Mismo patrón que los de ascensor: la foto viene encendida y el reposo se
   consigue apagándola. Aquí se conserva algo de saturación porque el aro y el
   rótulo son ámbar sobre gunmetal y a saturación 0 se volvían plomo. */
.boton-lobby {
  transition: box-shadow 0.35s ease;
}

.boton-lobby .boton-logo {
  filter: saturate(0.28) brightness(0.5);
  transition: filter 0.35s ease;
}

.boton-lobby.enviando .boton-logo,
.boton-lobby.exito .boton-logo {
  filter: none;
}

.boton-lobby.enviando,
.boton-lobby.exito {
  box-shadow: 9px 9px 18px var(--sombra-oscura), -9px -9px 18px var(--sombra-clara),
              0 0 30px 7px rgba(255, 176, 62, 0.7);
}

.skin-muestra.boton-lobby img {
  filter: saturate(0.28) brightness(0.5);
}

/* Nota corta debajo de un campo replegado, para explicar cuándo hace falta. */
.campo-ayuda {
  font-size: 12.5px;
  color: var(--texto-suave);
  margin: 6px 0 0;
  line-height: 1.45;
}


/* Buscador de una lista de gestión. */
.buscador {
  width: 100%;
  padding: 11px 14px;
  margin: 0 0 6px;
  border: none;
  border-radius: 12px;
  background: var(--fondo);
  color: var(--texto);
  font-size: 15px;
  font-family: inherit;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}

.buscador::placeholder { color: var(--texto-suave); }

/* ===== Aspecto Mando: el control remoto del portón =====
   La foto recortada ES el control. La zona de toque va sobre el botón crema
   (medido: 25.9% / 14.3%, 47.7% x 21.2%), no sobre todo el mando: es lo que se
   aprieta de verdad, y evita aperturas por rozar el cuerpo. */
.control-mando {
  gap: 10px;
}

.mando-cuerpo {
  position: relative;
  width: 170px;
  height: 247px;   /* proporción del recorte (386x560) */
  background: url("mando.webp?v=1") center/100% 100% no-repeat;
  filter: drop-shadow(4px 6px 10px rgba(0, 0, 0, 0.55));
}

.mando-boton {
  position: absolute;
  left: 25.9%;
  top: 14.3%;
  width: 47.7%;
  height: 21.2%;
  border: none;
  border-radius: 4px;
  background: none;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: box-shadow 0.12s ease, transform 0.12s ease;
}

/* Al apretar, el botón se hunde y vuelve solo (`pulsado` se quita a los 1,1 s).
   NO se usan aquí las clases .enviando/.exito de `pulsar`: esas duran lo que
   tarda el portón en abrir, y un mando no se queda hundido 15 segundos. */
.mando-boton:active,
.mando-boton.pulsado {
  transform: translateY(2px) scale(0.94);
  box-shadow: inset 0 0 14px rgba(0, 0, 0, 0.75);
}

/* Miniatura del Locker: el mando entero dentro del círculo. */
.muestra-mando {
  background: var(--superficie) url("mando.webp?v=1") center/auto 74% no-repeat;
}

/* Generar con IA o subir del carrete: las dos vías, del mismo tamaño. */
.skin-origen {
  display: flex;
  gap: 10px;
}

.skin-origen > * {
  flex: 1;
  margin: 0;
}

/* El input de archivo va DENTRO de la etiqueta y oculto ahí, no con .oculto
   (display:none): así el toque en la etiqueta abre el selector del teléfono de
   forma fiable en iOS. */
.btn-archivo {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.btn-archivo input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* La previa es recortable: la foto se posiciona y transforma a mano, así que se
   anulan el 100%/cover de .boton-logo. El contenedor circular la recorta. */
.skin-previa .boton-circular {
  cursor: grab;
  touch-action: none;   /* los gestos son nuestros, no del navegador */
}

.skin-previa .boton-circular:active { cursor: grabbing; }

.skin-previa .boton-logo {
  position: absolute;
  left: 50%;
  top: 50%;
  width: auto;
  height: auto;
  max-width: none;
  /* `fill`, no `none`: con `none` el bitmap se dibuja a tamaño natural y se
     ignora el width/height que le pone el JS, así que la previa no coincidía
     con lo que se publica. El recuadro ya lleva la proporción correcta. */
  object-fit: fill;
  border-radius: 0;
  transform-origin: center center;
}

/* Pista y deslizador del recorte. */
.recorte-pista {
  text-align: center;
  margin: 0 0 8px;
}

.zoom-recorte {
  width: 100%;
  margin: 0 0 10px;
  -webkit-appearance: none;
  appearance: none;
  background: none;
}

.zoom-recorte::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: 999px;
  background: var(--fondo);
  box-shadow: inset 2px 2px 5px var(--sombra-oscura), inset -2px -2px 5px var(--sombra-clara);
}

.zoom-recorte::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -7px;
  border-radius: 50%;
  background: var(--primario);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
  cursor: grab;
}

.zoom-recorte::-moz-range-track {
  height: 8px;
  border-radius: 999px;
  background: var(--fondo);
}

.zoom-recorte::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 50%;
  background: var(--primario);
}

/* Los botones grandes se achican para que dos quepan enteros en la pantalla:
   220px (168 + 26 de aro a cada lado) no caben dos en un teléfono. */
.compacto .anillo {
  padding: 17px;
}

.compacto .boton-circular.grande {
  width: 122px;
  height: 122px;
}

.compacto .boton-circular.grande svg {
  width: 46px;
  height: 46px;
}

/* ===== Control espejado (columna derecha) =====
   En la columna derecha el pulgar tapa el costado derecho, justo donde está la
   columna de luces de la rueda. Se espeja el control entero: el rodillo es
   simétrico y las luces son círculos, así que lo único que cambia de verdad es
   de qué lado quedan. El gesto es vertical, así que no se invierte nada. */
.espejo .rueda-ctrl {
  transform: scaleX(-1);
}

/* Encabezado de grupo con algo caído: el naranja del fuego, que ya se usa para
   avisar sin ser una alarma roja. Va con la MISMA especificidad que
   `.lista-gestion li.grupo-gestion`, que si no le gana y no se ve. */
.lista-gestion li.grupo-gestion.grupo-alerta {
  color: var(--fuego);
}

/* Traer dispositivos de Tuya: botón y lista apilados dentro de su campo, con
   aire propio. Antes iban sueltos y se solapaban con el control de arriba.
   El margen de abajo iguala el de un input, para que el campo siguiente
   respire lo mismo que los demás. */
.tuya-lista {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 8px;
}

.tuya-lista > .btn-secundario,
.tuya-lista > select {
  margin: 0;
  width: 100%;
}

/* En medio de una columna de campos hundidos, el relieve del botón se lee
   como un halo. Aquí va hundido igual que ellos, y el texto se queda gris:
   en verde resaltaba más que el propio Guardar. */
.tuya-lista > .btn-secundario {
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}

.tuya-lista > .btn-secundario:not(:disabled):active {
  opacity: 0.6;
}

.tuya-lista > .dps-detectados:empty {
  display: none;
}

.tuya-lista > .dps-detectados {
  margin-top: 0;
}

/* Inmuebles en árbol: un edificio se pliega y sus apartamentos van dentro.
   La fila del padre imita a `.lista-gestion li` (que aquí no aplica, porque el
   li solo envuelve al <details>), y la flecha reaprovecha .pase-grupo-flecha:
   solo hace falta añadirle el giro, que allí está atado a .pase-grupo[open]. */
.lista-gestion li.inm-rama {
  display: block;
  padding: 0;
  background: none;
  box-shadow: none;
}

.inm-rama > details > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 10px 12px;
  border-radius: 12px;
  background: var(--fondo);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none;
  user-select: none;
}
.inm-rama > details > summary::-webkit-details-marker { display: none; }

.lista-gestion li.inm-hoja {
  padding-left: 40px;   /* 12 del summary + 18 de la flecha + 10 del hueco */
}

.inm-rama > details > summary > span {
  flex: 1;
}

/* El conteo de lo que hay dentro, sin gritar. */
.inm-rama > details > summary em {
  font-style: normal;
  color: var(--texto-suave);
}

.inm-rama > details[open] .pase-grupo-flecha {
  transform: rotate(90deg);
}

/* Los hijos, sangrados y colgando de una línea, para que se lea de un vistazo
   qué está dentro de qué. */
.inm-rama > details > .lista-gestion {
  margin: 9px 0 0 14px;
  padding-left: 14px;
  border-left: 1px solid var(--sombra-oscura);
}

/* Alta de vecinos en lote: una fila por unidad. En móvil se apila (la unidad
   arriba, los tres campos debajo) y en pantalla ancha va todo en línea. */
.lote-vecinos {
  display: flex;
  flex-direction: column;
  /* Aire ENTRE apartamentos. Dentro de uno las filas van pegadas: la distancia
     es lo que dice cuáles son de la misma casa, y sin esa diferencia una fila
     que no repite el número parecía suelta. */
  gap: 18px;
  margin-bottom: 10px;
  max-height: 50vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Las filas de UN apartamento: la primera trae el número y las demás cuelgan
   de ella. */
.lote-grupo {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.lote-fila {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  align-items: center;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--fondo);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
}

.lote-unidad {
  font-size: 14px;
  font-weight: 600;
  color: var(--texto);
}

/* El número repetido en las filas de continuación. Apagado y sin negrita: dice
   de qué casa es la fila que estás llenando sin competir con el primero, que es
   el que encabeza el apartamento. */
.lote-unidad-eco {
  font-weight: 400;
  color: var(--texto-suave);
}

/* Y mientras se escribe, el apartamento entero se enciende: ata el campo que
   tienes debajo del dedo con su número, que es justo la duda. */
.lote-grupo:focus-within .lote-unidad {
  color: var(--primario);
}

/* Su propia línea, de lado a lado. La fila es una rejilla de cuatro columnas
   —número y tres campos— y esto es un quinto elemento: sin sacarlo de la
   cuenta, empujaba el correo a la línea siguiente y lo metía en la columna
   estrecha del número. */
.lote-ocupado {
  grid-column: 1 / -1;
  font-size: 13px;
  color: var(--texto-suave);
}

/* Los inputs de la fila no son `.campo input`, así que se les da aquí el
   mismo aspecto hundido, un punto más bajo por ir tres seguidos. */
.lote-fila input {
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: 10px;
  background: var(--superficie);
  color: var(--texto);
  font-size: 16px;
  font-family: inherit;
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara);
  outline: none;
  -webkit-appearance: none;
}

.lote-fila input:focus {
  box-shadow: inset 3px 3px 6px var(--sombra-oscura), inset -3px -3px 6px var(--sombra-clara), 0 0 0 1.5px var(--primario);
}

/* La fila que llegó por el enlace de alta, no escrita a mano. Un filo verde y
   nada más: hay que poder distinguirlas de un vistazo, pero son filas normales
   —se corrigen y se borran igual— y una tarjeta con su propio fondo las haría
   parecer otra cosa. */
.lote-fila.lote-pedida {
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara),
    inset 3px 0 0 var(--primario);
}

/* Los que pidieron acceso y no traen apartamento. Van ARRIBA del listado: es
   lo que hay que resolver antes de nada, porque hasta que no los ubicas no
   tienen dónde caer. */
.alta-sin-casa {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 14px;
}

/* Dos columnas: quién es y dónde lo pones. No hereda las cuatro de la fila
   normal porque aquí no hay tres campos que llenar, hay una decisión. */
.alta-suelta {
  grid-template-columns: 1fr;
  gap: 8px;
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara),
    inset 3px 0 0 var(--aviso);
}

.alta-suelta .lote-ocupado {
  color: var(--texto);
}

@media (min-width: 620px) {
  .alta-suelta {
    grid-template-columns: 1fr 190px;
  }
  /* La vuelve a poner en su columna: la regla general la manda de lado a lado
     y aquí sí es una celda más. */
  .alta-suelta .lote-ocupado {
    grid-column: auto;
  }
}

/* El enlace que se reparte, al final del editor: se usa una vez y luego se
   espera, así que no puede estar por encima del trabajo de todos los días. */
.alta-enlace {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--sombra-clara);
}

@media (min-width: 620px) {
  .lote-fila {
    grid-template-columns: 70px 1fr 1fr 1.4fr;
    gap: 8px;
  }
}

/* ---- Conexión: una ficha por aparato, peor primero ----
   En rejilla y no en filas de ancho completo: con catorce aparatos —y van a
   ser más— una fila por cabeza obliga a desplazarse para ver el conjunto, que
   es justo lo que esta pantalla tiene que dar de un vistazo. Cada ficha lleva
   lo mismo, apilado en vez de en línea.
   `auto-fill` con 140px: dos columnas en el teléfono, las que quepan en
   pantalla grande. `1fr` en las filas iguala el alto de todas a la más alta,
   así un nombre de dos líneas no deja escalones. */
.lista-con {
  list-style: none;
  padding: 0;
  margin: 0 0 18px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  grid-auto-rows: 1fr;
  gap: 8px;
}

.fila-con {
  display: flex;
  flex-direction: column;
  padding: 12px;
  border-radius: 14px;
  background: var(--superficie);
  box-shadow: 4px 4px 10px var(--sombra-oscura), -4px -4px 10px var(--sombra-clara);
}
.con-nombre {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
/* El porcentaje manda en la ficha: es lo único que se compara entre unas y
   otras al recorrerlas con la vista. */
.con-pct {
  font-weight: 600;
  font-size: 20px;
  color: var(--primario);
  margin-top: 6px;
}
.con-detalle { font-size: 11px; color: var(--texto-suave); margin: 2px 0 10px; }

/* Por debajo del 99 % deja de ser un detalle. Lo marca el color, no una
   palabra: en una lista que se lee de un vistazo, el rojo es más rápido. Y lo
   marca la ficha entera, para que el número y la barra no digan cosas
   distintas. */
.fila-con.con-flojo .con-pct { color: var(--error); }
.fila-con.con-flojo .con-barra i { background: var(--error); }

/* La barra abajo del todo, empujada por el `auto` para que quede a la misma
   altura en todas las fichas aunque los nombres ocupen distinto. */
.con-barra {
  height: 4px;
  margin-top: auto;
  padding-top: 0;
  border-radius: 2px;
  background: var(--fondo);
  box-shadow: inset 1px 1px 3px var(--sombra-oscura);
  overflow: hidden;
}
.con-barra i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--primario);
  margin-top: 0;
}

/* Los mensajes de "no hay nada" no son fichas: ocupan la rejilla entera y van
   sin relieve, o un aviso de una línea parecería un aparato más. */
.con-vacio {
  grid-column: 1 / -1;
  color: var(--texto-suave);
  font-size: 13px;
}

/* El aparato que se cae seguido: muchas caídas cortas. El porcentaje puede
   verse bien —vuelve enseguida— y aun así ser el más inestable de todos. */
.con-seguido { color: var(--aviso); font-weight: 600; }

/* ---- El mapa de zonas ----
   Teselas oscuras de CARTO, no el mapa claro de siempre: un rectángulo blanco
   en medio de una app oscura se lee como un error de carga. */
#mapa-zonas {
  height: 320px;
  border-radius: 16px;
  margin-bottom: 10px;
  background: var(--fondo);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  z-index: 0;   /* Leaflet crea capas con z-index alto; sin esto se sube encima de la barra de abajo */
}
.leaflet-container { background: var(--fondo); font-family: inherit; }
.leaflet-control-attribution {
  background: rgba(0, 0, 0, 0.5) !important;
  color: var(--texto-suave) !important;
  font-size: 10px;
}
.leaflet-control-attribution a { color: var(--texto-suave); }
/* Los controles de zoom, en el lenguaje de la app y no en el de Leaflet. */
.leaflet-bar a {
  background: var(--superficie);
  color: var(--texto);
  border-bottom-color: var(--fondo);
}
.leaflet-bar a:hover { background: var(--superficie); color: var(--primario); }

/* Un PUNTO por zona. Se probó con una píldora que llevaba el nombre y la
   cuenta, como el precio en el mapa de una app de alquiler, y en Caracas no
   cabe: las zonas del este están a menos de un kilómetro y en un teléfono
   siete nombres se pisan. El punto no se estorba a ningún zoom; el detalle se
   cuenta al tocarlo. */
.pin-envoltura { background: none; border: none; }
.punto-zona {
  display: block;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;   /* centrado en su coordenada */
  border-radius: 50%;
  background: var(--primario);
  /* El halo oscuro lo despega del mapa: sin él, un punto verde sobre una calle
     clara se pierde. */
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.6);
  cursor: pointer;
}
.punto-zona.punto-mal { background: var(--error); }

/* El globo: por inmueble, con sus aparatos. */
.globo-zona { max-height: 220px; overflow-y: auto; }
.globo-zona > strong { display: block; margin-bottom: 6px; font-size: 14px; }
.globo-inmueble {
  margin: 8px 0 3px;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--texto-suave);
}
.globo-disp { display: flex; align-items: center; gap: 7px; padding: 1px 0; }
.punto-con.con-nada { background: var(--texto-suave); }

.leaflet-popup-content-wrapper, .leaflet-popup-tip {
  background: var(--superficie);
  color: var(--texto);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
}
.leaflet-popup-content { font-size: 13px; line-height: 1.5; }

/* El mapita del editor: donde se suelta el pin del inmueble. Más bajo que el
   mapa de zonas —es un campo de un formulario, no una pantalla— y con el mismo
   hundido que el resto de los campos. */
.mapa-pin {
  height: 200px;
  border-radius: 12px;
  background: var(--fondo);
  box-shadow: inset 3px 3px 7px var(--sombra-oscura), inset -3px -3px 7px var(--sombra-clara);
  z-index: 0;
}
.mapa-pin-pie {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 6px 4px 14px;
  font-size: 12px;
  color: var(--texto-suave);
  font-variant-numeric: tabular-nums;
}

/* ---- Sensor: informa, no obedece ----
   Sin relieve de botón ni nada que invite al dedo. Es una lámpara con una
   frase: quien la mire tiene que entender en un segundo que esto no se pulsa. */
.control-sensor { cursor: default; }
.sensor-ficha {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 22px;
  border-radius: 18px;
  background: var(--superficie);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
}
.sensor-luz {
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--texto-suave);
  transition: background 0.25s ease, box-shadow 0.25s ease;
}
.sensor-dice { font-size: 15px; font-weight: 600; color: var(--texto-suave); }

/* Con movimiento se enciende y late: es lo único de esta ficha que pide que la
   mires, y solo cuando hay motivo. */
.sensor-ficha.activo .sensor-luz {
  background: var(--primario);
  box-shadow: 0 0 10px var(--primario);
  animation: late-sensor 1.6s ease-in-out infinite;
}
.sensor-ficha.activo .sensor-dice { color: var(--texto); }
@keyframes late-sensor {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
/* Mientras no se sabe, apagada del todo: un sensor que no contesta no puede
   parecer que dice "no hay nadie". */
.sensor-ficha.sin-saber .sensor-luz { background: var(--linea, #2a2a30); box-shadow: none; }
@media (prefers-reduced-motion: reduce) {
  .sensor-ficha.activo .sensor-luz { animation: none; }
}

/* Estado de la puerta colgado del PROPIO botón (sensor de contacto enlazado):
   una pastilla bajo el control que dice Abierta/Cerrada. Se centra igual esté
   el control como columna flex o como bloque. */
/* Puerta ABIERTA: un aro PUNTEADO que GIRA alrededor del botón, para verlo de un
   vistazo sin leer (reemplaza la vieja pastilla). Va en el `.anillo` —el marco
   del botón de pulso—, que NO recorta, así el aro rodea el botón sin que el
   `overflow` de la foto lo tape. Cerrada o desconocida: sin aro, botón normal. */
.control.puerta-abierta .anillo::after {
  content: "";
  position: absolute;
  inset: 18px;                 /* justo por fuera del botón, dentro del anillo */
  border-radius: 50%;
  border: 3px dashed var(--primario);
  /* Un halo tenue para que el punteado resalte sobre cualquier fondo. */
  filter: drop-shadow(0 0 3px var(--primario));
  animation: girar-aro 8s linear infinite;
  pointer-events: none;
}
@keyframes girar-aro { to { transform: rotate(360deg); } }
/* Sin giro pero SÍ con el aro fijo: quien pide menos movimiento igual ve que
   está abierta. */
@media (prefers-reduced-motion: reduce) {
  .control.puerta-abierta .anillo::after { animation: none; }
}

/* Medidor de tanque (sensor de NIVEL de líquido): un tanque que se llena de
   agua hasta el % que reporta. Bajo del 20% el agua se pone naranja. */
.control-nivel { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.tanque {
  position: relative;
  width: 66px;
  height: 132px;
  border-radius: 18px;
  background: var(--superficie);
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  overflow: hidden;
}
.tanque-agua {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  background: linear-gradient(to top, #2b7fd4, #4aa6ff);
  transition: height 0.7s ease, background 0.3s ease;
}
.tanque.bajo .tanque-agua { background: linear-gradient(to top, #d97b2b, #f2a54a); }
.tanque.sin-saber .tanque-agua { background: var(--linea, #2a2a30); }
.tanque-cifra {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 19px;
  color: var(--texto);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.nivel-detalle { font-size: 13px; color: var(--texto-suave); }

/* Sección "Ajustes del tanque" dentro del editor (modo nivel). */
.campo-ajustes-nivel { display: flex; flex-direction: column; gap: 10px; }
.subtitulo-editor { margin: 4px 0 0; font-size: 15px; font-weight: 600; color: var(--texto); }

/* Cartel de "instalar en la pantalla de inicio" (PWA), en Perfil. */
.instalar-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  color: var(--texto-suave);
}
.instalar-banner span { flex: 1; line-height: 1.45; }
.instalar-banner b { color: var(--texto); }
.instalar-banner .btn-primario { padding: 8px 16px; font-size: 14px; white-space: nowrap; }

/* Aviso "hay versión nueva" (service worker), flotando sobre la barra de abajo. */
.sw-actualizar {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 74px);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 16px;
  background: var(--superficie);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45), inset 1px 1px 2px var(--sombra-clara);
  font-size: 14px;
  color: var(--texto);
}
.sw-actualizar span { flex: 1; }
.sw-actualizar .btn-primario { padding: 8px 16px; font-size: 14px; white-space: nowrap; }

/* ---- Cámara en vivo -------------------------------------------------------
   El marco es el hueco hundido de siempre —la misma pieza que el sensor— pero
   con proporción de vídeo. Se enseña vacío antes de conectar en vez de crecer
   de golpe cuando llega la imagen: un control que cambia de tamaño mueve todo
   lo que tiene debajo justo cuando lo estás mirando. */
/* La cámara ocupa la fila entera: no es un botón, es una imagen, y compartir
   línea con un portón la dejaría del tamaño de un sello.
   El `flex: 1 1 100%` es lo que hace que `width: 100%` tenga contra qué
   medirse. Sin él, `.control` centra a sus hijos y el ancho del marco acababa
   siendo el del texto de abajo — que es exactamente cómo quedó del tamaño de
   una estampilla al sacar el vídeo del flujo. */
.control-camara { cursor: pointer; flex: 1 1 100%; width: 100%; }
.camara-marco {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  /* El tope manda sobre el ancho disponible. A lo ancho de la pantalla la
     cámara dominaba el panel; del tamaño de un botón no se distingue una cara.
     360 la deja claramente por encima de un control sin comerse la vista. */
  min-width: 220px;
  max-width: 360px;
  transition: max-width 0.25s ease;
  margin-inline: auto;
  overflow: hidden;
  border-radius: 18px;
  /* `background-color` y NO la abreviada `background`: la abreviada reinicia
     todo lo que no nombra, así que borraba el `background-size` de abajo y la
     ilustración salía a tamaño natural — mil píxeles en un marco de 360, del
     que solo cabía el techo.
     Y el tamaño va DESPUÉS del color, para que no vuelva a pasar por orden. */
  background-color: var(--superficie);
  background-size: cover;
  background-position: center;
  box-shadow: inset 4px 4px 9px var(--sombra-oscura), inset -4px -4px 9px var(--sombra-clara);
  -webkit-tap-highlight-color: transparent;
}
/* El vídeo va ABSOLUTO, fuera del flujo. Con `width/height: 100%` a secas
   seguía contando para el tamaño del marco: al llegar el stream el navegador le
   aplicaba la proporción real de la cámara y el contenedor crecía de golpe,
   empujando todo lo de abajo justo cuando lo estabas mirando. La proporción fija
   del marco no gana contra un hijo en flujo con dimensiones propias; sacarlo del
   flujo sí. */
.camara-marco video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.camara-marco.viendo video { opacity: 1; }
/* Un velo oscuro sobre la ilustración: sin él, "Toca para ver" se pierde sobre
   la madera clara. Y de paso la baja de tono, que en un panel negro una
   ilustración a plena luz grita. */
.camara-marco::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(10, 10, 12, 0);
  transition: opacity 0.35s ease;
}
/* Con el vídeo en marcha estorba: se va. */
.camara-marco.viendo::before { opacity: 0; }

/* Agrandar = MÁS ANCHO, misma forma y mismo recorte. Es la diferencia entre
   enseñar más y estirar lo mismo: al cambiar la proporción, el hueco crecía y
   la imagen no —"lo que agranda es la ventana, no el video", lo dijo el
   usuario y era exacto—. Con 360 → 520 los mismos píxeles ocupan más pantalla,
   que es lo que se busca cuando quieres mirar de cerca quién llegó. */
.camara-marco.grande { max-width: 520px; }
.camara-crecer {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: none;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
/* Solo cuando hay algo que agrandar. */
.camara-marco.viendo .camara-crecer { display: grid; place-items: center; }
.camara-crecer svg { width: 18px; height: 18px; }

/* Un candado encima de cada puerta, donde se ve en la imagen. Solo mientras se
   mira: sin vídeo no hay a quién abrirle desde aquí.
   Sin nombre a propósito — un texto encima tapa justo lo que se quiere ver. */
.camara-puertas {
  position: absolute;
  inset: 0;
  display: none;
  pointer-events: none;      /* el marco sigue siendo el interruptor del vídeo */
}

.camara-marco.viendo .camara-puertas { display: block; }

.camara-puerta {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  display: grid;
  place-items: center;
  /* 32 px: el mínimo que sigue siendo cómodo para el dedo sin comerse la
     imagen. A 40 tapaba demasiado de lo que se quiere mirar. */
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: background 0.2s ease, transform 0.2s ease;
}

.camara-puerta svg { width: 16px; height: 16px; }
.camara-puerta:active { transform: translate(-50%, -50%) scale(0.92); }

/* Abriendo: el candado se pone verde. Es la unica confirmacion que cabe aqui
   sin tapar la imagen, y ademas se ve la puerta abrirse detras. */
.camara-puerta.abriendo {
  background: rgba(46, 160, 67, 0.85);
}

/* --- El plano de la ficha: donde se colocan los candados --- */
.plano-camara {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 12px;
  background: var(--fondo-suave, rgba(127, 127, 127, 0.18)) center / cover no-repeat;
  overflow: hidden;
  touch-action: none;        /* el arrastre es nuestro, no del scroll */
}

.plano-marca {
  position: absolute;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: grab;
  touch-action: none;
}

.plano-marca:active { cursor: grabbing; }
.plano-marca svg { width: 18px; height: 18px; }
.plano-camara-caja { display: flex; flex-direction: column; gap: 10px; }

.camara-aviso {
  position: absolute;
  inset: 0;
  /* Sin velo detrás, el texto se apoya en su propia sombra: sobre madera clara
     un gris suave desaparecería. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 0 12px rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 18px;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto-suave);
  pointer-events: none;
}
/* Mientras se ve, el aviso estorba: se va del todo. */
.camara-marco.viendo .camara-aviso { display: none; }

/* ---- Botones de refrescar: girar mientras trabajan ---------------------
   Sin esto, pulsar y no ver nada durante dos segundos se siente como que el
   botón no respondió — y se vuelve a pulsar. El giro no es adorno: es la
   respuesta al toque. Se queda deshabilitado además, así que el propio giro
   explica por qué no acepta otro clic. */
.btn-icono.girando svg { animation: girar-refresco 0.9s linear infinite; }
.btn-icono.girando { cursor: progress; }
@keyframes girar-refresco {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-icono.girando svg { animation: none; }
  /* Sin giro, al menos que se apague un poco: la señal no puede desaparecer
     solo porque alguien pidió menos movimiento. */
  .btn-icono.girando { opacity: 0.55; }
}

/* ---- Casillas agrupadas por edificio (editor del vecino) ---------------- */
.casillas-rama > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  list-style: none;
  cursor: pointer;
}
.casillas-rama > summary::-webkit-details-marker { display: none; }
.casillas-rama > summary .pase-grupo-flecha {
  flex: none;
  color: var(--texto-suave);
  transition: transform 0.18s ease;
}
.casillas-rama[open] > summary .pase-grupo-flecha { transform: rotate(90deg); }
/* Los hijos sangrados bajo la flecha, para que se lea a qué pertenecen. */
.casillas-dentro { padding-left: 22px; }


/* ── Secciones plegadas: lo que se mira poco no hace bulto ──────────────── */
.plegado > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1.17em;
  font-weight: 600;
  margin: 18px 0 8px;
}
.plegado > summary::-webkit-details-marker { display: none; }
.plegado > summary::after {
  content: "›";
  color: var(--texto-suave, #888);
  font-size: 1.1em;
  transition: transform 0.15s;
}
.plegado[open] > summary::after { transform: rotate(90deg); }


/* ViYi se comporta como app, no como página.
   Sin zoom al tocar dos veces, y sin seleccionar texto al mantener pulsado: en
   una app de abrir puertas, un menú de copiar encima del botón es un estorbo, y
   el zoom se dispara solo al pulsar rápido. Los campos donde SÍ se escribe
   quedan fuera —ahí seleccionar es lo normal—. */
body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}
input, textarea, select, [contenteditable] {
  -webkit-user-select: auto;
  user-select: auto;
  -webkit-touch-callout: default;
}

/* Aviso de movimiento de las cámaras Blink. Va abajo a la izquierda, donde no
   pelea con el botón de agrandar (que vive arriba a la derecha). Es el único
   sitio del marco que se toca por su cuenta: el resto abre el vivo. */
.camara-movimiento {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 2;
  border: 0;
  border-radius: 999px;
  padding: 6px 12px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  /* Velo propio, no del marco: tiene que leerse igual sobre una entrada
     iluminada que sobre una de noche. */
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
  transition: opacity 0.18s ease;
}
/* Sin clip todavía: informa, pero no invita a tocar. */
.camara-movimiento:disabled { cursor: default; opacity: 0.75; }
/* Mientras se está viendo algo, el chip estorba igual que el aviso. */
.camara-marco.viendo .camara-movimiento { opacity: 0; pointer-events: none; }

/* Ofrecerle al invitado entrar con la cara. Va dentro de su tarjeta de acceso
   temporal, después del billete y antes de la lista de puertas. */
.cara-pase {
  margin: 14px 0 6px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--superficie);
  /* Un borde tenue en vez de otro velo: la tarjeta del pase ya tiene fondo, y
     dos superficies apiladas sin separación se leen como una sola. */
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.cara-titulo {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto);
}
.cara-ayuda {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--texto-suave);
}
.cara-boton { width: 100%; }
.cara-estado {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--primario);
}
.cara-estado.cara-mal { color: var(--error); }
/* La promesa sobre la foto va al final y en tono bajo, pero VISIBLE: es lo que
   uno querría leer antes de dar su cara, no letra pequeña escondida. */
.cara-promesa {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--texto-suave);
}

/* Personas de la casa: los que entran por la cara y no tienen app. Cada fila
   es un nombre, su estado y qué hacer con él. */
.casa-lista {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* Dos columnas y NUNCA se parte: quién es a la izquierda, qué hacer con él a la
   derecha. Con los botones en la misma línea que el nombre —que fue lo primero—
   en un teléfono cabían tres cosas y la cuarta caía sola a la línea siguiente:
   las filas se mezclaban y no se sabía qué botón era de quién. */
.casa-lista li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
}
/* Una raya entre filas y no en la primera: es lo que las separa ahora que cada
   una ocupa dos líneas. */
.casa-lista li + li { border-top: 1px solid rgba(255, 255, 255, 0.06); }
.casa-datos {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.casa-quien {
  font-size: 14px;
  color: var(--texto);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.casa-estado {
  font-size: 12px;
  color: var(--texto-suave);
}
/* Los dos botones del mismo ancho: la columna queda recta en toda la lista
   aunque un nombre sea largo y otro corto. */
.casa-botones {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}
.casa-botones button { min-width: 62px; }
/* Que le falte la foto no es un error —acaba de darla de alta— pero sí es
   trabajo pendiente: sin cara no abre la puerta. */
.casa-estado.casa-sin-cara { color: var(--aviso); }
.casa-lista .btn-secundario {
  padding: 6px 12px;
  font-size: 13px;
}
.casa-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}
.casa-form input {
  width: 100%;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: var(--fondo);
  color: var(--texto);
  font: inherit;
  font-size: 15px;   /* menos de 16 y el iPhone hace zoom al enfocar */
}
.casa-acciones {
  display: flex;
  gap: 8px;
}
.casa-acciones button { flex: 1 1 0; }

/* Aviso del terminal de la entrada, en Actividad. Solo aparece cuando hay algo
   trabado, y NO va plegado: si una baja falló, alguien sigue entrando cuando no
   debería, y eso no puede estar escondido tras un triángulo. */
.aviso-terminal {
  margin: 0 0 16px;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--superficie);
  border: 1px solid var(--aviso);
}
/* Una baja trabada es de otro orden: no es una molestia, es alguien con acceso
   que no debería tenerlo. El color lo dice antes que el texto. */
.aviso-terminal.aviso-grave { border-color: var(--error); }
.aviso-terminal-titulo {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--aviso);
}
.aviso-terminal.aviso-grave .aviso-terminal-titulo { color: var(--error); }
.aviso-terminal-detalle {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--texto-suave);
}
.aviso-terminal-quienes {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--texto);
}

/* El botón de la foto es un LABEL con el input dentro (ver el comentario en
   app.js): en el iPhone un input escondido no abre el selector si se le llama
   desde código. Mientras sube, se apaga a la vista sin poder usar `disabled`,
   que en un label no existe. */
.cara-ocupado { opacity: 0.55; pointer-events: none; }

/* Cámara propia para la foto, a PANTALLA COMPLETA.
   Estando dentro de la tarjeta quedaba abajo, y ahí uno mira hacia abajo
   mientras la cámara —que está arriba del teléfono— le graba desde arriba: mal
   ángulo para la persona y peor para que el terminal la reconozca. Ocupando la
   pantalla, la mirada va sola hacia el lente. */
.cara-camara {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: #000;
  overflow: hidden;
}
.cara-video,
.cara-previa {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Espejo solo en la previa en vivo: verse al revés desorienta al colocarse. La
   foto tomada se muestra tal cual, que es como va a llegar al terminal. */
.cara-video { transform: scaleX(-1); }
.cara-guia {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.cara-guia svg { width: 100%; height: 100%; display: block; }
.cara-pista {
  position: absolute;
  left: 0;
  right: 0;
  /* Arriba, donde está mirando: es la instrucción de qué hacer, no un pie. */
  top: calc(env(safe-area-inset-top, 0px) + 22px);
  margin: 0;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
  pointer-events: none;
}
.cara-acciones {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 28px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 0 20px;
}
/* El disparador, redondo y grande: se toca con el pulgar y sin mirarlo, porque
   quien lo usa está mirándose a sí mismo. */
.cara-disparo {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 5px solid rgba(255, 255, 255, 0.92);
  background: var(--primario);
  cursor: pointer;
  flex: none;
}
.cara-disparo:active { transform: scale(0.94); }
.cara-cancelar {
  position: absolute;
  left: 20px;
  bottom: 26px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 15px;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
  cursor: pointer;
}
/* Aceptar o repetir. Nadie debería mandar su cara sin verla antes. */
.cara-repetir,
.cara-usar { flex: 1; max-width: 190px; }

/* Con la cámara abierta, el fondo no se mueve. */
body.sin-scroll { overflow: hidden; }

/* El camino de respaldo, si la cámara no arranca. Discreto: es la excepción. */
.cara-respaldo {
  display: block;
  margin: 10px 0 0;
  font-size: 13px;
  text-align: center;
  color: var(--texto-suave);
  text-decoration: underline;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

/* El bloque de la cara dentro de Perfil: la tarjeta ya pone el fondo, así que
   aquí sobra el suyo. Dos superficies apiladas se leen como un error. */
.cara-en-perfil {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
}

/* Salir de la cámara, siempre a la vista. Antes solo había "Cancelar" en la
   pantalla de encuadre, y quien se arrepentía después de disparar se quedaba
   sin salida. */
.cara-cerrar {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 14px);
  right: 14px;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  cursor: pointer;
  z-index: 2;
}
.cara-cerrar svg { width: 20px; height: 20px; }
/* La foto tomada se muestra completa, sin recortar: es exactamente lo que se
   va a mandar, y quien decide tiene que ver eso y no otra cosa. */
.cara-previa { object-fit: contain; background: #000; }


/* El buscador y el botón de filtros, en una fila. */
.fila-buscar {
  display: flex;
  align-items: center;
  gap: 8px;
}

.fila-buscar .buscador { flex: 1 1 auto; min-width: 0; }

/* Abierto, o con algo filtrado: en los dos casos hay que notarlo. Si no, se
   podría estar mirando una lista recortada creyendo que es toda. */
#btn-filtros-todos.activo,
#btn-filtros-todos.con-filtro { color: var(--primario); }

/* Los dos desplegables del filtro, lado a lado. Van envueltos en `.campo`
   para heredar el aspecto de ViYi —fondo de la app y sombra hacia dentro— en
   vez del blanco cuadrado de fábrica del navegador. */
.filtros-todos {
  display: flex;
  gap: 8px;
  margin: 8px 0 4px;
}

.filtros-todos .campo { flex: 1 1 0; min-width: 0; margin: 0; }
.filtros-todos select { margin-bottom: 0; font-size: 14px; padding: 10px 12px; }

/* ===== Todos los controles: una linea por boton, para USARLOS =====
   Plana y ordenada por nombre. La de Admin esta agrupada por inmueble y con el
   modo y el registro en cada fila: sirve para configurar, no para encontrar. */
.lista-controles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.lista-controles li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 4px;
}

.lista-controles li.vacio {
  justify-content: center;
  color: var(--texto-suave);
  padding: 22px 4px;
}

/* El boton ES la miniatura: si su aspecto tiene imagen se pinta con ella, y si
   no queda liso. Una sola pieza para reconocerlo y para accionarlo. */
.control-mini {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: center / cover no-repeat;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.2s ease;
}

.control-mini.liso { background-color: var(--acento, #4a7dff); }
.control-mini:active { transform: scale(0.92); }
.control-mini:disabled { cursor: default; }
.control-mini.accionando { box-shadow: 0 0 0 3px rgba(46, 160, 67, 0.85); }

/* Pin de la fila en Todos: trae el control al inicio "para pruebas". A la
   derecha; apagado es un contorno gris, encendido se tiñe del verde de la
   marca. Deshabilitado (ya lo tienes por herencia) se ve fijo, sin invitar. */
.todos-fijar {
  flex: 0 0 auto;
  margin-left: auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--texto-suave);
  cursor: pointer;
  transition: transform 0.15s ease, color 0.2s ease;
}
.todos-fijar svg { width: 20px; height: 20px; }
.todos-fijar:active { transform: scale(0.9); }
.todos-fijar.activo { color: var(--primario); }
.todos-fijar:disabled { cursor: default; opacity: 0.6; }

/* Pin en la esquina del control fijado, en el home: lo marca como puesto a
   mano y lo quita sin volver a Todos. */
.control.fijado { position: relative; }
.quitar-fijado {
  position: absolute;
  top: 0;
  right: 0;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--superficie);
  color: var(--primario);
  box-shadow: 2px 2px 5px var(--sombra-oscura), -2px -2px 5px var(--sombra-clara);
  cursor: pointer;
  z-index: 2;
}
.quitar-fijado svg { width: 15px; height: 15px; }
.quitar-fijado:active { transform: scale(0.9); }

.control-mini-texto {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.control-mini-texto strong {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.control-mini-texto em {
  font-style: normal;
  font-size: 13px;
  color: var(--texto-suave);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* El "¿Olvidaste tu clave?" cuando ES el camino, no el plan B: a un vecino
   recién dado de alta ese enlace no es una salida de emergencia, es la única
   puerta —nunca puso una clave—. Se enciende para que se lea como la acción y
   no como la letra pequeña. */
.enlace-clave.destacado {
  color: var(--primario);
  font-weight: 600;
}

/* La fila de "no han entrado nunca": es un encabezado con una acción al lado,
   así que se sale del bloque en mayúsculas de los demás grupos — se lee como
   una frase, no como una etiqueta. */
.lista-gestion li.fila-invitar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 10px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}
.lista-gestion li.fila-invitar .btn-secundario {
  flex: 0 0 auto;
  padding: 6px 12px;
  font-size: 13px;
}

/* Las dos salidas del aviso de la foto, una al lado de la otra. El "No,
   gracias" no se esconde ni se encoge: si decir que no cuesta mas que decir
   que si, no es una eleccion de verdad. */
.aviso-cara-acciones {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}
.aviso-cara-no {
  padding: 0;
  font-size: 13px;
  color: var(--texto-suave);
}
