/* ============================================================================
   MANTENCIÓN LINK ADVANCE — estilos propios de la app

   Todo lo que trae el sistema de diseño (tokens, base, shell, ios) ya está
   enlazado antes. Acá va SOLO lo que este dominio necesita y el sistema no
   tiene: el semáforo de TBO, la barra de vida del componente y las piezas de
   chasis que los snippets daban por hechas (arranque, pie del cajón).

   Este archivo va ANTES de ios.css. Regla 8: si se pone después, el chasis
   táctil deja de corregir por cascada.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   EL ATRIBUTO hidden TIENE QUE OCULTAR. No es una obviedad: shell.css le da
   `display: grid` a #login-view y app.css le da `display: grid` a .arranque, y
   cualquier regla de autor le gana a la del navegador (`[hidden]{display:none}`).
   Sin esta línea, `el.hidden = true` deja la propiedad en true y el elemento
   igual de visible. Eso dejó la pantalla de carga tapando la app entera, con
   el DOM en el estado correcto y sin ningún error en consola.
   ---------------------------------------------------------------------------- */
[hidden] { display: none !important; }

/* ----------------------------------------------------------------------------
   Arranque: se ve mientras se valida la sesión guardada. Sin esto parpadea el
   login medio segundo aunque la sesión sea válida, y se ve roto.
   ---------------------------------------------------------------------------- */
.arranque {
  position: fixed;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-4);
  background: var(--page);
  color: var(--text-muted);
  z-index: 90;
}
.arranque .sello {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .06em;
  color: var(--text-muted);
  opacity: .55;
}

.spinner {
  width: 34px;
  height: 34px;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  border-radius: var(--r-pill);
  animation: girar .8s linear infinite;
}
@keyframes girar { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

/* Pie del cajón: el botón de salir queda abajo del todo, separado de la
   navegación, y respeta la barra de gestos del teléfono. */
.side-pie {
  margin-top: auto;
  padding: var(--space-4);
  padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}

/* Botón dentro de un input (mostrar contraseña). Mantiene los 44px de blanco
   de toque aunque el ícono sea chico. */
.input-icon { position: relative; }
.input-accion {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  min-width: 44px;
  min-height: 44px;
}
.input-icon:has(.input-accion) .input { padding-right: 48px; }

.pie {
  margin-top: var(--space-6);
  font-size: .82rem;
  color: var(--text-muted);
  text-align: center;
}

/* ============================================================================
   SEMÁFORO DE TBO
   Cuatro estados más "sin TBO". El color NUNCA va solo (regla 13): cada chip
   lleva su palabra, y en las listas además va la barra de vida.
   ============================================================================ */
.chip-estado {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: .74rem;
  font-weight: 700;
  line-height: 1.5;
  white-space: nowrap;
}
.chip-estado .punto {
  width: 7px;
  height: 7px;
  border-radius: var(--r-pill);
  background: currentColor;
  flex: none;
}
/* El fondo y el texto del chip se DERIVAN del color del estado con
   color-mix, no se escriben a mano. Así el usuario elige un solo color en
   Configuración y el chip sigue legible en claro y en oscuro. Las dos líneas
   planas de arriba son el respaldo para navegadores sin color-mix. */
.est-normal    { background: var(--success-bg); color: var(--success-fg); }
.est-aviso     { background: var(--warning-bg); color: var(--warning-fg); }
.est-inminente { background: var(--alert-bg);   color: var(--alert-fg); }
.est-atrasado  { background: var(--danger-bg);  color: var(--danger-fg); }
.est-sin_tbo   { background: var(--surface-2);  color: var(--text-muted); }

.est-normal    { background: color-mix(in srgb, var(--est-normal) 16%, var(--est-fondo));
                 color: color-mix(in srgb, var(--est-normal) 78%, var(--est-tinta)); }
.est-aviso     { background: color-mix(in srgb, var(--est-aviso) 16%, var(--est-fondo));
                 color: color-mix(in srgb, var(--est-aviso) 78%, var(--est-tinta)); }
.est-inminente { background: color-mix(in srgb, var(--est-inminente) 16%, var(--est-fondo));
                 color: color-mix(in srgb, var(--est-inminente) 78%, var(--est-tinta)); }
.est-atrasado  { background: color-mix(in srgb, var(--est-atrasado) 16%, var(--est-fondo));
                 color: color-mix(in srgb, var(--est-atrasado) 78%, var(--est-tinta)); }
.est-sin_tbo   { background: var(--surface-2);
                 color: color-mix(in srgb, var(--est-sin_tbo) 70%, var(--est-tinta)); }

/* Barra de vida: cuánto lleva usado el componente contra su TBO. Se llena de
   izquierda a derecha y cambia de color en los mismos cortes que el chip. */
.vida {
  --pct: 0;
  position: relative;
  height: 6px;
  min-width: 64px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
}
.vida::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: min(100%, calc(var(--pct) * 1%));
  border-radius: var(--r-pill);
  background: var(--ink-400);
}
.vida.est-normal::before    { background: var(--est-normal); }
.vida.est-aviso::before     { background: var(--est-aviso); }
.vida.est-inminente::before { background: var(--est-inminente); }
.vida.est-atrasado::before  { background: var(--est-atrasado); }
.vida.est-sin_tbo::before   { background: var(--est-sin_tbo); }

