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

:root {
  /* Single source of truth for the gap between the fixed top bar and the
     content block, shared by every screen (.section, .card, .screen-title)
     so they can never drift apart from each other again. */
  --screen-gap: 1rem;
  --screen-gap-mobile: -1rem;

  /* El redondeo de los bloques, en DOS familias, y la diferencia no es
     estética: **el mismo radio se lee muy distinto según el alto de la caja.**

     `--section-radius` es el bloque grande que llena una pantalla interior
     (`.section`). Ahí 30px se leen como una esquina generosa.

     `--block-radius` es lo demás: tarjetas, filas y diálogos (`.card`,
     `.device-card`, `.mg-device`, `.rb-modal`). Se quedan en 20 porque
     `.mg-device` es una fila baja y con 30 dejaría de parecer una fila con las
     esquinas redondeadas para parecer una píldora. Los cuatro comparten valor
     por construcción y no por coincidencia, que es el motivo de la variable —
     el mismo que el del hueco de arriba.

     **No entra lo que no es un bloque.** Los controles (`button`, `input`,
     `.msg`) y las cajas de dentro de un bloque (`.mg-info-note`, `.grant-key`,
     `.nav-menu`) llevan 6-8px a propósito: son más pequeñas, y con el radio del
     contenedor se comerían su propio contenido. Y `.badge`, `.transmit-dot` y `.mg-info-btn` no son esquinas
     redondeadas sino formas —píldora y círculo—, así que tampoco. */
  --section-radius: 30px;
  --block-radius: 20px;

  /* El diámetro del punto que marca el modo que emite (`.transmit-dot`), en las
     tres listas y en la barra de la tarjeta. Está aquí y no dentro de su regla
     porque **la barra de la tarjeta lo necesita para alinear**: el rótulo con el
     nombre del modo se sangra el ancho del punto más su separación, para que
     empiece en la misma columna que el contador (§12.7). Escrito a mano en los
     dos sitios, cambiar el punto descuadraría esa columna sin que nada avisara. */
  --dot-size: 10px;
}

/* html/body are pinned and never scroll themselves - Safari's page-level
   rubber-band bounce is handled by a native component that ignores
   overscroll-behavior on the document; it DOES respect it on a real
   scrolling element, so #app below is the one that actually scrolls. */
html, body {
  height: 100%;
  overflow: hidden;
}

body {
  position: fixed;
  inset: 0;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: #f0f2f5;
  color: #1a1a1a;
}

#app {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior-y: none;
  -webkit-overflow-scrolling: touch;
}


/* ── Pull-to-refresh indicator ───────────────────────────────────────────────── */

#ptr-indicator {
  position: fixed;
  top: 0; left: 0; right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 3rem;
  background: #f0f2f5;
  transform: translateY(-100%);
  transition: transform 0.15s ease;
  z-index: 1000;
  color: #888;
  font-size: 1.25rem;
}
#ptr-indicator.ptr-visible  { transform: translateY(0); }
#ptr-indicator.ptr-ready    { color: #555; }

/* ── Logo corner ─────────────────────────────────────────────────────────────── */

.logo-corner {
  position: fixed;
  top: 0.75rem;
  left: max(1.25rem, calc(50% - 480px + 1.25rem));
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.site-name {
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #111;
  line-height: 1;
}

.logo {
  height: 32px;
  width: 32px;
  object-fit: contain;
  display: block;
  opacity: 0.85;
}

/* ── Main layout ─────────────────────────────────────────────────────────────── */

main {
  max-width: 960px;
  margin: 0 auto;
  padding: 3.5rem 1.25rem 2rem;
}

main h2 {
  font-size: 1.35rem;
  margin-bottom: 1.5rem;
  color: #111;
}

/* ── Card (login / register / link screens) ─────────────────────────────────── */

.card {
  max-width: 420px;
  margin: var(--screen-gap) auto;
  background: #fff;
  border-radius: var(--block-radius);
  padding: 2rem 2.25rem;
  box-shadow: 0 2px 16px rgba(0, 0, 0, .1);
}

.card h1 {
  font-size: 1.5rem;
  margin-bottom: 1.5rem;
}

.card h2 {
  font-size: 1.25rem;
  margin-bottom: 0.75rem;
}

.card p {
  margin-bottom: 1rem;
  font-size: 0.93rem;
  color: #555;
  line-height: 1.5;
}

/* Long addresses must not push the card wider than its 420px */
.link-account strong {
  word-break: break-all;
}

/* ── Forms ───────────────────────────────────────────────────────────────────── */

form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.88rem;
  color: #555;
  font-weight: 500;
}