/* Celda de uso: porcentaje + barra, apilados. En teléfono la tabla se vuelve
   tarjeta y esta celda es la que more importa, así que ocupa el ancho. */
.celda-uso { display: grid; gap: 5px; min-width: 108px; }
.celda-uso .cifra {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: .9rem;
}

/* ============================================================================
   TARJETAS DE RESUMEN
   ============================================================================ */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.stat.k-normal    { border-left-color: var(--est-normal); }
.stat.k-aviso     { border-left-color: var(--est-aviso); }
.stat.k-inminente { border-left-color: var(--est-inminente); }
.stat.k-atrasado  { border-left-color: var(--est-atrasado); }

/* El KPI es accionable: lleva a la lista ya filtrada por ese estado. */
button.stat {
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  border: 1px solid var(--border);
  border-left-width: 4px;
}
@media (hover: hover) {
  button.stat:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
}
button.stat:active { transform: scale(.985); }

/* Anillo de cumplimiento: una sola cifra grande, que es la que se lleva la
   jefatura a la reunión. */
.medidor {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.anillo {
  --pct: 0;
  --color: var(--success);
  width: 116px;
  height: 116px;
  flex: none;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: conic-gradient(var(--color) calc(var(--pct) * 1%), var(--surface-2) 0);
}
.anillo > span {
  width: 88px;
  height: 88px;
  border-radius: var(--r-pill);
  background: var(--surface);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   LISTAS Y TABLAS DEL DOMINIO
   ============================================================================ */
.filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-end;
  margin-bottom: var(--space-4);
}
.filtros .field { margin: 0; min-width: 150px; flex: 1 1 150px; }
.filtros .field.ancho { flex: 2 1 240px; }

/* Bajo 640px los filtros se apilan enteros: en dos columnas los selects
   quedan de 120px y no se lee el nombre de ningún equipo. */
@media (max-width: 639px) {
  .filtros { display: grid; grid-template-columns: 1fr; }
  .filtros .field { min-width: 0; }
}

.tabla-comp td.td-comp { min-width: 200px; }
.tabla-comp .sub {
  display: block;
  font-size: .78rem;
  color: var(--text-muted);
  margin-top: 2px;
}
.num-col {
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
/* En tarjeta (teléfono) el número alineado a la derecha queda pegado a la
   etiqueta y se lee mal; ios.css ya reordena, acá solo se suelta el alineado. */
@media (max-width: 767px) {
  .num-col { text-align: left; }
}

/* Fila del equipo en la lista: código grande, barra de estados debajo. */
.barra-estados {
  display: flex;
  height: 8px;
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--surface-2);
  min-width: 120px;
}
.barra-estados > i { display: block; height: 100%; }
.barra-estados .s-normal    { background: var(--est-normal); }
.barra-estados .s-aviso     { background: var(--est-aviso); }
.barra-estados .s-inminente { background: var(--est-inminente); }
.barra-estados .s-atrasado  { background: var(--est-atrasado); }
.barra-estados .s-sin_tbo   { background: var(--est-sin_tbo); }

/* Carga masiva de horómetros: una fila por equipo, el input grande y a la
   derecha. Es la pantalla que más se usa y se llena de pie en terreno. */
/* ----------------------------------------------------------------------------
   CARGA DE HORÓMETROS

   El horómetro es un reloj: no retrocede y no puede haber avanzado más horas
   que las que trae el calendario. Las dos reglas se revisan al teclear y la
   fila entera lo dice —contorno verde o rojo, más el motivo en palabras—
   porque en faena esto se llena de pie y con guantes: descubrir el error
   recién al apretar Guardar significa volver a escribir las once lecturas.
   ---------------------------------------------------------------------------- */
.horom-cab { display: flex; align-items: flex-end; gap: var(--space-4); flex-wrap: wrap; }
.horom-cab .spacer { flex: 1; }
.horom-resumen {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .84rem; color: var(--text-muted);
  padding-bottom: 10px;
}
.horom-resumen .ico { width: 16px; height: 16px; }
.horom-resumen.bien { color: var(--success-fg); }
.horom-resumen.mal  { color: var(--danger-fg); }

.tabla-horom .celda-entrada { min-width: 200px; }
.tabla-horom .entrada-horom {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  max-width: 180px;
  margin-left: auto;
}
.tabla-horom .entrada-horom.bien {
  border-color: var(--success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 18%, transparent);
}
.tabla-horom .entrada-horom.mal {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 18%, transparent);
}
.pista-horom {
  display: block;
  margin-top: 5px;
  font-size: .75rem;
  line-height: 1.35;
  text-align: right;
  color: var(--text-muted);
}
.pista-horom .ico { width: 13px; height: 13px; vertical-align: -2px; }
.pista-horom.bien { color: var(--success-fg); }
.pista-horom.mal  { color: var(--danger-fg); font-weight: 600; }
.tabla-horom tr.es-mala { background: var(--danger-bg); }
.tabla-horom .sub { display: block; font-size: .74rem; color: var(--text-muted); font-weight: 400; }

@media (max-width: 639px) {
  /* Ya convertida en tarjeta: el campo y su mensaje ocupan el ancho. */
  .tabla-horom td.celda-entrada { display: block; }
  .tabla-horom td.celda-entrada::before { display: block; margin-bottom: 6px; }
  .tabla-horom .entrada-horom { max-width: none; width: 100%; }
  .pista-horom { text-align: left; }
}

/* Línea de tiempo del historial de una posición. */
.linea { list-style: none; margin: 0; padding: 0; }
.linea > li {
  position: relative;
  padding: 0 0 var(--space-5) var(--space-6);
  border-left: 2px solid var(--border);
}
.linea > li:last-child { border-left-color: transparent; padding-bottom: 0; }
.linea > li::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 4px;
  width: 10px;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--border-strong);
  border: 2px solid var(--surface);
}
.linea > li.vigente::before { background: var(--primary); }
.linea .cabeza {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  flex-wrap: wrap;
}
.linea .fecha {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--text-strong);
}
.linea .detalle {
  margin-top: 4px;
  font-size: .84rem;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--space-4);
}

/* Barras de un ranking simple (cambios por mes, clasificaciones críticas).
   No amerita una librería de gráficos: son barras y una cifra. */
.barras { display: grid; gap: var(--space-3); }
.barras .b {
  display: grid;
  grid-template-columns: minmax(96px, 34%) 1fr auto;
  gap: var(--space-3);
  align-items: center;
  font-size: .86rem;
}
.barras .pista {
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
}
.barras .pista > span {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--primary);
}
.barras .valor { font-variant-numeric: tabular-nums; color: var(--text-muted); }
.barras .b.crit .pista > span { background: var(--danger); }

/* ----------------------------------------------------------------------------
   GRÁFICO DE BARRAS POR MES

   La versión anterior ponía height:<n>% en la barra, dentro de un grid con
   align-content:end. El porcentaje se resuelve contra la altura de la FILA,
   que ahí es automática, o sea cero: el gráfico salía plano aunque el pie
   dijera "el más alto es 95 cambios". Ahora la altura la calcula el JS en
   píxeles y este CSS solo reserva el hueco (--g-alto). Por eso .g-hueco
   existe: es la caja de altura fija contra la que se apoya la barra.
   ---------------------------------------------------------------------------- */