input, select {
  padding: 0.6rem 0.8rem;
  border: 1px solid #d0d5dd;
  border-radius: 7px;
  font-size: 0.97rem;
  color: #1a1a1a;
  background: #fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}

input:focus, select:focus {
  outline: none;
  border-color: #4a7fe5;
  box-shadow: 0 0 0 3px rgba(74, 127, 229, .18);
}

input.input-code {
  text-transform: uppercase;
  letter-spacing: 0.25em;
  font-size: 1.35rem;
  text-align: center;
  font-family: monospace;
}

/* Checkbox rows (mode groups screen) y radios del selector de modo (pantalla
   principal): override the generic column-stacked label (text-above-input) with
   a row layout, and un-style the control itself — the generic input/select rule
   (padding/border) is meant for text inputs and selects, not checkboxes.
   Comparten regla a propósito: son la misma fila "control + nombre de modo" en
   dos pantallas, y separarlas las dejaría divergir. */
.mode-groups-row,
.mode-radio-row {
  flex-direction: row;
  align-items: center;
  gap: 0.6rem;
  font-weight: 400;
  color: #1a1a1a;
  padding: 0.4rem 0;
}

.mode-groups-row input[type="checkbox"],
.mode-radio-row input[type="radio"] {
  width: 1.1rem;
  height: 1.1rem;
  padding: 0;
  border: none;
  flex: none;
}

/* Emitiendo, los nombres se apagan con los radios. Un radio deshabilitado a
   secas se distingue poco del habilitado, y la lista tiene que leerse como lo
   que es: algo sobre lo que ahora mismo no se puede actuar — para cambiar de
   modo hay que parar primero. */
.mode-radio-row input:disabled + span { color: #9aa3af; }

/* El nombre del modo, a lo ancho de la fila. Sin `min-width: 0` un hijo de flex
   no baja de su ancho de contenido, y un nombre largo desbordaría la caja en
   vez de repartirse en dos líneas dentro de ella. */
.mode-radio-row span { min-width: 0; }

/* Enumerador circunstancial de la lista de checkboxes: la posición en el grupo
   que se está mirando y nada más. Va deliberadamente pequeño y gris para que se
   lea como una referencia y no como parte del nombre ni como un identificador.
   `inline-block` con ancho mínimo para tres cifras y alineado a la derecha, que
   es lo que mantiene los nombres a plomo al pasar de una cifra a dos o a tres.
   El margen negativo se come casi todo el `gap` de la fila: como la caja es
   fija y el número se pega a su borde derecho, sin él un enumerador de una
   cifra quedaba despegado del checkbox. Nunca recorta — `min-width` deja que la
   caja crezca si algún día hiciera falta una cuarta cifra. */
.mg-row-num {
  display: inline-block;
  min-width: 1.9em;
  margin-left: -0.4rem;
  margin-right: 0.25rem;
  text-align: right;
  font-size: 0.7rem;
  color: #9aa3af;
  font-variant-numeric: tabular-nums;
}

/* Punto del modo que el aparato está emitiendo, en la lista de checkboxes:
   avisa de que desmarcarlo lo deja en neutro. Reutiliza .transmit-dot y solo
   añade la separación y el color fijo — aquí no hay badge que lo acompañe, así
   que no puede heredar el color del contenedor.

   **Es azul desde el 2026-08-19 y fue verde hasta entonces**, y el azul no es un
   color cualquiera: es el mismo `#4a7fe5` del halo que late en el círculo de la
   Pantalla principal mientras el aparato emite. Las dos piezas dicen lo mismo,
   así que ahora lo dicen del mismo color — y las dos pasan juntas a naranja en
   la fase final. El badge de status se queda en verde: es otra familia, la de
   los estados (verde emite, gris parado, naranja atiende, rojo avería). */
.mg-active-dot {
  margin-left: 0.4rem;
  background: #4a7fe5;
  flex: none;
  /* El color y el pulso viven aquí y no en `.transmit-dot`, que solo pone la
     forma: son las tres listas de modos las que marcan el que emite —el
     selector de la Principal y las dos vistas de la gestión— y ninguna tiene un
     badge que haga de padre. Se repite la animación, no el keyframe, que es
     global. */
  animation: transmit-pulse 1.5s ease-in-out infinite;
}

/* Nota informativa de un modo (§12.3): el icono en la fila y el texto debajo.
   Solo aparecen en los modos cuyo descriptor trae `info` (§3).

   El icono va **al extremo derecho** con `margin-left: auto`, no pegado al
   nombre: así todos caen en la misma columna sea cual sea el largo del nombre,
   que es lo que permite recorrer la lista buscando cuáles tienen nota. El punto
   verde del modo activo se queda junto al nombre, delante de él.

   Tamaño: 1.6rem son unos 26px de zona de toque. Es más de lo que pide el
   círculo y menos de lo que recomienda Apple, y el compromiso es deliberado —
   en una lista densa un botón de 44px se comería la fila, pero fallar el toque
   aquí no es inofensivo: el fallo cae sobre el `<label>` y marca el checkbox. */
.mg-info-btn {
  flex: none;
  margin-left: auto;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border-radius: 50%;
  background: #fff;
  color: #4a7fe5;
  border: 1px solid #4a7fe5;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 0.85rem;
  font-style: italic;
  font-weight: 700;
  line-height: 1;
}
/* Más específica que la regla global `button:hover` (clase frente a etiqueta),
   o el icono se pintaría azul relleno al pasar por encima. */
.mg-info-btn:hover:not(:disabled) { background: #eaf0fc; }
.mg-info-btn--open {
  background: #4a7fe5;
  color: #fff;
}
.mg-info-btn--open:hover:not(:disabled) { background: #3a6fd5; }

/* Sangrada hasta donde empieza el nombre y no hasta el checkbox: la nota es del
   modo, así que cuelga de él y no de la fila entera.
   `pre-line` respeta los saltos de línea del JSON, que son el único formato que
   una nota puede traer — todo pasa por esc() y no hay HTML que valga. */
.mg-info-note {
  margin: 0 0 0.7rem 2.3rem;
  padding: 0.55rem 0.7rem;
  background: #f5f7fb;
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.45;
  color: #444;
  white-space: pre-line;
}

/* ── Gestión de modos: lista de dispositivos ─────────────────────────────────
   Deliberadamente distinta del selector de "Mis dispositivos": sin badges, sin
   status y sin modo. De un aparato gestionado no tenemos derecho a enseñar esos
   datos, así que si la lista los llevara habría filas con y filas sin. */

.mg-device-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;          /* algo más de aire que una lista normal, sin pasarse */
  /* Sin margen arriba: el hueco bajo el título ya lo ponen el margin-bottom del
     h2 y el padding de .section, y este 1rem extra hacía que esta pantalla
     respirara más que la selectora de "Mis dispositivos". */
  margin: 0 0 1.5rem;
}

/* Cada dispositivo es un bloque pulsable con borde propio, y no una fila suelta:
   dentro de una .section blanca una fila sin marco no se lee como algo que se
   pueda tocar, y esta pantalla existe justamente para elegir uno. El chevron
   dice hacia dónde lleva; es decorativo (aria-hidden) porque el role="button"
   del bloque ya cuenta lo que hace. */
/* Mismo fondo, borde y sombra que .device-card: desde que las dos conviven en
   la misma lista (§12.1), un tono distinto se leía como "esto es otra cosa" en
   lugar de como "esto es de otro". Lo que distingue a los ajenos es su
   encabezado y el no llevar badge, no el color. Cambia solo la forma —fila baja
   en vez de tarjeta alta—, porque tienen menos que contar. */
.mg-device {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  cursor: pointer;
  background: #fff;
  border: 1px solid #eee;
  border-radius: var(--block-radius);
  /* Horizontal igual que .device-card (1.5rem), para que el short id de una
     fila ajena arranque en la misma vertical que el de una tarjeta propia: en
     una lista donde se alternan, cualquier diferencia se lee como desalineado.
     El vertical sí es menor, que es lo que hace que la fila sea fila. */
  padding: 0.85rem 1.5rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .08);
  transition: box-shadow 0.2s, transform 0.15s;
}
.mg-device:hover {
  box-shadow: 0 4px 18px rgba(0, 0, 0, .13);
  transform: translateY(-2px);
}
.mg-device:focus { outline: 2px solid #4a7fe5; outline-offset: 2px; }
/* `min-width: 0` es lo que permite que el email de dentro se corte con puntos
   suspensivos: sin él, un hijo de flex no baja de su ancho de contenido. */
.mg-device-info { flex: 1; min-width: 0; }
.mg-device .device-uuid-full { margin-bottom: 0.15rem; }
.mg-device-chevron {
  flex: none;
  color: #8a97ab;
  font-size: 1.5rem;
  line-height: 1;
}

/* El email del propietario a lo ancho y en una sola línea: una dirección larga
   no debe partir la fila ni empujar el ancho de la tarjeta. */
.mg-device-tag {
  font-size: 0.85rem;
  color: #666;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* Grupos de modos que la cuenta puede gestionar, en la pantalla de selección.
   Lista simple sin viñeta —son nombres, no pasos ni opciones que elegir— e
   indentada bajo su etiqueta, que es lo que la hace leerse como lo que cuelga
   de ella y no como otro bloque más de la pantalla. */
/* Pantalla de Cuenta. El nombre es el dato de cabecera y el email va debajo con
   debajo en negro normal, no en gris: es un dato de la cuenta, no una etiqueta
   secundaria de otra cosa. */
.account-name {
  font-size: 1.05rem;
  font-weight: 600;
  color: #222;
  margin-bottom: 0.15rem;
}
.account-email {
  font-size: 0.95rem;
  color: #222;
}

/* ── El tipo de cuenta (§12.8) ─────────────────────────────────────────────────

   Básica · Avanzada · Profesional, bajo el correo y **en este sitio y en ningún
   otro**. Repetirlo en las cabeceras es exactamente lo que hacía la estrella (★)
   que se retiró al construir esta pantalla: decía que eres gestor sin decir
   dónde mirar para entenderlo.

   **Una sola idea gráfica en tres intensidades: la línea que rodea la etiqueta.**
   Ninguna, el subrayado, o el recuadro entero. Se explica en una frase —cuantas
   más líneas, más alto el escalón— y por eso los tres se leen como una familia
   en vez de como tres decisiones sueltas.

   **Y por qué NO es una rampa de grises, que era lo primero que se me ocurrió:
   el usuario nunca ve las tres juntas, solo ve la suya.** Claro-medio-oscuro
   comunica el escalón a quien las compara —o sea a nosotros mirando una tabla— y
   no le dice nada a quien abre su propia pantalla. Un subrayado o un recuadro sí
   se perciben sin término de comparación.

   **Ni un color nuevo, y ninguno de los que ya significan algo**: verde emite,
   gris parado, naranja atiende, rojo avería, azul emitiendo. Aquí solo hay grises
   y negro, y el marco va en `currentColor`, así que la línea y la letra no pueden
   separarse nunca.

   **Radio de 6px y no de 20**: el de 20 es la píldora de `.badge`, o sea la forma
   de la familia de estados, y tomar prestada la forma es tomar prestado el
   significado. Los 6px son la convención que `:root` documenta para las cajas de
   dentro de un bloque. No se confunde con un botón porque en esta app todos son
   azules y macizos: no hay ninguno con contorno y fondo transparente.

   Las versales las pone el CSS y no el fichero de idioma, que guarda "Cuenta
   básica" en su caja normal. Es la misma regla que `Device::printedShortId()`:
   el valor es una cosa y su forma impresa es otra. */
.account-type {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8a94a3;
}

.account-type--advanced {
  color: #4a5560;
  border-bottom: 1px solid currentColor;
  padding-bottom: 0.15rem;
}

.account-type--professional {
  color: #1a1a1a;
  border: 1px solid currentColor;
  border-radius: 6px;
  padding: 0.25rem 0.5rem;
}

/* La lista de grupos gestionables de la pantalla de Cuenta, con el enumerador
   de §12.3. **Sin sangría desde que lo lleva**: el número hace de marca a la
   izquierda, así que la sangría de 1rem que la separaba del rótulo lo dejaba
   flotando lejos de él. Ahora las cifras caen a plomo bajo el rótulo y los
   nombres arrancan todos a la misma altura, que es justo lo que compra la caja
   fija del enumerador.

   Y se le quita a éste el margen negativo, que allí existe para comerse el
   `gap` de una fila flex con checkbox delante — aquí no hay ni gap ni
   checkbox. */
.mg-group-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding-left: 0;
}
.mg-group-list li { font-size: 0.95rem; }
.mg-group-list .mg-row-num { margin-left: 0; }

/* La misma lista, usada como índice de navegación en la gestión de un aparato
   (§12.3). Lo único que cambia es el hueco: allí la lista va detrás de la
   etiqueta del propietario y no de un rótulo, y sin esto la primera fila queda
   pegada a ella. */
.mg-group-list--nav { margin-top: 1.25rem; }


/* Bloque del borrado de cuenta, al final de la pantalla de selección. Va en su
   propia .section y con un texto delante haciendo de separador: lo irreversible
   no debe quedar pegado a "Incluir dispositivo ajeno", que es la acción de
   todos los días y se pulsa con el mismo pulgar. */
.section-danger {
  border-color: #f0d5d5;
}
.danger-intro {
  font-size: 0.88rem;
  color: #666;
  margin: 0 0 1rem;
}

/* ── Vista de la tarjeta a pantalla completa (§12.7) ─────────────────────────── */

/* Ocupa el viewport entero y tapa todo lo demás: sin barra, sin menú y sin logo,
   porque la tarjeta ES la pantalla. Fondo negro para que las bandas que sobren
   no compitan con ella. */
.card-view {
  position: fixed;
  inset: 0;
  background: #000;
  overflow: hidden;
}

/* El hueco de la tarjeta: todo el viewport MENOS la barra de control de abajo.
   Existe para que `fitCardStage()` mida una sola caja y no tenga que restar
   nada, y para que el `top: 50%` del escenario centre en lo que queda en vez de
   en la pantalla entera — con la resta hecha a mano en JS, cambiar el alto de la
   barra en el CSS habría dejado las dos mitades discrepando. */
.card-area {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--card-bar-h);
}

/* El escenario girado. Sus medidas se CRUZAN a propósito —ancho = alto del
   viewport— porque después se gira 90°: así, tras el giro, ocupa exactamente la
   pantalla. Y dentro de él todo se posiciona en porcentaje como si la tarjeta
   estuviera derecha, que es lo que permite colocar el pie con las coordenadas
   que manda la API sin hacer ninguna cuenta con la rotación. */
.card-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Valor de partida: lo definitivo lo pone fitCardStage() midiendo la caja
     real, porque en iOS 100vh no es el área visible sino la que habría con las
     barras escondidas, y el eje largo de la tarjeta se salía. */
  width: 100vh;
  height: 100vw;
  transform: translate(-50%, -50%) rotate(90deg);
  transform-origin: center center;
  /* El giro definitivo también lo pone fitCardStage(), junto a las medidas: las
     dos dependen de si el aparato está en vertical, y en sitios distintos
     acabarían discrepando. */
}

/* El escenario ya tiene la proporción exacta de la tarjeta, así que la imagen lo
   llena sin bandas. `contain` se queda como red por si la proporción que manda
   la API no cuadrara con la de la imagen guardada. */
.card-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* El pie, superpuesto. `position: absolute` con los porcentajes que llegan de la
   API, y el tamaño en porcentaje del ancho del escenario — que tras el giro es
   el alto de la pantalla, así que escala con ella. */
.card-footer {
  position: absolute;
  color: #000;
  /* `pre` y no `nowrap`: el texto llega con los mismos ocho espacios que usa el
     PDF para separar el identificador del aparato del usuario, y `nowrap` los
     colapsaría en uno —HTML colapsa los espacios consecutivos— dejando el pie
     con distinta separación en pantalla que impreso. `pre` los conserva y
     tampoco parte la línea. */
  white-space: pre;
  font-family: Helvetica, Arial, sans-serif;
  line-height: 1;
}

/* **Los dos de las esquinas son BOTONES aquí y enlaces en el resto de la app**,
   y la excepción tiene motivo: sobre el negro de esta pantalla un enlace azul
   sin fondo es lo único que hay que encontrar para salir, y con la tarjeta
   detrás compitiendo cuesta verlo. Un botón se ve como un control. El resto de
   pantallas conserva `.btn-back-corner`, que es un enlace y no se toca — esta
   clase es propia y no un modificador de aquélla para no arrastrarlas.

   "Descargar" va en la esquina opuesta a "Volver" para que no se toquen con el
   pulgar. Los dos, fuera del escenario girado: son interfaz, no parte de la
   tarjeta. Heredan del `button` de siempre —fondo azul, blanco, radio 7px— y
   solo se les recorta el tamaño, que aquí no es un control principal. */
.card-corner-btn {
  position: fixed;
  top: 0.75rem;
  z-index: 100;
  padding: 0.4rem 0.9rem;
  font-size: 0.85rem;
}
.card-corner-back { right: max(1.25rem, calc(50% - 480px + 1.25rem)); }
.card-corner-dl   { left:  max(1.25rem, calc(50% - 480px + 1.25rem)); }

/* Los tres controles se desvanecen juntos a los 2,5 s y vuelven al tocar la
   pantalla, como un visor de fotos. Con `pointer-events: none` mientras están
   ocultos, para que no sigan capturando toques en zonas invisibles — y para que
   ese toque llegue al contenedor, que es quien los vuelve a sacar.

   **La barra entra en el grupo desde el 2026-08-19**, y con ella se va también
   el contador: la pantalla existe para ver la tarjeta sin nada encima, y dejar
   una franja permanente para que el contador nunca falte cambiaba eso a cambio
   de un dato que está a un toque. Lo que NO se va con la barra es el hueco que
   ocupa: `.card-area` mantiene su `bottom`, así que la tarjeta no cambia de
   tamaño al esconderse los controles. Reclamar ese hueco obligaría a recalcular
   el escenario en cada parpadeo, y la tarjeta daría un salto cada 2,5 s. */
.card-view .card-corner-btn,
.card-view .card-bar {
  /* La de `background` se repite porque `transition` es una propiedad única:
     declarándola aquí a secas, los botones perderían la suya al pulsarlos. */
  transition: opacity 0.25s ease, background 0.15s;
}
.card-view--bare .card-corner-btn,
.card-view--bare .card-bar {
  opacity: 0;
  pointer-events: none;
}

/* ── La barra de control de la tarjeta (§12.7) ─────────────────────────────────

   Punto, contador y un solo botón Iniciar/Parar, en una franja de una línea bajo
   la tarjeta. **Va fuera del escenario girado**: lo que se pinta dentro rota con
   la tarjeta —en horizontal se leería de lado— y además dejaría de ser lo que el
   cliente se descarga, que es la regla por la que el `MODE {modo}` sobre el pie
   se retiró el 2026-08-15.

   **Se desvanece con los dos botones de las esquinas** y vuelve con ellos al
   tocar la pantalla (la regla, más arriba). Se probó al revés —la barra fija y
   solo los botones desvaneciéndose— para que el contador no faltara nunca, y lo
   que se perdía era justo lo que esta pantalla vende: la tarjeta sin nada
   encima.

   El alto es una variable porque lo comparten dos reglas —esta y el `bottom` de
   `.card-area`— y separarlas metería la tarjeta debajo de los controles. */
.card-view {
  --card-bar-h: 4.75rem;

  /* La separación entre el punto y el contador. Es variable por lo mismo que el
     alto: la comparten dos reglas —el `gap` de `.card-bar-state` y el sangrado
     del rótulo— y si divergieran, el nombre del modo dejaría de empezar en la
     misma columna que el contador. */
  --card-bar-dot-gap: 0.5rem;
}

/* Dos líneas: el rótulo arriba y los controles debajo. Fue de una hasta que el
   rótulo entró, y el alto subió con él — está en la variable justamente para
   que el hueco que reserva `.card-area` suba a la vez y la tarjeta no se meta
   por debajo. */
.card-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--card-bar-h);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.35rem;
  padding: 0 1.25rem;
  /* Sobre el negro de la vista, un gris muy oscuro separa la franja de la
     tarjeta sin encender una segunda superficie que compita con ella. */
  background: #111;
  box-sizing: border-box;
}