.grafico { overflow-x: auto; padding-bottom: var(--space-2); }
.g-cols { display: flex; align-items: flex-end; gap: 6px; min-width: 100%; }
.g-c {
  flex: 1 0 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.g-hueco { display: flex; align-items: flex-end; height: var(--g-alto, 132px); width: 100%; }
.g-b {
  width: 100%;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: var(--primary);
  opacity: .85;
  transition: opacity .15s ease;
}
@media (hover: hover) { .g-c:hover .g-b { opacity: 1; } }
.g-n {
  font-size: .68rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  min-height: 1em;
}
.g-x { font-size: .66rem; color: var(--text-muted); white-space: nowrap; }
/* El mes en curso se marca para no tener que contar columnas. */
.g-c.destacada .g-b { background: var(--alert); opacity: 1; }
.g-c.destacada .g-x { color: var(--alert-fg); font-weight: 700; }
.g-c.destacada .g-n { color: var(--text-strong); font-weight: 700; }

/* Cifras de cabecera del gráfico de proyección. */
.proy-cifras {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-4);
}
.proy-cifra { display: grid; gap: 2px; }
.proy-cifra .v {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.proy-cifra .r { font-size: .76rem; color: var(--text-muted); }
.proy-cifra.es-mala .v { color: var(--est-atrasado); }

/* Aviso de datos vencidos: el % de uso miente si el horómetro es viejo. */
.aviso-lectura {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border-radius: var(--r-lg);
  background: var(--warning-bg);
  color: var(--warning-fg);
  margin-bottom: var(--space-5);
}
.aviso-lectura .ico { flex: none; margin-top: 2px; }

/* Cabecera de sección con acción a la derecha, que se apila en teléfono. */
.seccion {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: var(--space-8) 0 var(--space-4);
}
.seccion h2 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  margin: 0;
  color: var(--text-strong);
}
.seccion .spacer { flex: 1; }

/* Paginador del historial. */
.paginador {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: center;
  margin-top: var(--space-5);
  color: var(--text-muted);
  font-size: .86rem;
}

/* Ficha de dos columnas (detalle de equipo y de componente). */
.ficha {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-4);
}
.ficha .dato .rot {
  display: block;
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted);
  margin-bottom: 2px;
}
.ficha .dato .val {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   LISTA AGRUPADA — TAMBIÉN EN ESCRITORIO

   .list-group / .list-row / .list-title existían SOLO dentro del bloque
   (hover: none) and (pointer: coarse) de ios.css. En un notebook no había
   ninguna regla que las tocara, así que "Mi cuenta" se veía como una pila de
   textos sueltos con el visto del tema colgando debajo de la palabra "Claro".
   Estas son las reglas base; ios.css las sigue afinando para el dedo.
   ============================================================================ */
.list-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-bottom: var(--space-5);
}
.list-group > .list-row + .list-row { border-top: 1px solid var(--border); }
.list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 52px;
  padding: 12px var(--space-5);
  font-size: .94rem;
}
.list-row .spacer { flex: 1; }
.list-row > .label { flex: none; font-weight: 600; color: var(--text-strong); }
.list-row > .value {
  flex: 1;
  text-align: right;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.list-row.is-link { cursor: pointer; }
.list-row.is-link::after {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  border-right: 2px solid var(--text-muted);
  border-top: 2px solid var(--text-muted);
  transform: rotate(45deg);
  opacity: .55;
}
@media (hover: hover) { .list-row.is-link:hover { background: var(--surface-2); } }
.list-row.es-peligro > .label { color: var(--danger-fg); }
.list-title {
  padding: var(--space-5) var(--space-1) var(--space-2);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ============================================================================
   MI CUENTA
   ============================================================================ */
.cuenta-cabeza {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}
.cuenta-avatar {
  display: grid;
  place-items: center;
  width: 54px; height: 54px;
  flex: none;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.15rem;
  letter-spacing: .02em;
}
.cuenta-quien { display: grid; gap: 2px; min-width: 0; }
.cuenta-quien h3 { margin: 0; font-size: 1.15rem; }
.cuenta-quien .sub { color: var(--text-muted); font-size: .86rem; }

/* Selector de tema en línea: la misma pastilla segmentada del sistema de
   diseño, con el valor actual marcado. Vive acá y no en la barra superior:
   arriba el botón cambia de tema de una, sin menú. */
.tema-seg { display: inline-flex; gap: 2px; padding: 3px; background: var(--surface-2); border-radius: var(--r-pill); }
.tema-seg button {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border: 0; background: none; cursor: pointer;
  border-radius: var(--r-pill);
  font-size: .82rem; font-weight: 600;
  color: var(--text-muted);
}
.tema-seg button .ico { width: 15px; height: 15px; }
.tema-seg button.active { background: var(--surface); color: var(--text-strong); box-shadow: var(--shadow-xs); }
@media (max-width: 520px) { .tema-seg button span { display: none; } }

/* ============================================================================
   SEMÁFORO CONFIGURABLE
   ============================================================================ */
.semaforo-lista { display: grid; gap: var(--space-3); }
.semaforo-fila {
  display: grid;
  grid-template-columns: 26px 1fr 150px 96px;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.semaforo-fila .muestra {
  width: 26px; height: 26px;
  border-radius: var(--r-pill);
  border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border);
}
.semaforo-fila .quien { min-width: 0; }
.semaforo-fila .quien strong { display: block; color: var(--text-strong); }
.semaforo-fila .quien span { font-size: .8rem; color: var(--text-muted); }
.semaforo-fila .field { margin: 0; }
.semaforo-fila input[type="color"] {
  width: 100%; height: 38px; padding: 2px;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface); cursor: pointer;
}
@media (max-width: 720px) {
  .semaforo-fila { grid-template-columns: 26px 1fr; }
  .semaforo-fila .field, .semaforo-fila .col-color { grid-column: 2; }
}
/* Vista previa: los chips reales, con los colores que se están eligiendo. */
.semaforo-previa { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* ============================================================================
   FILTROS — la fila de acciones no se estira como un campo más.
   ============================================================================ */
.filtros .acciones-filtro { flex: 0 0 auto; justify-content: flex-end; }
.filtros .acciones-filtro .row { flex-wrap: nowrap; }
@media (max-width: 639px) {
  .filtros .acciones-filtro .row { flex-wrap: wrap; }
  .filtros .acciones-filtro .btn { flex: 1 1 auto; }
}

/* ============================================================================
   INSTALAR LA APP

   Chrome y Edge (escritorio y Android) avisan con beforeinstallprompt que la
   app se puede instalar; iOS no lo implementa y hay que explicar el gesto de
   "Compartir → Agregar a inicio". El botón es flotante y descartable: si el
   usuario lo cierra, no vuelve a aparecer por 30 días.
   ============================================================================ */
.instalar-app {
  position: fixed;
  z-index: 70;
  right: max(var(--space-5), env(safe-area-inset-right));
  bottom: calc(var(--space-5) + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 5px 5px 5px 6px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  animation: la-subir .28s ease both;
}
/* La barra de pestañas solo existe bajo 900px (ver ios.css): el botón se
   levanta únicamente ahí, no en el notebook ancho donde no hay barra. */
@media (max-width: 899px) {
  body.has-tabbar .instalar-app { bottom: calc(var(--tabbar-h) + var(--space-4) + env(safe-area-inset-bottom)); }
}
.instalar-app .btn { border-radius: var(--r-pill); }
.instalar-app .cerrar {
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border: 0; background: none; cursor: pointer;
  border-radius: var(--r-pill);
  color: var(--text-muted);
}
@media (hover: hover) { .instalar-app .cerrar:hover { background: var(--surface-2); color: var(--text-strong); } }
@keyframes la-subir { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

@media (max-width: 520px) {
  .instalar-app { left: max(var(--space-4), env(safe-area-inset-left)); right: max(var(--space-4), env(safe-area-inset-right)); }
  .instalar-app .btn { flex: 1; justify-content: center; }
}