/* El rótulo, que desde el 2026-08-20 es **el nombre del modo** y ya no el texto
   "Ciclo de programación" (redundante: el contador y el botón Iniciar/Parar ya
   dicen de qué va la franja).

   Se queda en gris y pequeño aunque ahora lleve contenido y no una etiqueta: lo
   único de aquí que hay que poder leer de lejos sigue siendo el contador, y el
   nombre del modo se lee de cerca, cuando se va a pulsar el botón.

   **Y hay que recortarlo a una línea**, que antes no hacía falta porque el texto
   era una constante corta. El nombre más largo del catálogo real son 123
   caracteres: envolviendo a dos líneas, la franja crecería por encima de
   `--card-bar-h`, que es lo que reserva el hueco de la tarjeta, y los controles
   se meterían sobre ella. Es el mismo recorte que llevaba el botón mientras el
   nombre vivía dentro de él.

   **Y empieza en la misma columna que el contador**, no en el borde de la
   franja: se sangra el ancho del punto más su separación. Las dos medidas salen
   de variables —`--dot-size` en `:root` y `--card-bar-dot-gap` aquí al lado— y
   no de un 18px escrito a mano, porque quien las cambie no va a venir a mirar
   esta regla. La columna se sostiene en los dos estados porque el punto, parado,
   se esconde con `visibility` y sigue ocupando su hueco (más abajo). */
.card-bar-title {
  font-size: 0.78rem;
  color: #9aa3af;
  line-height: 1;
  padding-left: calc(var(--dot-size) + var(--card-bar-dot-gap));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-bar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.card-bar-state {
  display: flex;
  align-items: center;
  gap: var(--card-bar-dot-gap);
}

/* Parado, el punto se esconde **sin dejar de ocupar su hueco**: con
   `display: none` el contador se corría de sitio en cada arranque y cada parada,
   y en una franja de una línea, con el número cambiando cada segundo, ese salto
   se ve más que el propio punto. La animación se para también — un latido que
   nadie ve es trabajo del compositor a cambio de nada. */
.card-bar-dot--off {
  visibility: hidden;
  animation: none;
}

/* El contador hereda su tamaño y sus cifras tabulares de `.device-countdown`,
   que es la misma pieza que el del círculo — de hecho comparten `id`, porque
   las dos pantallas nunca coexisten. Aquí solo cambia el color: sobre negro, el
   del texto de la app no se vería. */
.card-bar .device-countdown {
  color: #fff;
  margin-top: 0;
}
.card-bar .device-countdown--idle { color: #9aa3af; }

/* El botón dice "Iniciar" o "Parar" y nada más. Llevó el nombre del modo dentro
   ("Iniciar · Relax") hasta el 2026-08-20, y con él llevaba un `max-width: 62%`
   más su recorte: la franja es de una línea y un nombre largo dejaba al contador
   fuera. Eso se fue con el nombre, que ahora vive en el rótulo de arriba — y ahí
   es donde está el recorte. Se queda el `nowrap`, que es barato y evita que un
   idioma con una palabra más larga parta el botón en dos. */
.card-bar-btn {
  padding: 0.5rem 1.1rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Texto explicativo del formulario de inclusión. Tamaño y color de párrafo
   secundario, como los tenía dentro de .card, para que el cambio de contenedor
   no lo convierta en texto principal. */
.claim-desc {
  font-size: 0.93rem;
  color: #555;
  line-height: 1.5;
  margin-bottom: 1rem;
}

/* Razón de que el botón de desvincular esté deshabilitado. Debajo de él, para
   que se lea como su explicación y no como un aviso suelto de la pantalla.

   **Es la única restricción que se anticipa así**: los cuatro topes se avisan al
   chocar, con un popup. La diferencia es que aquí la razón no es un tope sino
   que no hay nada que hacer — con un solo aparato propio, desvincular no puede
   ocurrir nunca, así que no hay ningún intento que valga la pena dejar hacer. */
.mg-unlink-blocked {
  font-size: 0.88rem;
  color: #666;
  margin-top: 0.5rem;
}

/* ── Conceder gestión ────────────────────────────────────────────────────────── */

/* Lista de gestores del aparato. Una fila por gestor: el email a la izquierda,
   ocupando lo que sobre y cortado con puntos suspensivos, y su "Anular
   concesión" a la derecha. Con el tope en 10 esto puede ser una columna de
   rojo; se acepta, porque en la práctica serán uno o dos y el tope es un
   guardarraíl, no una expectativa. */
.grant-manager-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.grant-manager-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.grant-manager-email {
  flex: 1;
  min-width: 0;          /* sin esto un hijo de flex no baja de su ancho de contenido */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.95rem;
}

.grant-manager-row .btn-danger { flex: none; }

/* El aviso de qué conocerá el gestor. Va junto al botón de generar, no bajo la
   clave: el consentimiento se acepta al repartir la invitación, no al leerla
   después.
   Sin tamaño ni color propios a propósito: se lee con el mismo peso que la
   explicación que tiene encima, porque es tan importante como ella.

   La separación entre los dos la pone la explicación, con margen INFERIOR, y no
   este aviso con margen superior. Cuando hay gestores la explicación se oculta y
   el aviso queda el primero del bloque, y entonces un margen superior lo
   despegaría del borde. Tampoco vale `p + .grant-exposure`: el `<p>` del mensaje
   de error va siempre justo antes, y aunque esté oculto la adyacencia en CSS es
   estructural y no visual, así que casaría igual. */
.grant-desc {
  margin-bottom: 1rem;
}

.grant-key {
  font-family: 'Courier New', Courier, monospace;
  font-size: 2.4rem;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-align: center;
  color: #222;
  background: #f5f7fa;
  border: 1px solid #e0e4ea;
  border-radius: 8px;
  padding: 0.9rem 0.5rem;
  /* Sin margen inferior: lo pone .grant-key-expiry, que va justo debajo. */
  margin: 0.5rem 0 0;
}

/* La caducidad, pegada a la clave y en tono menor: el dato que se dicta es la
   clave, y esto es la letra pequeña que se consulta después. */
.grant-key-expiry {
  text-align: center;
  font-size: 0.9rem;
  color: #666;
  margin: 0.4rem 0 1rem;
}

/* ── Gestión de modos: vista de resumen ──────────────────────────────────────
   Solo lectura: los mismos modos que las vistas de edición, sin checkbox y sin
   botón de actualizar. */

.mg-summary-list {
  margin: 1rem 0;
}

.mg-summary-row {
  display: flex;
  align-items: center;
  font-size: 0.95rem;
  color: #1a1a1a;
  padding: 0.25rem 0;
}

/* En el resumen la fila no lleva control delante, así que su nota no se sangra:
   la regla es "la nota cuelga del nombre", y aquí el nombre empieza en el
   margen. Con la sangría de las otras dos listas quedaría desplazada respecto
   de lo que describe. */
.mg-summary-list .mg-info-note {
  margin-left: 0;
}

.mg-summary-empty {
  font-size: 0.9rem;
  color: #888;
  margin: 1rem 0;
}

/* "No tienes grupos de modos gestionables asignados". Una sola definición, y en
   eso está el detalle: aparece en dos sitios —como texto del resumen vacío y
   como nota bajo la lista— y con dos clases se veía de dos tamaños según lo que
   hubiera encima. Es la misma frase respondiendo a la misma pregunta, así que
   se ve igual siempre. */
.mg-no-groups {
  font-size: 0.9rem;
  color: #888;
  margin: 1rem 0;
}

/* La nota que dice de qué NO habla el resumen. Pequeña y gris porque es una
   aclaración, pero imprescindible: sin ella un aparato con modos de otros
   grupos parecería tener menos de lo que tiene. */
.mg-summary-note {
  font-size: 0.78rem;
  line-height: 1.45;
  color: #888;
  margin-top: 1.25rem;
}

/* El subtítulo «Grupo Equilibrio» de la vista de checkboxes, en el sitio que
   ocupaba el desplegable. Separa la identificación del aparato del formulario
   que hay debajo; el aspecto lo pone `.device-label`, que es la misma pieza que
   rotula «Dispositivo» dos líneas más arriba. */
.mg-group-label {
  margin-top: 1.25rem;
}

/* El botón rojo del pie va separado de lo que tenga encima —la lista de grupos
   o la frase que explica por qué está vacía—, para que lo irreversible no quede
   pegado a una fila que se toca. Es 1rem y no 0.5 porque .btn-sm ya aporta
   medio por su cuenta: la regla suma, no sustituye. */
#mg-release,
#mg-unlink {
  margin-top: 1rem;
}

/* ── Buttons ─────────────────────────────────────────────────────────────────── */

button {
  padding: 0.65rem 1.25rem;
  background: #4a7fe5;
  color: #fff;
  border: none;
  border-radius: 7px;
  font-size: 0.97rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}

button:hover:not(:disabled) { background: #3a6fd5; }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-danger { background: #d32f2f; }
.btn-danger:hover:not(:disabled) { background: #b71c1c; }

.btn-secondary { background: #4a7fe5; }
.btn-secondary:hover:not(:disabled) { background: #3567c8; }

.btn-sm {
  padding: 0.45rem 0.9rem;
  font-size: 0.85rem;
  margin-top: 0.5rem;
}

/* ── Messages ────────────────────────────────────────────────────────────────── */

.msg {
  padding: 0.6rem 0.9rem;
  border-radius: 7px;
  font-size: 0.88rem;
  line-height: 1.45;
  margin-bottom: 0.75rem;
}
.msg-error   { background: #fdecea; color: #b71c1c; border: 1px solid #f5c6cb; }
.msg-warn    { background: #fff8e1; color: #6d4c00; border: 1px solid #ffe082; }
.msg.msg-page { max-width: 420px; margin: 0; padding: 1.25rem 1.5rem; }
/* El aviso de estado que va sobre el círculo del device. Separa de lo de abajo,
   que quedaba pegado.

   Tiene que ser MAYOR que el margin-top de .section (--screen-gap, 1rem) para
   que se note: son hermanos en el flujo, así que sus márgenes colapsan y el
   hueco real es el mayor de los dos — el margin-bottom de .msg (0.75rem) no se
   veía en absoluto. Subirlo a 1rem tampoco habría cambiado nada.

   Eran 2rem cuando debajo iba un bloque rectangular a todo el ancho. Con el
   círculo se leen como más, porque el hueco ya no está delimitado por dos
   bordes rectos, así que bajan a 1.25rem — sigue por encima del 1rem que hace
   falta para que no colapse. */
.msg--banner { margin-bottom: 1.25rem; }
.msg-success { background: #e8f5e9; color: #1b5e20; border: 1px solid #c3e6cb; }

/* ── Badges ──────────────────────────────────────────────────────────────────── */

.badge {
  display: inline-block;
  padding: 0.18rem 0.55rem;
  border-radius: 20px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.badge-green  { background: #e8f5e9; color: #2e7d32; }
.badge-red    { background: #fdecea; color: #c62828; }
.badge-orange { background: #fff3e0; color: #e65100; }
/* `Inactivo`: sano y parado. Gris y no naranja a propósito — el naranja
   significa "hay algo que mirar", y un aparato en neutro es el estado más común
   de uno que no se está usando. En naranja, el color habría dejado de decir
   nada: se vería casi siempre y habría que leer el texto para saber si pasa
   algo. Cada color conserva su significado — verde emite, gris parado, naranja
   atiende, rojo avería. */
.badge-grey   { background: #eceff1; color: #4a5560; }

/* ── Transmit badge ──────────────────────────────────────────────────────────── */

/* El punto que marca el modo que el aparato está emitiendo. **Ya no hay
   badge de transmisión**: decía con palabras lo que ahora dicen el badge de
   estado (`Activo` / `Inactivo`) y este punto sobre la fila del modo, así que la
   pantalla lo repetía tres veces. Aquí queda solo la forma; el color y el pulso
   los pone `.mg-active-dot`, que es quien lo usa en las tres listas. */
.transmit-dot {
  width: var(--dot-size);
  height: var(--dot-size);
  border-radius: 50%;
}

@keyframes transmit-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74,127,229,.5); }
  50%       { opacity: .5; box-shadow: 0 0 0 5px rgba(74,127,229,0); }
}

/* ── La fase final (§12.2) ─────────────────────────────────────────────────────

   Los últimos 30 segundos del ciclo se anuncian con un doble pitido cada dos
   segundos, y el color es lo que dice lo mismo cuando el sonido no puede: el
   interruptor de silencio del iPhone enmudece la página entera sin que nada
   pueda evitarlo, así que el aviso sonoro nunca es el único.

   **Una sola clase, `.prog-final`, para las tres piezas que afirman "esto está
   emitiendo"**: el halo del círculo, el punto del modo activo en las listas de
   modos y el punto de la barra de la tarjeta. La pone `progPaintPhase()` en el
   documento entero, porque solo hay una pantalla a la vez y todos sus puntos son
   del aparato que esa pantalla enseña. Con reglas separadas por pantalla, una
   podía quedarse en azul diciendo lo contrario que otra.

   **El azul y el naranja no significan lo mismo y por eso no se mezclan**: el
   azul dice "emite", el naranja dice "emite y está acabando". El punto sigue
   latiendo en los dos, porque lo que cambia es la fase y no si transmite. */
.transmit-dot.prog-final {
  background: #e07b00;
  animation: transmit-pulse-final 1.5s ease-in-out infinite;
}

@keyframes transmit-pulse-final {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(224,123,0,.5); }
  50%       { opacity: .5; box-shadow: 0 0 0 5px rgba(224,123,0,0); }
}

.device-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
}

.device-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: #333;
  margin-bottom: 0.5rem;
}
/* Encabezado de "Dispositivos de otros usuarios" en la lista: necesita aire por
   arriba para leerse como una sección nueva y no como el pie de las tarjetas
   de los propios, que van pegadas justo encima. */
.device-label--spaced { margin-top: 2rem; }

.device-id-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.device-id-row .device-uuid-full { margin-bottom: 0; }


.sub-info {
  font-size: 0.82rem;
  color: #666;
}

.screen-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: #333;
  margin-top: var(--screen-gap);
  margin-bottom: 1rem;
}

/* .section lleva margen superior NEGATIVO en móvil, para pegarse bajo la
   cabecera fija cuando es lo primero de la pantalla. Con un título delante deja
   de serlo, y ese tirón la montaría encima del título: aquí el hueco ya lo pone
   el margin-bottom del h2. Mayor especificidad que la regla del media query, así
   que vale en los dos anchos sin repetirla. */
.screen-title + .section {
  margin-top: 0;
}

/* Hubo aquí una `.msg--banner + .section` con el mismo fin, para cuando entre el
   título y el bloque se colaba el aviso de la Pantalla principal. Ya no hace
   falta: el aviso solo aparece en esa pantalla, donde ahora lo que sigue es el
   círculo, y `.device-circle-slot` no tiene margen que anular — el hueco lo pone
   entero el margin-bottom del aviso. Su equivalente para el bloque de modos
   está junto a `.device-circle-slot`. */


/* ── Device grid ─────────────────────────────────────────────────────────────── */

.device-grid {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.device-card {
  background: #fff;
  border-radius: var(--block-radius);
  padding: 1.75rem 1.5rem;
  min-height: 120px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .08);
  cursor: pointer;
  transition: box-shadow 0.2s, transform 0.15s;
  border: 1px solid #eee;
}

.device-card:hover {
  box-shadow: 0 4px 18px rgba(0, 0, 0, .13);
  transform: translateY(-2px);
}

.device-card:focus {
  outline: 2px solid #4a7fe5;
  outline-offset: 2px;
}

/* Mode block: column layout, always stacks (label+badge on top, name below) */
.mode-block {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  width: 100%;
  color: #333;
}
.mode-block__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.mode-block__label { font-size: 0.95rem; font-weight: 600; }
.mode-block__name  { font-size: 0.95rem; font-weight: 700; }

/* ── Device manage ───────────────────────────────────────────────────────────── */

.device-uuid-full {
  font-family: 'Courier New', Courier, monospace;
  font-size: 1.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #222;
  margin-bottom: 0.25rem;
}

.device-card .device-id-row {
  margin-bottom: 0.5rem;
}

/* ── Sections ────────────────────────────────────────────────────────────────── */

.section {
  background: #fff;
  border-radius: var(--section-radius);
  padding: 1.5rem;
  margin-top: var(--screen-gap);
  margin-bottom: 1.25rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
  border: 1px solid #eee;
}

.section h3 {
  font-size: 0.95rem;
  font-weight: 600;
  color: #333;
  margin-bottom: 1rem;
}


.section form { margin-top: 0.75rem; }
/* Sin margen: el formulario es lo PRIMERO del bloque desde que el modo activo
   se fue al círculo. Los 2rem que tenía separaban "Cambiar modo" del bloque de
   modo actual que había encima dentro del mismo `.section`, y sin él quedaban
   sumados al padding de 1.5rem — 56px de aire sobre la primera línea del bloque. */
#mode-form { margin-top: 0; }
/* La etiqueta de la lista, con la tipografía de .mode-block__label */
#mode-form label { font-size: 0.95rem; font-weight: 600; color: #333; }
.section p + button { margin-top: 0.75rem; }

/* El rótulo del ciclo y su botón, envueltos juntos. **El envoltorio no es
   decoración**: el formulario es un flex con `gap: 1rem`, que es el hueco entre
   bloques, y un rótulo a 1rem de su propio botón deja de leerse como suyo. Aquí
   dentro son 0,4rem, y hacia la lista sigue mandando el gap del formulario.

   La tipografía es la de `.device-label` y la de las etiquetas del propio
   formulario: es un rótulo de los de esta app, no una variante nueva.

   **`.mode-head` es lo mismo un piso más abajo**: el rótulo "Modo" con su lista
   de radios, envueltos por la misma razón. Van agrupados en una regla y no
   copiados en dos para que el hueco de dentro no pueda separarse entre los dos
   pares — son el mismo gesto repetido, no dos decisiones.

   El nombre `.mode-head` ya existió y se retiró el 2026-08-19 con la línea de
   instrucciones que encabezaba entonces la lista. Vuelve para otra cosa: aquélla
   decía **qué hacer** ("Selecciona un modo y pulsa Iniciar") y ésta **nombra**
   lo que hay debajo, que es la misma distinción por la que el rótulo del ciclo
   sí se quedó. */
.prog-head,
.mode-head {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.prog-head-label,
.mode-head-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: #333;
}

/* ── El círculo de la Pantalla principal (§12.2) ──────────────────────────────

   La identidad del aparato —qué es, cómo está y qué emite— va dentro de un
   círculo, y el cambio de modo en un bloque aparte. Antes eran un solo
   `.section--control` rectangular con las dos cosas dentro.

   **No es un `.section`**: no comparte ni el radio, ni el padding, ni el ancho.
   Por eso es una clase propia y no un modificador — un `.section` redondeado al
   50% seguiría llevando su `padding: 1.5rem`, que aquí no vale porque el texto
   no dispone del ancho del círculo sino del cuadrado inscrito.

   **`width`/`height` con `min()` y no `aspect-ratio`**, que es Safari 15.0 y
   queda justo en el borde del suelo del proyecto (§12). `min()` es Safari 11.1,
   así que no hay nada que arriesgar a cambio de nada.

   El tope de 300px es lo que impide que en una tablet el círculo ocupe media
   pantalla; el 78vw es lo que lo mantiene dentro del margen en un móvil
   estrecho.

   **El diámetro vive en `--circle-d` y no escrito tres veces**, porque el
   padding de abajo se calcula a partir de él: si se separaran, el hueco del
   cuadrado inscrito dejaría de corresponder al círculo que lo rodea. */
.device-circle {
  --circle-d: min(78vw, 300px);
  width: var(--circle-d);
  height: var(--circle-d);
  border-radius: 50%;
  background: #fff;
  border: 3px solid #4a7fe5;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Alineado ARRIBA y no centrado: el rótulo "Dispositivo" tiene que quedar lo
     más alto que permita el círculo, y con `center` bajaba en cuanto el
     contenido crecía —un nombre de modo de tres líneas lo empujaba solo—. */
  justify-content: flex-start;
  gap: 0.2rem;
  /* **El padding superior ES el borde del cuadrado inscrito**, que es el único
     sitio donde el texto cabe de verdad dentro de una circunferencia. Para un
     círculo de diámetro D, el lado del cuadrado es D/√2, así que su borde queda
     a (1 − 1/√2) / 2 ≈ 0.1465 · D.
     Va calculado y no en píxeles fijos porque el círculo es fluido: un valor
     fijo solo sería correcto a 300px y caería dentro de la curva en un móvil
     estrecho.

     **Y va sobre `--circle-d`, NO en porcentaje**, que es lo que fue hasta el
     2026-08-20 y estaba mal: un padding en porcentaje se resuelve contra el
     ancho del BLOQUE CONTENEDOR —aquí `.device-circle-slot`, o sea el ancho de
     `main`—, nunca contra el del propio elemento. En un móvil los dos anchos
     casi coinciden (350 contra 300) y la desviación eran siete píxeles que no
     se veían; en escritorio el contenedor son 920px con el círculo capado a
     300, así que el padding salía de **135px** por lado: el contenido arrancaba
     a media altura y se derramaba fuera del círculo, y con 30px de ancho útil
     «Sin caducidad» se partía en una palabra por línea.

     Abajo no lleva el mismo: el contenido crece hacia abajo y reservar otro
     14.65% dejaría sin sitio a un nombre de modo largo. */
  padding: calc(var(--circle-d) * 0.1465) calc(var(--circle-d) * 0.1465) 1.5rem;
  text-align: center;
}

/* Aire bajo el badge. Es la frontera entre lo que el aparato ES —rótulo y
   short id— y lo que está HACIENDO —modo y contador—, así que separarla algo
   más que el `gap` general hace que el círculo se lea en dos mitades en vez de
   como una lista de seis renglones. */
.device-circle .badge {
  margin-bottom: 0.55rem;
}

/* El hueco de abajo existe para el halo: son 32px que se expanden desde el
   borde, y sin sitio el bloque siguiente los taparía a media expansión.

   **Y arriba lleva el margen que llevaba el título**, que desapareció el
   2026-08-19: el círculo pasó a ser lo primero de la pantalla, y sin esto
   quedaba pegado bajo la cabecera. Es el mismo par de valores que usaban
   `.screen-title` y `.section` —negativo en móvil para meterse bajo la cabecera
   fija—, no un número elegido a ojo. */
.device-circle-slot {
  display: flex;
  justify-content: center;
  margin-top: var(--screen-gap);
  padding: 0 0 2rem;
}

/* Con el aviso delante, el círculo ya no es lo primero: el hueco lo pone entero
   el `margin-bottom` del aviso, igual que hacía el título. Hermana de
   `.screen-title + .section`, y hace falta aparte porque las dos son de hermano
   adyacente. */
.msg--banner + .device-circle-slot {
  margin-top: 0;
}

/* El bloque de cambio de modo va DETRÁS del círculo, así que no es lo primero
   de la pantalla — y en móvil `.section` lleva margen superior negativo
   precisamente para pegarse bajo la cabecera cuando sí lo es. Sin esto, ese
   -1rem lo subía sobre el halo del círculo. */
.device-circle-slot + .section {
  margin-top: 0;
}

/* El nombre del modo, dentro del círculo. **Recortado a tres líneas con `…`**:
   medidos los 1.074 nombres del catálogo real, la mediana son 20 caracteres y
   el percentil 95 son 40, pero el más largo son 123 — y ése, sin recortar,
   desborda el círculo entero y se lleva por delante la línea de expiración.
   Recortar aquí no esconde nada: el nombre completo está en la lista de abajo,
   donde la fila envuelve sin límite y va marcada con su punto azul.
   `-webkit-line-clamp` es Safari 5 / Chrome 6, muy por debajo del suelo.

   **Y ese hueco de tres líneas se RESERVA siempre, lo ocupe el nombre o no**
   (`min-height`). Es lo que deja el contador y la fecha de validez en un sitio
   fijo: sin ello, la pareja de abajo subía y bajaba según lo largo que fuera el
   nombre del modo, así que dos aparatos del mismo cliente enseñaban su fecha a
   alturas distintas. El precio, que es real: con un nombre de una línea —la
   mediana del catálogo son 20 caracteres— queda un hueco vacío de unos 38px
   entre el nombre y el contador.

   **No se ancla la pareja al fondo con un `margin-top: auto`**, que es la otra
   forma de conseguir lo mismo y es peor: el fondo del círculo es su parte más
   estrecha —a la altura del padding de 1.5rem el ancho disponible baja a unos
   163px— así que la fecha quedaría metida en la curva SIEMPRE, y no solo en el
   caso raro del nombre largo. Reservando el hueco arriba, la pareja cae donde ya
   caía con un nombre de tres líneas, o sea en una posición que ya funcionaba.

   **El `line-height` tiene que ser explícito para que "tres líneas" sea un
   número.** Heredaba `normal`, que no se puede multiplicar.

   **Y el 3 está escrito dos veces a sabiendas**, que es lo contrario de lo que
   hace el resto de este fichero. La alternativa era `var(--mode-lines)` en los
   dos sitios, y no compensa: si la sustitución fallara en el `-webkit-line-clamp`
   —una propiedad prefijada—, el recorte desaparecería y un nombre de 123
   caracteres volvería a desbordar el círculo. Un fallo mudo y grave a cambio de
   elegancia. Si se toca uno, se tocan los dos. */
.device-circle-mode {
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.25;
  min-height: calc(3 * 1.25em);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* El contador de la programación. **Tabular a propósito**: sin cifras de ancho
   fijo, la cuenta atrás baila horizontalmente cada segundo, y un número que se
   mueve dentro de un círculo que además late es ruido puro.
   `font-variant-numeric` es Safari 9.1 / Chrome 52, muy por debajo del suelo.

   Parado va en gris (`--idle`, justo debajo), que es el mismo gesto que hace el
   borde del círculo al dejar de emitir. */
.device-countdown {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  margin-top: 0.35rem;
}

/* Parado, el número es gris. Es el mismo gesto que el borde del círculo, que
   también pierde el color al dejar de emitir: así el estado se lee del bloque
   entero y no de una sola pieza. Y separa los dos sentidos del mismo número —
   cuánto queda frente a cuánto va a durar— sin añadir ninguna palabra. */
.device-countdown--idle { color: #9aa3af; }

/* La validez, despegada de lo que hay encima. El `gap` del círculo es el mismo
   para todos los hijos, y aquí hace falta algo más: es el único dato del círculo
   que no habla del ahora —el badge, el modo y el contador dicen qué está
   haciendo el aparato, y esto hasta cuándo sirve—, así que sin este respiro se
   lee como un renglón más de la lista.

   **Se separa de la fecha y no del resto porque hay sitio**: con el padding
   arreglado, el contenido queda alto dentro del círculo y sobran unos 55px por
   debajo con un nombre de modo de una línea. El margen sale de ahí.

   Lo que acota cuánto se puede subir es el **peor caso, no el normal**: un
   nombre de modo ocupa hasta tres líneas (`-webkit-line-clamp: 3`), que son unos
   36px más, y ahí el hueco baja a la decena de píxeles. Es también el motivo de
   que el padding de abajo no sea el mismo 14.65% que el de arriba. */
.device-circle .sub-info {
  margin-top: 1rem;
}

/* Y **late mientras el aparato emite**, quieto en pausa. No es decoración: dice
   lo mismo que el punto del modo activo —de hecho la condición es la misma
   función, `isTransmitting()`— y por eso el bloque entero se lee de un vistazo
   sin buscar el badge.

   **Es el mismo gesto que `transmit-pulse`**, el halo que se expande desde ese
   punto, en su mismo azul y a mayor escala. Se probó antes tiñendo
   el `border-color` y no se veía: 3 px de línea cambiando de tono es demasiado
   poco para una pantalla que se mira de lejos, mientras que un halo que sale
   del bloque se ve sin buscarlo.

   **La sombra base va repetida en los dos fotogramas** y no solo el halo:
   `box-shadow` es una propiedad única, así que animarla sustituye lo que
   `.section` declara — sin repetirla, el bloque perdería su sombra durante toda
   la animación.

   **3s, el doble de `transmit-pulse`** (1,5 s): los dos latidos coinciden uno de
   cada dos y se leen como una sola cosa en vez de como dos animaciones sueltas.
   Lento a propósito — lo que en un punto de 8 px es un pulso, en un bloque del
   tamaño de la pantalla sería un parpadeo.

   **El borde acompaña al halo** en vez de quedarse fijo, y no es adorno: con el
   halo solo, el efecto se quedó corto en un móvil (10 px y .45 pasaban
   inadvertidos). Que el borde aclare a la vez que el halo se expande hace que se
   mueva el contorno entero del bloque y no un reflejo alrededor, que es lo que
   lo vuelve perceptible sin subir la velocidad — subir la velocidad es lo que
   convertiría un latido en un parpadeo.

   Los tres números que se ajustan mirándolo: el alcance del halo (32 px), su
   opacidad de salida (.55) y el azul claro del borde (#7ea6f0). El alcance se
   dobló —eran 16— al pasar el bloque a círculo: un halo circular se lee peor que
   uno rectangular al mismo tamaño, porque no hay esquinas donde el ojo lo pille. */
@keyframes control-breathe {
  0%, 100% {
    border-color: #4a7fe5;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .06), 0 0 0 0 rgba(74, 127, 229, .55);
  }
  50% {
    border-color: #7ea6f0;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .06), 0 0 0 32px rgba(74, 127, 229, 0);
  }
}

.device-circle--live {
  animation: control-breathe 3s ease-in-out infinite;
}

/* El mismo latido en naranja durante los últimos 30 segundos. Se repite el
   keyframe entero en vez de animar solo el color: `box-shadow` y `border-color`
   van dentro de los fotogramas, así que no hay ninguna variable que cambiar
   desde fuera sin duplicar igualmente los dos estados.

   El ritmo NO cambia —los mismos 3 s— y es deliberado: acelerarlo convertiría el
   latido en un parpadeo, que es justo lo que el comentario de arriba explica que
   no hay que hacer. Lo que dice que el ciclo se acaba es el color, más los dobles
   pitidos que suenan a la vez. */
.device-circle--live.prog-final {
  animation: control-breathe-final 3s ease-in-out infinite;
}

@keyframes control-breathe-final {
  0%, 100% {
    border-color: #e07b00;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .06), 0 0 0 0 rgba(224, 123, 0, .55);
  }
  50% {
    border-color: #f0a952;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .06), 0 0 0 32px rgba(224, 123, 0, 0);
  }
}

/* Sin transmitir, el círculo no solo deja de latir: se vuelve gris. El borde
   azul quieto seguía afirmando una actividad que no hay, y el gris es el mismo
   par de colores que ya usan el badge y el interruptor. */
.device-circle--off {
  border-color: #b6bec7;
}

/* **Sin guarda de `prefers-reduced-motion`, y es una decisión medida.** Lo llevó
   un día y el resultado fue que en un iPhone el bloque no se movía **nada**
   mientras el punto latía a su lado: en iOS esa preferencia la activan
   tanto *Accesibilidad → Reducir movimiento* como el **modo de bajo consumo**,
   así que no era una preferencia declarada sino cualquier cliente con la batería
   baja. Se localizó con una sonda de color estático — el borde en rojo fijo se
   veía en iOS, así que la clase llegaba y lo único bloqueado era la animación.
   Y el punto tampoco lo respeta, de modo que el guarda dejaba a la app
   diciendo dos cosas distintas sobre la misma preferencia.
   Lo que se acepta a cambio, dicho claro: quien pide menos movimiento lo recibe
   igual. Si algún día se quiere respetar de verdad, la vía NO es volver a poner
   `animation: none` —eso borra la información, porque este latido *dice* que el
   aparato emite— sino dejar una marca estática equivalente en su lugar. */

.section-danger {
  border-color: #ffcdd2;
}

.section-danger h3 {
  color: #c62828;
}

/* ── Loading / empty states ──────────────────────────────────────────────────── */

.loading {
  text-align: center;
  padding: 3rem;
  color: #aaa;
  font-size: 0.95rem;
}

.empty {
  text-align: center;
  padding: 3rem 1rem;
  color: #888;
  font-size: 0.95rem;
  line-height: 1.7;
}

/* ── Gear menu ───────────────────────────────────────────────────────────────── */

.btn-menu {
  position: fixed;
  top: 0.5rem;
  right: max(1.25rem, calc(50% - 480px + 1.25rem));
  width: 2.5rem;
  height: 2.5rem;
  background: transparent;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}
.btn-menu:focus, .btn-menu:focus-visible, .btn-menu:active { outline: none; }

/* Back link pinned to the same corner the gear menu occupies elsewhere
   (mode groups screen: no menu here, "Volver" takes that spot instead). */
.btn-back-corner {
  position: fixed;
  top: 0.75rem;
  right: max(1.25rem, calc(50% - 480px + 1.25rem));
  z-index: 100;
  font-size: 0.9rem;
  font-weight: 500;
  color: #4a7fe5;
  text-decoration: none;
}
.btn-back-corner:hover { text-decoration: underline; }

.nav-menu {
  position: fixed;
  top: calc(0.5rem + 2.5rem + 0.25rem);
  right: max(1.25rem, calc(50% - 480px + 1.25rem));
  background: #fff;
  border: 1px solid #000;
  border-radius: 8px;
  overflow: hidden;
  z-index: 101;
  min-width: 180px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.menu-email {
  padding: 0.6rem 1rem;
  font-size: 0.78rem;
  color: #555;
  border-bottom: 1px solid #000;
  word-break: break-all;
}
.nav-menu button {
  display: block;
  width: 100%;
  padding: 0.75rem 1rem;
  background: none;
  border: none;
  border-bottom: 1px solid #e0e0e0;
  color: #111;
  text-align: left;
  cursor: pointer;
  font-size: 0.9rem;
  border-radius: 0;
  margin: 0;
}
.nav-menu button:last-child { border-bottom: none; }
.nav-menu button:hover { background: #f5f5f5; }
.nav-menu button.btn-danger { color: #c62828; }
.nav-menu button.btn-danger:hover { background: #fdecea; }

/* ── Responsive ──────────────────────────────────────────────────────────────── */

@media (max-width: 600px) {
  .card { margin: var(--screen-gap-mobile) auto; padding: 1.5rem; }
  .section { margin-top: var(--screen-gap-mobile); }
  .screen-title { margin-top: var(--screen-gap-mobile); }
  .device-circle-slot { margin-top: var(--screen-gap-mobile); }
  main { margin: 1rem auto; }
  .device-grid { flex-direction: column; }
}

@media (max-width: 400px) {
  .device-grid { flex-direction: column; }
}

/* ── Modal ───────────────────────────────────────────────────────────────────── */

.rb-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  width: 100%; height: 100%;
  background: rgba(0,0,0,0.45);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000;
}

.rb-modal {
  background: #fff;
  border-radius: var(--block-radius);
  padding: 1.75rem 1.75rem 1.25rem;
  max-width: 360px;
  width: calc(100% - 2rem);
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
}

.rb-modal p {
  font-size: 0.97rem;
  line-height: 1.5;
  color: #222;
  margin-bottom: 1.25rem;
}

.rb-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
}


/* ── Inspección del catálogo (§12.8) ─────────────────────────────────────────
   Tres pantallas colgando del bloque de Grupos de Cuenta. Reutilizan las filas
   del resumen de modos (.mg-summary-row) y el contenedor negro de la tarjeta
   (.card-view), así que aquí solo está lo que no existía. */

/* La fila de un grupo. **El icono "i" no puede ir dentro del botón** —un
   `<button>` no puede contener otro—, así que la fila es el contenedor flex y el
   botón es solo su primera mitad: nombre y punto. La nota, el recuento y el
   chevron van detrás, y quien navega es la fila entera para que los tres
   respondan al toque. Misma solución que la lista de modos de §12.8. */
.mg-group-row {
  display: flex;
  align-items: center;
}
/* El dedo solo en las que llevan a algún sitio: una fila apagada con cursor de
   mano prometería un toque que no responde. */
.mg-group-row[data-i] { cursor: pointer; }
/* La nota cuelga del nombre, y aquí el nombre empieza en el margen: sin control
   delante, la sangría de 2.3rem de las listas de modos la dejaría desplazada
   respecto de lo que describe. */
.mg-group-list .mg-info-note { margin-left: 0; }

/* El grupo, ahora tocable. Botón y no <li> a secas, para que responda al
   teclado y se anuncie como lo que es; se le quita todo el aspecto de botón
   porque lo que se ve es una fila de lista.

   **Se come el hueco libre** (`flex: 1`) en vez de medir lo que mide: así la
   zona pulsable llega hasta la nota en vez de acabar en el nombre, y el punto
   se queda pegado a éste porque el hueco queda a su derecha, dentro del botón. */
.mg-group-open {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
  padding: 0.35rem 0;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
/* **El nombre NO se come el hueco libre**, y ésa es toda la regla de esta fila:
   si creciera, empujaría el punto del modo que emite hasta el otro extremo y
   quedaría pegado al recuento. El punto va **detrás del nombre**, como en todas
   las listas de modos, así que el nombre mide lo que mide y el hueco lo reparte
   el recuento con su `margin-left: auto`.

   Se marca con una clase y no con `:nth-child(2)` porque las dos listas que usan
   esta fila no tienen las mismas piezas —la de Cuenta lleva enumerador delante y
   la de gestión lleva el recuento detrás—, así que la posición no es la misma.
   El `min-width: 0` deja que un nombre largo encoja en vez de empujar fuera al
   recuento y al chevron. */
.mg-group-name { min-width: 0; }

/* Cuántos modos de ese grupo tiene instalados el aparato. Gris y en cifras de
   caja fija, para que los paréntesis caigan a plomo de una fila a otra y la
   columna se pueda recorrer de un vistazo. El `auto` es lo que lo manda al
   extremo derecho: el chevron ya no puede hacerlo por su cuenta, porque la regla
   `.mg-group-count + .insp-chevron` le quita su propio `margin-left: auto`. */
.mg-group-count {
  flex: none;
  margin-left: auto;
  color: #6b7280;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}
/* Igual que la nota: el orden que se lee es `(3) ›`, con aire entre los dos. */
.mg-group-count + .insp-chevron { margin-left: 0.55rem; }

/* El icono de la fila de un grupo va entre el punto y el recuento, y necesita
   su propia separación: el `margin-left: auto` con el que se va al extremo en
   las listas de modos aquí no reparte nada, porque el hueco libre ya se lo ha
   comido el botón. */
.mg-group-row .mg-info-btn { margin-left: 0.5rem; }
.mg-group-row .mg-info-btn + .mg-group-count { margin-left: 0.5rem; }
/* La fila de un grupo que NO se gestiona: mismo alto y mismas columnas que la
   pulsable, para que la lista se lea como una sola, y apagada para que se vea
   que no lleva a ninguna parte. Sin chevron por lo mismo. */
.mg-group-plain {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
  padding: 0.35rem 0;
  color: #8a97ab;
}
/* El recuento ya es gris; dentro de una fila apagada se iguala al nombre para
   que no destaque más que él. */
.mg-group-plain .mg-group-count { color: inherit; }

/* El chevron que no está: ocupa su hueco y no se ve, para que el recuento de
   una fila apagada caiga en la misma columna que el de una pulsable. Es
   `visibility` y no `display: none` justamente por eso — y es el mismo glifo,
   así que el hueco es exacto y no una estimación que se descuadre al cambiar la
   tipografía. `aria-hidden` ya lo tiene puesto el marcado. */
.insp-chevron--ghost { visibility: hidden; }

.insp-chevron {
  flex: none;
  margin-left: auto;
  color: #9aa3af;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
/* Detrás de la nota, con aire: el orden que se lee es `(i) ›`. Sin nota, el
   `margin-left: auto` de arriba lo manda solo al extremo. */
.mg-info-btn + .insp-chevron { margin-left: 0.55rem; }

/* El "path" bajo el título: dónde estás. El título es siempre "Modos", así que
   ésta es la línea que distingue las dos pantallas. Mismo margen lateral que
   `.screen-title`, para que arranquen a plomo. */
.insp-path {
  margin: -0.6rem 0 0.7rem;
  font-size: 0.85rem;
  color: #6b7280;
}
.insp-path span { color: #b3b9c4; margin: 0 0.15rem; }

/* **Hermana de `.screen-title + .section`, y hace falta por lo mismo.** En móvil
   `.section` lleva margen superior NEGATIVO para pegarse bajo la cabecera fija
   cuando es lo primero de la pantalla; el título lo anula por adyacencia, pero
   el path se mete EN MEDIO y rompe esa adyacencia, así que el bloque volvía a
   subirse 1rem y se pegaba al path. Es exactamente la trampa que este fichero ya
   documenta para `.msg--banner`: las dos son de hermano adyacente, y cualquier
   cosa que se cuele entre medias necesita su propia regla.
   Con esto, el hueco lo pone entero el margin-bottom del path. */
.insp-path + .section { margin-top: 0; }

/* La fila de un modo. **Enlazada = inspeccionable**: no hay icono que lo diga,
   lo dice que responda al toque. El nombre es un botón y la nota queda fuera de
   él, porque un `<button>` no puede contener otro. */
.insp-open,
.insp-plain {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
  padding: 0;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
}
.insp-open { cursor: pointer; }
.insp-open > span:nth-child(2),
.insp-plain > span:nth-child(2) { flex: 1; min-width: 0; }

/* **Las filas que son botones no pueden heredar el azul de `button`.** La regla
   global `button:hover:not(:disabled)` las pintaba de azul oscuro al tocarlas, y
   en un móvil el `:hover` SE QUEDA PEGADO después del toque: la fila se quedaba
   azul hasta tocar otra cosa. Hace falta `:hover:not(:disabled)` y no un `:hover`
   a secas, porque aquella regla tiene más especificidad que una clase sola.
   Y el `transform` de `button:active` daría un salto de un píxel a una fila de
   lista, que no es un botón que se hunde. */
.mg-group-open:hover:not(:disabled),
.insp-open:hover:not(:disabled),
.insp-item--open:hover:not(:disabled) { background: none; }
.mg-group-open:active:not(:disabled),
.insp-open:active:not(:disabled),
.insp-item--open:active:not(:disabled) { transform: none; }
.mg-group-open,
.insp-open,
.insp-item--open { -webkit-tap-highlight-color: transparent; }

/* La lista de contenidos de un modo. Las filas que abren imagen son botones;
   las demás, texto. */
.insp-list { display: flex; flex-direction: column; }
.insp-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.55rem 0;
  border-bottom: 1px solid #eef0f4;
  font-size: 0.95rem;
  color: #222;
  text-align: left;
  background: none;
  border-left: none;
  border-right: none;
  border-top: none;
  font-family: inherit;
}
.insp-item > span:first-child { flex: 1; word-break: break-word; }
.insp-item--open { cursor: pointer; }
/* La última fila no lleva raya: una lista que termina en una línea suelta
   parece cortada. */
.insp-item:last-child { border-bottom: none; }
.insp-missing { flex: none; font-size: 0.8rem; color: #9aa3af; }

/* La imagen a pantalla completa, dentro del contenedor negro de la tarjeta. */
.insp-image-area {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3.5rem 0.75rem 1rem;
}
.insp-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.insp-image-failed { color: #eee; font-size: 0.95rem; text-align: center; }

/* ── Términos y condiciones (§12.10) ───────────────────────────────────────────

   El documento se trae de `/terms/` y se adopta tal cual, así que llega con las
   clases de `site.css` —`section`, `h2`, `.note`— que esta hoja no tiene. Lo de
   abajo es lo mínimo para que se lea bien dentro de la app; no se copia la hoja
   del sitio entera, que traería su barra, su rejilla y su tipografía.

   **El documento NO es una caja: sale directamente dentro de su `.section`.**
   Tuvo borde, radio, fondo y padding propios, y eso era una caja blanca dentro
   de otra caja blanca —`.section` ya pone las cuatro cosas—, que no separa nada
   de nada porque no hay nada de lo que separarlo: el bloque no contiene más que
   el documento y sus dos botones.

   **Y tampoco tiene scroll propio, que es lo que pone los botones al final del
   texto.** Lo tuvo —`max-height: 55vh`— para que Aceptar y Rechazar quedaran
   siempre visibles, y era justo lo contrario de lo que hace falta: pudiendo
   pulsar sin bajar, nada garantiza que el usuario haya pasado por el documento.
   Sin recorte, el texto fluye entero, scrollea la página y los botones caen
   detrás por construcción, sin ningún detector de scroll.

   Lo que queda de `.terms-doc` es tipografía y nada más. */
.terms-doc .doc { display: flex; flex-direction: column; gap: 1.25rem; }
.terms-doc header { display: flex; flex-direction: column; gap: 0.3rem; }
/* **El apartado es una columna con hueco, y eso arregla algo que estaba mal.**
   El reset de esta hoja pone `margin: 0` a todo, así que dos párrafos seguidos
   dentro de un mismo `<section>` salían pegados sin una línea entre ellos — y
   los términos tienen apartados de cuatro. En el sitio público no se ve porque
   allí `section` ya es una columna con `gap`; aquí no lo era. El hueco entre
   el `h2` y su primer párrafo sale ahora de aquí y no de un `margin-bottom`,
   para que no se sumen los dos. */
.terms-doc section { display: flex; flex-direction: column; gap: 0.5rem; }
.terms-doc h1 { font-size: 1.15rem; font-weight: 700; color: #111; }
.terms-doc h2 { font-size: 0.98rem; font-weight: 600; color: #333; }
.terms-doc p, .terms-doc li { font-size: 0.9rem; line-height: 1.55; color: #444; }
/* La sangría la pone esta regla porque el reset se llevó el `padding-left` por
   delante, y sin él los topos se salen de la caja. */
.terms-doc ul { padding-left: 1.2rem; display: flex; flex-direction: column; gap: 0.5rem; }
.terms-doc .lede { font-size: 0.85rem; color: #777; }
.terms-doc .note { font-size: 0.85rem; color: #666; border-left: 2px solid #cfd6e0; padding-left: 0.75rem; }
.terms-doc a { color: #4a7fe5; }
.terms-doc [hidden] { display: none !important; }

/* Los dos botones, con Aceptar primero: es la acción que se busca, y en un
   móvil la de abajo es la que cae bajo el pulgar. Rechazar va en secundario y
   no en rojo — no es destructivo, es una decisión legítima. */
.terms-actions {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1.25rem;
}

/* Los dos documentos legales del bloque de Cuenta, uno debajo del otro. Un
   `button` es inline-block, así que sin esto saldrían en la misma línea
   separados por el espacio del marcado. El margen superior lo pone el
   envoltorio porque `.section p + button` ya no aplica: entre el párrafo y los
   botones hay ahora un div. */
.legal-links { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.75rem; }

/* La línea de cuándo aceptó, en el bloque de Cuenta. */
.terms-accepted {
  font-size: 0.9rem;
  color: #555;
  margin-bottom: 0.75rem;
}
