/* Paleta y reglas de "Diseño de la interfaz" (CLAUDE.md).

   Sistema v3. Sigue siendo el mismo panel oscuro y sigue sin haber una sola
   sombra: la jerarquía se hace con superficie, borde y degradado. Lo que
   cambia respecto a la v2 es que **todo eso estaba demasiado junto**:

     - el teal iba desaturado (S 51%) y no llegaba a AA como texto a partir de
       `--superficie-2`, así que lo accionable se leía apagado;
     - las superficies altas se despegaban 3,7 de L* entre ellas y, sin
       sombras, eso es no tener profundidad;
     - el crema lo pintaba todo, y un color que lo pinta todo no destaca nada.

   La v3 separó la escalera en pasos iguales de 8 de L*, dejó el crema para
   títulos, rótulos y cifras, y añadió un magenta de energía para lo que debe
   destacar.

   **La v4 cambia el acento y el fondo, y está a prueba.** El teal sale y entra
   el morado del logo (`#885DF6`, sacado del propio PNG); el fondo baja al
   negro `#0A0C10` y las cuatro superficies se rehacen encima manteniendo los
   pasos de 8. El crema y los colores de estado no se tocan. El violeta
   secundario desaparece —quedaba a 8 grados del acento nuevo, y un dato de
   apoyo no necesita color propio— y el magenta de energía cruza al ámbar, para
   que lo que destaca no comparta familia con lo que se pulsa.

   Todo está medido contra la superficie real donde se apoya, los números están
   en los comentarios y las cuentas se rehacen en `tests/test_paleta.py`. */

:root {
  /* --- Superficies: cuatro escalones, de más honda a más alta ----------- */
  /* La escalera de la v3 está medida en L* de CIE, no elegida a ojo: los
     escalones van de 8 en 8 (3,3 / 11,3 / 19,3 / 27,2). Como aquí no hay
     sombras, esa separación **es** la profundidad.

     La escalera se rehizo entera sobre el negro del fondo, que antes era sólo
     el fondo de la barra lateral, y ya no tira al teal: el tono de las cinco
     es el del morado del logo, con muy poca saturación, para que el acento no
     caiga sobre superficies de otra familia. El propio `--fondo` entró como
     `#0A0C10`, que era azul (H 220) aunque a L* 3,3 no se note: se pasó a
     `#0D0B12`, el mismo L* y 1,001:1 de diferencia, para que en el código no
     quede un hex de una familia que ya no está en la paleta. */
  --fondo:        #0D0B12;
  --superficie:   #1E1D27;
  --superficie-2: #302D3B;
  --superficie-3: #413E50;
  --borde:        #4A4859;
  /* El borde de arriba: 3,09:1 sobre `--superficie` y 3,63:1 sobre `--fondo`,
     que es el 3:1 que pide un borde de control. Es el que llevan los controles
     que se apoyan sólo en un borde neutro (`.boton--secundario` en reposo), y
     también el que separa la barra lateral de la página: las dos están en el
     mismo negro, así que esa raya es la única frontera que hay. */
  --borde-alto:   #6C6781;

  /* --- Texto: cuatro escalones, y el de arriba es el crema -------------- */
  /* El crema dejó de ser el color de todo el texto. Cuando lo pinta todo no
     destaca nada, y era justo lo que pasaba: la identidad no se veía porque no
     había nada de lo que distinguirse. Ahora el cuerpo va en un neutro frío y
     el crema **marca**: títulos, rótulos de sección y cifras. Se distinguen por
     temperatura además de por claridad (1,34:1 entre los dos), que es lo que
     lo hace legible como jerarquía y no como un gris más. */
  --texto-alto:   #FFFBD5; /* crema: títulos, rótulos y cifras */
  --texto:        #DAD8E0; /* cuerpo */
  --texto-suave:  #B8B6C3; /* secundario; AA sobre las cuatro superficies */
  --texto-tenue:  #A4A1B0; /* rótulos y metadatos; AA hasta --superficie-2 */

  /* --- Acento: el teal es el único color de acción ---------------------- */
  /* El morado sale del logo, y es exacto: `#885DF6` es el 30% de los píxeles
     opacos de `img/logo-bot-s2.png`, con una desviación de un punto de tono.

     Ese morado **no llega a AA como texto sobre ninguna superficie** (4,61:1
     sobre `--fondo`, y 3,92 / 3,16 / 2,44 sobre las tres de encima): el azul
     aporta muy poca luminancia, así que un morado saturado se queda oscuro por
     mucha saturación que tenga. Se parte igual que se partió el teal, pero con
     los papeles al revés de como suena: el del logo es el que **rellena y
     bordea** —incluido el relleno del botón, que es donde se reconoce— y la
     variante clara es la que se **lee**.

     `--acento-hondo` es el del logo subido cuatro puntos de claridad, mismo
     tono y misma saturación. Con el `#885DF6` exacto, el texto del botón —que
     va en `--fondo`— se quedaba en 4,61:1: pasaba AA por dos centésimas y
     cualquier retoque posterior lo tiraba. Subido da 5,45:1. Aclarar el texto
     en vez de subir el morado **no vale y está medido**: el crema deja 4,05 y
     el blanco 4,25, los dos por debajo de AA, porque el morado del logo cae
     justo por encima del punto donde conviene invertir.

     `--acento` llega a AA sobre `--fondo`, `--superficie` y `--superficie-2`
     (6,86 / 5,84 / 4,70) pero no sobre `--superficie-3` (3,62): ahí va
     `--acento-alto`, que da 4,99. Es un escalón menos de cobertura que el teal
     de la v3, y es el precio del morado: para llegar a las cuatro habría que
     subirlo a un lavanda pálido que ya no se parece al logo. */
  --acento:       #A685FA; /* lo que se lee: enlaces, texto y estado activo */
  --acento-alto:  #BFA7FB; /* el que aclara: hover, foco y --superficie-3 */
  --acento-hondo: #956EF7; /* el morado del logo: rellenos y bordes */
  --acento-tenue: #1D1434; /* relleno hondo: 8,43:1 con --acento-alto encima */

  /* No hay color secundario, y es una decisión, no un olvido. Era un violeta
     de apoyo, y con el acento ya morado quedaba a 8 grados de él: el color de
     lo que no se pulsa y el de lo que sí eran casi el mismo. En vez de buscarle
     otro tono se ha quitado. **Un dato de apoyo no necesita color propio**: se
     distingue por tamaño y por tono, y la cifra que lo usaba —la de amigos—
     baja de crema a `--texto`, que es el mismo recurso que separa el cuerpo de
     un título. El color queda para lo accionable y lo destacado. */

  /* --- Energía: el magenta de lo que destaca --------------------------- */
  /* El color saturado del sistema, y el único que no tiene un trabajo
     funcional: no es estado —no dice si algo va bien o mal—, no es identidad de
     nada y **no es accionable**. Sólo dice "mira aquí". Por eso se usa poco y
     en sitios contados: el escalón de arriba de los planes y la cola del
     degradado de acento.

     Era magenta cuando el acento era teal. Con el acento morado se quedaba a
     59° de él —la misma familia violeta-rosa—, y lo que debe destacar no puede
     parecerse a lo que se pulsa. Así que cruza al lado cálido.

     **Aquí hay que ser honesto: la mitad cálida ya estaba llena.** Están
     `--peligro` (5,5°), `--vbucks` (42,5°) y `--aviso` (45,6°), y entre el rojo
     y el oro sólo quedan 37 grados de hueco. El ámbar se pone en el punto que
     más se aleja de los tres a la vez —24°—, y aun así queda a 19° del rojo, a
     18° del oro y a 21° del ámbar de aviso. **El tono no basta para
     distinguirlo: lo que lo separa es la saturación** (95% frente al 74% de
     `--vbucks`, el 67% de `--aviso` y el 49% de `--peligro`) y la claridad
     (L* 63,7 frente a los 77,2 de `--vbucks`).

     Se ha decidido **separarlo de `--vbucks` y no fundirlos**: compartir token
     volvería a mezclar identidad y atención, que es exactamente el error que se
     corrigió cuando `--vbucks` se separó de `--aviso`. Un saldo sano volvería a
     leerse como "mira aquí". Nunca coinciden dentro de una misma pieza; el
     riesgo es sólo de reojo y en pantallas distintas.

     Es el pareo más ajustado de la paleta. Si alguna vez se ven iguales, el
     que se mueve es éste, no `--vbucks`. */
  --energia:      #F9731A;
  --energia-alto: #F89859; /* el que se lee: 6,15:1 sobre --superficie-2 */

  /* --- Estado. El tono base rellena y bordea; el -alto es el que se lee - */
  --ok:            #4E9E7E;
  --ok-alto:       #6BC4A0;
  --aviso:         #C9A227;
  --aviso-alto:    #D9B640;
  --peligro:       #C2564B;
  --peligro-alto:  #E5837A;

  /* --- Moneda: el dorado de los V-Bucks --------------------------------- */
  /* **No es un estado, es una identidad.** Estuvo saliendo de `--aviso`, que
     es el otro dorado de la casa, y son dos cosas distintas: `--aviso` quiere
     decir "al límite" —el medidor de cuentas se tiñe de él justo al llegar al
     tope— y esto sólo dice "esto es dinero del juego". Compartir token hacía
     que un saldo sano se pareciese a una alarma.

     Se distinguen a la vista, que es lo que importa: éste es más claro y algo
     más cálido (L 60% frente a 47%, H 42,5° frente a 45,6°).

     Misma convención que los de estado: **el tono base rellena y el `-alto` es
     el que se lee**. El `-alto` entró con el Dashboard, que enseña saldos en
     texto, y está medido: 10,46:1 sobre `--superficie` y 8,75:1 sobre
     `--superficie-2`, muy por encima del 4,5:1 de AA.

     Ninguno de los dos bordea un control ni marca nada accionable: eso sigue
     siendo del teal. */
  --vbucks:        #E5B94E;
  --vbucks-alto:   #ECC66B;

  /* --- Identidad: la cuenta reservada ----------------------------------- */
  /* El dorado de la tarjeta dedicada y de su etiqueta. **Es el mismo oro que
     `--vbucks` y lleva otro nombre a propósito**, que es la misma decisión que
     ya se tomó con `--canje-oro` en la tienda: `--vbucks` quiere decir "esto es
     dinero del juego" y nada más, y una cuenta reservada no es dinero. Con el
     token de al lado, el alcance de esto dejaría de poder comprobarse en cuanto
     alguien buscara quién usa `--vbucks`.

     **No es un cálido nuevo.** Comparte hex con `--vbucks` y no abre un cuarto
     tono en los 37° que ya se reparten `--peligro`, `--energia`, `--vbucks` y
     `--aviso`, que es la parte más frágil de la paleta. Si el oro de la moneda
     se mueve, éste se mueve con él.

     Y como el resto: **rellena y bordea, el `-alto` es el que se lee**. No tiñe
     bordes de control, ni focos, ni botones —eso sigue siendo del morado— y no
     es un estado: no dice que nada vaya mal.

     Su alcance lo fija un test, no la buena fe: sólo lo usan las piezas de la
     cuenta dedicada. */
  --dedicada:      #E5B94E;
  --dedicada-alto: #ECC66B;

  /* --- Dos entradas del menú: Tienda Gift en oro, Historial en plata ------
     **Es una excepción a «lo accionable es morado», y está acotada.** Son
     las dos entradas que más se usan del menú —regalar y mirar lo regalado—
     y van marcadas para encontrarlas de un vistazo. Lo demás del menú sigue
     con sus tonos; el foco y la marca de sección siguen siendo los de todos.

     Tokens propios y no `--vbucks`: aquél dice «esto es dinero del juego» y
     nada más, y su alcance se perdería en cuanto alguien buscara quién lo usa.

     Los tres de cada uno, **de más oscuro a más claro**: el primero son los
     dos extremos del degradado, el segundo su centro y el color del icono, y
     el tercero el brillo que cruza el texto, bastante más claro que el centro
     para que se vea pasar. Oscuro, claro, oscuro: es lo que
     hace que se lea como metal y no como un color plano —con el oro del
     hex de los V-Bucks de un extremo a otro apenas se notaba, y la plata se
     confundía con el gris del menú—. El más oscuro llega a AA sobre lo más
     claro que puede tener detrás una entrada —el hover, `--superficie-2`—:
     5,21:1 el oro y 5,40:1 la plata. Lo fija `tests/test_paleta.py`, con el
     alcance.

     El oro ya no comparte hex con `--vbucks`, y mejor: es más hondo. */
  --nav-oro:          #C99A2E;
  --nav-oro-alto:     #E5B94E;
  --nav-oro-brillo:   #FFF6D8;
  --nav-plata:        #9FA4B4;
  --nav-plata-alto:   #C9CDD8;
  --nav-plata-brillo: #FFFFFF;

  /* --- Las medallas del Top compradores ----------------------------------
     Oro, plata y bronce de los tres primeros puestos de `/fn/` y del panel.
     **Fijos en los dos temas** y fuera del acento del vendedor, como el oro
     del canje: son la misma pista en las treinta tiendas. Rellenan un disco y
     el dibujo de la medalla va encima en `--medalla-tinta`, que es lo que se
     lee; el puesto va además en `sr-solo`. Lo fija `tests/test_paleta.py`,
     con su alcance: sólo `.fn-top-medalla*` y `.cl-top-medalla*`. */
  --medalla-oro:      #E5B94E;
  --medalla-plata:    #C9CDD8;
  --medalla-bronce:   #CD8A4E;
  --medalla-tinta:    #1A1622;

  /* --- Degradados ------------------------------------------------------ */
  /* Fondo de página: dos halos muy tenues, morado a la izquierda y magenta a
     la derecha. Se quedan detrás de todo y no compiten con nada. El de la
     derecha era el violeta secundario, que ya no existe; con el acento morado
     los dos halos salían del mismo tono y el fondo se quedaba plano. */
  --grad-fondo:
    radial-gradient(78rem 34rem at 8% -8%,
      color-mix(in srgb, var(--acento) 13%, transparent), transparent 70%),
    radial-gradient(62rem 30rem at 96% 0%,
      color-mix(in srgb, var(--energia) 10%, transparent), transparent 68%);

  /* Superficies: una caída de luz de arriba-izquierda a abajo-derecha. */
  --grad-superficie: linear-gradient(163deg,
    color-mix(in srgb, var(--superficie-2) 82%, transparent) 0%,
    transparent 58%);
  --grad-superficie-alta: linear-gradient(163deg,
    color-mix(in srgb, var(--superficie-3) 92%, transparent) 0%,
    color-mix(in srgb, var(--superficie-2) 40%, transparent) 62%);

  /* Acento: cae de la variante clara al morado del logo y se calienta sólo al
     final con el magenta. El cuerpo del degradado —y del botón— es el morado
     del logo, no la variante clara: es la que se reconoce.

     El texto encima va en --fondo y pasa AA contra los tres tramos: 6,86:1 en
     el claro, 4,61:1 en el morado del logo y 4,68:1 en la cola. El del medio
     es el más justo de toda la paleta, y marca el suelo: si el morado del logo
     se oscurece un punto, el texto del botón deja de pasar AA. */
  --grad-acento: linear-gradient(135deg,
    var(--acento) 0%,
    var(--acento-hondo) 55%,
    color-mix(in srgb, var(--acento-hondo) 62%, var(--energia)) 100%);

  /* --- Geometría y movimiento ------------------------------------------ */
  /* Radios: más redondos que en v2 (eran 8 y 6). Un panel de gestión con las
     esquinas casi vivas se lee como una hoja de cálculo; redondeando se
     acerca sin perder la densidad, que es lo que no se toca.

     Tres escalones y no uno solo, porque un radio único hace que un botón
     dentro de una tarjeta parezca del mismo tamaño que ella:
       --r-tarjeta  superficies grandes (tarjetas, cajón, panel de pedido)
       --r-control  botones, campos, tarjetas pequeñas de la rejilla
       --r-menudo   lo que va dentro de un control: fotos, avisos, pastillas
                    cuadradas. Redondearlo como el control lo desborda. */
  --r-tarjeta: 14px;
  --r-control: 10px;
  --r-menudo: 8px;
  --r-pastilla: 999px;

  /* Espaciado en múltiplos de 4, con un escalón nuevo (--e5) para el aire
     que faltaba entre 16 y 24: subir de 16 a 24 de golpe descuadra las cajas
     densas, y era justo el salto que hacía falta dar. */
  --e1: 4px;  --e2: 8px;  --e3: 12px;
  --e4: 16px; --e5: 20px; --e6: 24px; --e8: 32px;

  /* Microinteracciones: la MISMA curva que frena y nunca rebota. Las
     duraciones suben un punto respecto a v2 (eran 120/180): a 120ms el hover
     se siente seco, más como un parpadeo que como una respuesta. Siguen
     siendo tres y siguen siendo cortas; esto no es una animación. */
  --curva: cubic-bezier(0.2, 0.7, 0.3, 1);
  --t-rapida: 160ms var(--curva);
  --t-media:  240ms var(--curva);
  --t-lenta:  420ms var(--curva);
}

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

/* El desplegable de un <select> y las barras de desplazamiento las pinta el
   navegador, no esta hoja. Sin esto salen en claro sobre un panel oscuro. */
:root { color-scheme: dark; }

body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--fondo);
  background-image: var(--grad-fondo);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--texto);
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  /* Sin síntesis: un peso que falte se verá más flojo de la cuenta, pero
     nunca deformará la línea engordando los glifos. */
  font-synthesis-weight: none;
}

/* <b> y <strong> piden peso 700, y de IBM Plex Sans sólo se autoalojan
   400, 500 y 600. Sin una cara real de 700 el navegador se inventa la negrita
   engordando los glifos (faux bold): ensancha el texto sin que la maquetación
   lo tenga del todo en cuenta y acaba amontonándose con lo que viene detrás.
   600 ya es la negrita de este diseño (h1, h2, botones), así que se usa esa. */
b, strong { font-weight: 600; }

/* Righteous, la de rótulo. Sólo aquí y en la marca: no tiene más peso que el
   400 ni cursiva, así que en un párrafo o en una tabla se lee mal.

   Nada de `font-weight: 600`: esa cara no existe. Con `font-synthesis-weight:
   none` el navegador tampoco se la inventa, así que pedirla sólo serviría para
   que el título saliera más flojo de lo que se espera.

   Y sin apretar las letras: el -0.015em de Space Grotesk le venía bien a una
   grotesca estrecha; Righteous ya es ancha y redonda, y apretándola se toca. */
h1, h2, h3 {
  font-family: 'Righteous', 'IBM Plex Sans', system-ui, sans-serif;
  font-weight: 400;
  margin: 0;
  letter-spacing: 0;
  /* En crema, que es lo que separa un título del párrafo que va debajo: el
     cuerpo va en el neutro y esto es lo que marca. */
  color: var(--texto-alto);
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.125rem; }

/* Un título con su icono delante (hoy, «Códigos de regalo»): el icono en el
   crema del título —no es accionable, así que no va en morado— y centrado con
   la línea, que en Righteous no cae donde la de Plex. */
.titulo-con-icono {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
}
/* La caja a la medida de la letra: con los 20px de siempre, al lado de un h1
   de 1,5rem el dibujo se quedaba en un tercio de la altura del rótulo. */
.titulo-con-icono .icono { width: 1em; height: 1em; }

/* Jerarquía por superficie, borde y degradado. Sin sombras en ningún sitio. */
.tarjeta {
  position: relative;
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  padding: var(--e8) var(--e6);
}

/* Filo de luz en el borde superior: es lo que da el relieve que antes se
   pedía a una sombra. Un pixel, y sólo en las tarjetas de entrada. */
.tarjeta::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  border-radius: var(--r-tarjeta) var(--r-tarjeta) 0 0;
  background: linear-gradient(90deg,
    transparent, color-mix(in srgb, var(--acento-hondo) 55%, transparent), transparent);
}

.envoltorio { max-width: 880px; margin: 0 auto; padding: var(--e6) var(--e4); }
.centrado { min-height: 100vh; display: grid; place-items: center; padding: var(--e4); }
.pila { display: flex; flex-direction: column; gap: var(--e5); }
.pila-corta { display: flex; flex-direction: column; gap: var(--e3); }

.suave { color: var(--texto-suave); }
.tenue { color: var(--texto-tenue); }
.pequeno { font-size: 0.875rem; }

/* Monoespaciada con cifras de ancho fijo: IDs, códigos y V-Bucks. */
.mono, .pin-input {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* Cifras de ancho fijo en la MISMA familia que la frase que las rodea. La
   monoespaciada la reserva CLAUDE.md para ids, códigos y V-Bucks. */
.cifras { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }

/* El morado es el único acento: si algo es accionable, es morado. */
a { color: var(--acento); transition: color var(--t-rapida); }
a:hover { color: var(--acento-alto); }

/* ==========================================================================
   Botones y microinteracciones
   ========================================================================== */

.boton {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--e2);
  padding: 11px var(--e5);
  border: 1px solid transparent;
  border-radius: var(--r-control);
  background-color: var(--acento-hondo);
  background-image: var(--grad-acento);
  /* El degradado se pinta al doble de ancho para poder deslizarlo al pasar
     por encima: se mueve la luz, no el color. */
  background-size: 180% 100%;
  background-position: 0% 50%;
  /* Sobre teal el texto va en --fondo, no en crema: por contraste. */
  color: var(--fondo);
  font: inherit; font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: background-position var(--t-media), transform var(--t-rapida),
              border-color var(--t-rapida), color var(--t-rapida);
}
.boton:hover {
  background-position: 100% 50%;
  color: var(--fondo);
  transform: translateY(-1px);
}
.boton:active { transform: translateY(0); }

/* El botón que invita a entrar: el de mejorar plan. Lo que le da presencia es
   el relieve, no el movimiento —un filo de luz arriba y el borde teal claro—,
   así que **no anima nada por su cuenta**: sólo tiene el deslizamiento de luz
   que ya trae `.boton` al pasar por encima, y ése se va con
   `prefers-reduced-motion` como todos los demás.

   Un botón que late en bucle es un anuncio, y esta pantalla no lo es. */
.boton--llamada {
  padding: 13px var(--e6);
  border-color: color-mix(in srgb, var(--acento-alto) 60%, transparent);
}
.boton--llamada::after {
  content: "";
  position: absolute;
  inset: 1px 1px auto;
  height: 1px;
  border-radius: var(--r-control) var(--r-control) 0 0;
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--texto-alto) 55%, transparent), transparent);
}

/* El icono acompaña al verbo: se adelanta un pixel, nada más. */
.boton .icono { transition: transform var(--t-media); }
.boton:hover .icono { transform: translateX(2px); }

.boton--secundario {
  background-image: none;
  background-color: transparent;
  border-color: var(--borde-alto);
  color: var(--acento);
}
.boton--secundario:hover {
  background-color: var(--acento-tenue);
  border-color: var(--acento-hondo);
  color: var(--acento-alto);
}

.boton[disabled], .boton[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
  filter: saturate(0.4);
}

/* ==========================================================================
   Campos y foco
   ========================================================================== */

.campo {
  width: 100%;
  padding: 11px var(--e4);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto);
  font: inherit;
  transition: border-color var(--t-rapida), background-color var(--t-rapida);
}
/* Un campo de varias líneas. **Hereda todo lo del campo** y sólo cambia lo que
   un `<textarea>` trae distinto del navegador: la fuente por defecto es la
   monoespaciada del sistema —`font: inherit` ya lo arregla— y el
   redimensionado, que se deja **sólo en vertical**: a lo ancho, arrastrarlo
   desborda la tarjeta que lo contiene.

   `min-height` y no `height`: las filas las pone el `rows` del marcado, y esto
   es el suelo para que no se quede en una línea si alguien lo baja. */
.campo--area {
  min-height: 7rem;
  resize: vertical;
  line-height: 1.5;
}

/* Selector. La flecha es un icono nuestro y no la del sistema: `appearance:
   none` quita la del navegador, que no se puede teñir. Va `pointer-events:
   none` para que pulsar encima abra el desplegable igual.

   **`appearance` faltaba y salían dos flechas**: la nuestra encima de la del
   navegador, en los cinco desplegables del panel —los tres filtros del
   Historial y los dos de Amigos—. El comentario decía desde el principio que
   se quitaba, y la línea que la quitaba no estaba. `-webkit-` delante porque
   Safari sigue pidiéndolo.

   Y venían **dos** de la misma causa: había una segunda regla
   `.campo--selector` ochocientas líneas más abajo que volvía a declarar
   `padding-right`, con
   `--e2` en vez del hueco de la flecha. A igual especificidad gana la última,
   así que el sitio reservado para el icono se quedaba en 8px y éste caía encima
   de la última letra del texto. Las dos reglas son ahora ésta; lo único que
   traía la otra —el `max-width`— está aquí. */
.selector { position: relative; display: flex; align-items: center; }
.campo--selector {
  -webkit-appearance: none;
  appearance: none;
  width: auto;
  /* Un nombre de cuenta de Epic puede ser largo y el desplegable mide lo que
     su opción más ancha. */
  max-width: 16rem;
  /* El sitio de la flecha. **Éste es el hueco que no había**: sin él, el icono
     cae encima de la última letra del texto. */
  padding-right: calc(20px + var(--e4));
  cursor: pointer;
}
.selector .icono {
  position: absolute;
  right: var(--e3);
  color: var(--texto-tenue);
  pointer-events: none;
}
.selector:hover .icono { color: var(--texto-suave); }

/* Apilado bajo su etiqueta y emparejado con otro campo, el selector ocupa lo
   mismo que él: dos campos de la misma pareja con anchos distintos se leen
   como si uno estuviera a medio rellenar. El de Agregar sigue estrecho, que
   ahí va en una fila junto al buscador y no apilado. */
.selector--ancho { width: 100%; }
.selector--ancho .campo--selector { width: 100%; max-width: none; }

.campo::placeholder { color: var(--texto-tenue); }
.campo:hover { border-color: var(--borde-alto); }
/* El borde de foco va en `--acento` y no en el morado del logo: el campo
   enfocado se pinta de `--superficie-3`, y contra ese fondo el del logo se
   queda en 2,88:1, por debajo del 3:1 que pide un borde de control. `--acento`
   da 3,62:1 por dentro y 5,84:1 por fuera. */
.campo:focus { background-color: var(--superficie-3); border-color: var(--acento); }

/* Sólo lectura: se enseña un dato, no se pide. Baja un escalón de superficie y
   pierde el borde vivo para que no parezca que hay algo que escribir. Sigue
   siendo un `input` y no un `<p>` porque así se lee como el campo que
   acompaña, y `readonly` —a diferencia de `disabled`— no lo saca del recorrido
   del tabulador ni del árbol de accesibilidad. */
.campo[readonly] {
  background-color: var(--fondo);
  color: var(--texto-suave);
  cursor: default;
}
.campo[readonly]:hover { border-color: var(--borde); }
.campo[readonly]:focus { background-color: var(--fondo); border-color: var(--borde); }

/* --- Un icono dentro del campo ------------------------------------------
   Mismo montaje que `.selector`, que es el que ya hacía esto con su flecha: un
   envoltorio en relativo y el icono en absoluto. Se reaprovecha la idea y no la
   clase porque aquel también hace de caja de un `<select>`.

   El icono es **un adorno que informa**, no un control: va `aria-hidden` en el
   marcado y `pointer-events: none` aquí, así que pulsar encima enfoca el campo
   que hay debajo en vez de no hacer nada. */
.campo-caja { position: relative; display: block; }
.campo--con-afijo { padding-right: calc(20px + var(--e5)); }
.campo-afijo {
  position: absolute;
  right: var(--e4);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--texto-tenue);
  pointer-events: none;
}
/* Al escribir, el lápiz se despierta con el campo: es el mismo cambio de tono
   que hace la flecha del selector al pasar por encima. */
.campo-caja:focus-within .campo-afijo { color: var(--acento-alto); }

/* El campo que no se puede editar porque lo cierra el plan. **Es un enlace**, no
   un `input`: al pulsarlo se abre el diálogo que explica por qué está cerrado, y
   un input no navega a ninguna parte sin JavaScript.

   Se viste como el campo de sólo lectura —`--fondo`, un escalón por debajo de la
   tarjeta, y el borde en reposo— para que se lea como un dato que se enseña y no
   como algo que se pide. Lo que lo distingue de un `readonly` de verdad es que
   éste sí responde al puntero: el borde se aviva y el candado se aclara, porque
   aquí hay algo que pulsar. */
.campo-bloqueado {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  margin: 0;
  padding: 11px calc(20px + var(--e5)) 11px var(--e4);
  background-color: var(--fondo);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-suave);
}
/* **Aquí el candado sí recibe el puntero**, al contrario que el lápiz. En un
   campo que se escribe, `.campo-afijo` va con `pointer-events: none` para que
   pulsar el icono enfoque el `input` que hay debajo; en éste no hay input que
   enfocar, y lo que hace falta es justo lo contrario: que el puntero llegue al
   icono para que salga su `title`. Con `none`, el globo no aparecería nunca. */
.campo-bloqueado .campo-afijo { pointer-events: auto; }

/* --- La invitación a mejorar, al lado del campo cerrado -----------------
   El texto y su botón en una línea, y se apilan cuando no caben: en móvil, un
   botón al lado de una frase de dos líneas deja la frase en una columna de
   cuatro palabras. */
.codigo-invita {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e3);
}
.codigo-invita p { margin: 0; }

/* Apagado: no hay nada que escribir aquí todavía —falta conectar una cuenta— y
   se ve. Mismo escalón que el de sólo lectura; lo que cambia es el color del
   texto, porque el motivo no es "esto se mira" sino "esto no se puede usar".
   El porqué va escrito al lado: el gris por sí solo no lo dice. */
.campo:disabled {
  background-color: var(--fondo);
  color: var(--texto-tenue);
  cursor: not-allowed;
}
.campo:disabled:hover { border-color: var(--borde); }

/* Un valor que se enseña, no un campo que se escribe. Es un `<output>` y no un
   `<input readonly>` porque aquí dentro va el icono de V-Bucks, y en el
   `value` de un input no cabe una imagen. Se pinta igual que el campo de al
   lado —lee como su pareja—, un escalón más hondo y sin borde vivo. */
.campo-salida {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e2);
  background-color: var(--fondo);
  cursor: default;
}
.campo-salida:hover { border-color: var(--borde); }
.campo-salida .elegido {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
}
.campo-salida .elegido[hidden],
.campo-salida [data-elegido-pista][hidden] { display: none; }
.campo-salida .separa { color: var(--borde-alto); }

.pin-input {
  font-size: 1.75rem;
  letter-spacing: 0.4em;
  text-align: center;
  padding: var(--e3);
}

/* Foco de teclado siempre visible, en el acento. */
:focus-visible {
  /* Más grueso y más separado que en v2 (era 2px/2px). El foco tiene que
     ganarle al borde de la propia caja, que ahora es más redondo y más
     visible; con 2px pegados se confundían. Sigue en --acento-alto, que es el
     teal que pasa AA a partir de --superficie-2. */
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* ==========================================================================
   Avisos, notas y etiquetas
   ========================================================================== */

.aviso {
  padding: var(--e3);
  border: 1px solid var(--peligro);
  border-left-width: 3px;
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  color: var(--texto);
}

/* Ni error ni acierto: "esto ha cambiado y conviene mirarlo". Hoy sólo la
   usa el aviso de que la cuenta activa la ha cambiado otro dispositivo.

   Va en --aviso y no en --peligro, que es de lo que se tiñe `.aviso`: nada ha
   ido mal, hay un estado que pide atención. La palabra la lleva el propio
   texto, así que el color no es el único canal. */
.advertencia {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e3);
  margin-bottom: var(--e4);
  border: 1px solid var(--aviso);
  border-left-width: 3px;
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--texto);
}
.advertencia .icono { flex: none; color: var(--aviso-alto); }

/* La conexión caducada, en la barra lateral. Va en --aviso, que es el color
   de "esto pide atención": no ha fallado nada, hay que reconectar.

   **La línea de `[hidden]` es obligatoria y no sobra.** El aviso nace
   escondido con el atributo y lo enseña el reloj al llegar a cero; el
   `display: flex` de aquí abajo le gana al `display: none` que el navegador
   trae de serie para `[hidden]`, así que sin esta regla salía **siempre** —con
   la sesión viva y todo—, contradiciendo al contador de al lado. Es la misma
   lección que `.lista li[hidden]` y `.cargando[hidden]`. */
.sesion-caducada {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: var(--e2) 0 0;
  padding: var(--e2);
  border: 1px solid var(--aviso);
  border-radius: var(--r-menudo);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--texto);
  font-size: 0.8125rem;
}
.sesion-caducada[hidden] { display: none; }
.sesion-caducada .icono { flex: none; color: var(--aviso-alto); }
.sesion-caducada a { color: var(--acento); }

/* El aviso con icono delante: hoy, la línea roja del diálogo de pedido, que
   lleva candado igual que la verde lleva su check. Es un modificador y no una
   regla sobre `.aviso` porque ese cajón se usa suelto en media docena de
   sitios donde no hay ningún icono que alinear. */
.aviso--con-icono { display: flex; align-items: center; gap: var(--e2); }
.aviso--con-icono .icono { flex: none; color: var(--peligro-alto); }

.exito {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e3);
  margin-bottom: var(--e4);
  border: 1px solid var(--ok);
  border-left-width: 3px;
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  /* **Declara su color, igual que `.aviso`.** No es redundante: donde la caja
     cae dentro de algo que redefine la escala de texto —la tarjeta clara de la
     página pública— heredarlo significa heredar el `color` que se computó en
     `body`, o sea el del tema oscuro. Sin esta línea el texto salía en
     #DAD8E0 sobre una tarjeta blanca: 1,19:1. */
  color: var(--texto);
}
.exito .icono { color: var(--ok-alto); }

.nota {
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  background-image: var(--grad-superficie);
  color: var(--texto-suave);
  font-size: 0.875rem;
}

.etiqueta {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 2px var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-3);
  color: var(--texto-suave);
  font-size: 0.8125rem;
}
.etiqueta .icono { font-size: 16px; width: 16px; height: 16px; }
/* La de las 48 horas de amistad (`m.espera_48h_etiqueta`) no se parte:
   «Faltan 47 h 59 min» en dos líneas se lee como dos datos. Lo que cede en la
   fila es el nombre, que ya se parte por donde haga falta. */
.etiqueta--espera { white-space: nowrap; }
/* Su línea de `hidden`, porque **hay etiquetas que nacen escondidas**: la de
   «Cuenta nueva» de la lista de canjes de un código sale sólo cuando la cuenta
   nació en ese canje. El `inline-flex` de aquí arriba le ganaría al `display`
   del navegador (CLAUDE.md, Reglas de UI). */
.etiqueta[hidden] { display: none; }

/* Etiqueta de sólo icono: redonda y sin el hueco del texto. Lo que dice está
   en el `title` y en su `.sr-solo`, así que el icono no es el único canal.
   Se usa para lo que se repite en todas las tarjetas y siempre dice lo mismo;
   en cuanto la etiqueta informa de algo que hay que atender, vuelve el texto. */
.etiqueta--sola {
  gap: 0;
  padding: 2px;
  border-radius: 50%;
}
.etiqueta--ok {
  border-color: color-mix(in srgb, var(--ok) 55%, transparent);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  color: var(--ok-alto);
}
.etiqueta--aviso {
  border-color: color-mix(in srgb, var(--aviso) 55%, transparent);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--aviso-alto);
}

/* La cuenta reservada. **No es un estado**: no dice que pase nada, dice de
   quién es esa cuenta. De ahí que vaya en `--dedicada` y no en `--aviso`,
   aunque los dos sean dorados. El color no es el único canal: la etiqueta
   lleva siempre la palabra «Dedicada» y su estrella. */
.etiqueta--dedicada {
  border-color: color-mix(in srgb, var(--dedicada) 55%, transparent);
  background-color: color-mix(in srgb, var(--dedicada) 14%, transparent);
  color: var(--dedicada-alto);
}

/* ==========================================================================
   Contador del plan: una línea de texto y un medidor fino debajo
   ========================================================================== */

/* Texto corrido, en el cuerpo de siempre. La cifra sólo fija el ancho del
   dígito (.cifras), sin cambiar de familia ni de tamaño: es un dato de la
   cabecera, no un titular. */
.contador {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e2);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.9375rem;
}

/* El recuento de cuentas se tiñe: verde mientras quede sitio y aviso al llegar
   al tope del plan. Los tonos `-alto` porque esto ES texto, y `--ok` y
   `--aviso` a secas no pasan AA sobre el fondo (CLAUDE.md). El color no es el
   único canal: la cifra ya dice cuántas quedan y debajo está el medidor. */
.contador-cifras { color: var(--ok-alto); }
.contador-cifras strong { color: inherit; font-weight: 600; }
.contador-cifras--lleno { color: var(--aviso-alto); }

/* El plan, en su pastilla: es una etiqueta de estado, no parte de la frase. */
.contador > strong { color: var(--texto); font-weight: 600; }

.pastilla-plan {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 2px var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  font-size: 0.8125rem;
}

/* Las pastillas del contador, en grupo: en estrecho bajan juntas al segundo
   renglón y la frase «N de M cuentas» se queda entera en el primero. */
.contador-pastillas {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
}

/* La pastilla que **se pulsa**: comprar cuentas extra. Misma forma que las
   informativas y otro trabajo, así que va en el acento —lo único accionable
   es morado— y con el texto en `--acento-alto`, que es el que se lee sobre
   `--acento-tenue`. Apagada (LITE), vuelve al neutro y no reacciona. */
.pastilla-accion {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 2px var(--e3);
  border: 1px solid var(--acento-hondo);
  border-radius: var(--r-pastilla);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
  font-size: 0.8125rem;
  font-weight: 500;
  text-decoration: none;
  transition: border-color var(--t-rapida), background-color var(--t-rapida);
}
.pastilla-accion .icono { width: 16px; height: 16px; }
a.pastilla-accion:hover { border-color: var(--acento-alto); }
a.pastilla-accion:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.pastilla-accion[aria-disabled="true"] {
  border-color: var(--borde-alto);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  cursor: not-allowed;
}

/* La cara del plan, a la altura de la letra de la pastilla: 16px de caja, que
   es lo que miden los iconos de las etiquetas (`.etiqueta .icono`). */
.pastilla-plan .icono { width: 16px; height: 16px; }

/* Medidor de consumo del plan. El ancho del relleno lo pone el servidor en
   línea. Va debajo del contador y no se estira más que la frase que explica. */
.medidor {
  width: 100%;
  max-width: 200px;
  height: 4px;
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-3);
  border: 1px solid var(--borde);
  overflow: hidden;
}
.medidor-relleno {
  display: block;
  height: 100%;
  border-radius: inherit;
  background-image: var(--grad-acento);
  transition: width var(--t-lenta);
}
/* Al llegar al límite deja de ser informativo y pasa a avisar. */
.medidor-relleno--lleno {
  background-image: linear-gradient(135deg, var(--aviso-alto), var(--aviso));
}
/* Con cuentas extra, **dos tramos**: el del plan y el de las extra, cada uno
   del ancho de lo que da (`flex-grow` en línea, que lo pone el servidor) y
   separados por un hueco. Mismo color en los dos: el dato de las extra no
   necesita color propio, lo distingue el corte. */
.medidor--tramos {
  display: flex;
  gap: 2px;
  background-color: transparent;
  border: 0;
  overflow: visible;
}
.medidor-tramo {
  flex-basis: 0;
  min-width: 4px;
  height: 100%;
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-3);
  border: 1px solid var(--borde);
  overflow: hidden;
}
/* Un trabajo que terminó sin acabarlo todo. Mismo aviso, nombre distinto: el
   de arriba dice "no caben más cuentas" y éste "esto se quedó a medias". */
.medidor-relleno--parado {
  background-image: linear-gradient(135deg, var(--aviso-alto), var(--aviso));
}

/* ==========================================================================
   Listas y estados vacíos
   ========================================================================== */

.lista { list-style: none; margin: 0; padding: 0; }
.lista li {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--e3);
  padding: var(--e4) 0;
  border-bottom: 1px solid var(--borde);
}
.lista li:last-child { border-bottom: 0; }

/* El buscador del navegador esconde las filas poniéndoles `hidden`, y el
   `display: flex` de aquí arriba le gana al `display: none` que el navegador
   trae de serie para `[hidden]`. Sin esto el filtro corría, pintaba el
   "ninguno coincide" y las filas seguían a la vista: parecía que no filtraba
   hasta pulsar Buscar, que es cuando filtra el servidor. */
.lista li[hidden] { display: none; }

/* Estado vacío: una acción, no una ilustración. */
.vacio {
  padding: var(--e8) var(--e4);
  text-align: center;
  border: 1px dashed var(--borde-alto);
  border-radius: var(--r-tarjeta);
  background-image: radial-gradient(60% 100% at 50% 0%,
    color-mix(in srgb, var(--acento) 8%, transparent), transparent 70%);
}
.vacio p { margin: 0 0 var(--e4); color: var(--texto-suave); }

/* La caja de todo icono: 20px, a `currentColor`. **`inline-block` y no el
   `inline` de un `<svg>`**: era lo que ponía `iconos.css` a la ligadura de
   Material, y los de Tabler la heredaban; al irse aquella hoja se queda aquí,
   para que ningún icono cambie de caja ni de línea base. */
.icono {
  display: inline-block;
  width: 20px;
  height: 20px;
  fill: currentColor;
  flex: none;
}

/* **La tinta de un icono, a la de Material.** El glifo de Material Symbols
   pintaba unos 13px de tinta en su caja de 20 —medido en la barra lateral: de
   12 a 14 según el dibujo— y un símbolo de Tabler a la misma caja pinta 17,
   porque su rejilla deja menos margen. Al pasar el panel a Tabler, todos los
   iconos habrían crecido un tercio de golpe.

   Se encoge **el dibujo y no la caja**: `scale` no toca la maquetación, así que
   la alineación, los huecos y el ancho de cada botón se quedan exactamente
   donde estaban. Y es la propiedad `scale` y no `transform: scale()` a
   propósito: es independiente de `transform`, así que el icono del botón que
   se adelanta 2px al pasar por encima sigue haciéndolo.

   0,76 = 13/17. Los que ya eran Tabler antes —la tienda pública, la portada,
   `/comprar`, `/api/docs`— conservan su tamaño con `icono--lleno`. */
.icono--tabler { scale: 0.76; }
.icono--lleno { scale: none; }

/* --- Iconos dibujados a mano (SVG inline) --------------------------------
   Los de fuente son la norma; éstos existen sólo donde el icono tiene que
   moverse **por dentro**, que con una ligadura no se puede: un glifo no tiene
   partes que animar por separado. Ver `_macros.html`.

   Van con `.icono` puesto para heredar la caja de 20px y sus recortes
   (`.boton--menudo .icono` los baja a 18), y este bloque va DESPUÉS a
   propósito: misma especificidad, así que es el orden el que deja el trazo
   ganando al `fill` de arriba. Geometría de 24 y trazo 2, como lucide. */
.icono--traza {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Los de Tabler, que vienen de un sprite y no pegados en la página.

   Lo que traen los símbolos ya es `fill: none` y `stroke: currentColor`, así
   que aquí sólo hay que asegurar que **el `<svg>` de fuera no pinte nada**: sin
   esto, el `stroke-width: 2` de `.icono--traza` se aplicaría al hueco y en
   algún navegador se ve un filo. El color lo hereda el símbolo del `color` de
   este nodo, que es el del texto de al lado. */
.icono--tabler { stroke: none; }

/* El robot de "Abre el bot" parpadea al pasar por encima.

   Los dos ojos son un solo `path` con dos trazos, y comparten centro vertical,
   así que un `transform-origin` en y=14 vale para los dos: cerrar el ojo es
   aplastarlos en Y contra su propio centro. `transform-box: view-box` va
   escrito aunque sea el valor inicial, porque de él dependen esos 14 —son
   unidades del `viewBox`, no píxeles de pantalla—.

   Se anima `transform`, que es de lo barato. Dos parpadeos y para: uno solo se
   confunde con un parpadeo de la pantalla y en bucle sería un anuncio. Dura
   `--t-lenta`, que es la única de las tres duraciones en la que un ojo se cierra
   y se abre y se ve: a 160ms el ojo está cerrado 20ms y no llega a leerse. */
.icono--bot .bot-ojos {
  transform-box: view-box;
  transform-origin: 12px 14px;
}
.boton:hover .icono--bot .bot-ojos,
.boton:focus-visible .icono--bot .bot-ojos {
  animation: bot-parpadeo var(--t-lenta);
}

@keyframes bot-parpadeo {
  0%,  20% { transform: scaleY(1); }
  32%, 44% { transform: scaleY(0.08); }
  56%, 68% { transform: scaleY(1); }
  76%, 84% { transform: scaleY(0.08); }
  96%      { transform: scaleY(1); }
}

.barra {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--e3);
  padding-bottom: var(--e4);
  border-bottom: 1px solid var(--borde);
  margin-bottom: var(--e6);
}

@media (max-width: 380px) {
  .envoltorio { padding: var(--e4) var(--e3); }
  .tarjeta { padding: var(--e4); }
  .barra { flex-direction: column; align-items: flex-start; }
  .lista li { flex-direction: column; align-items: flex-start; gap: var(--e2); }
  .acciones--fila { width: 100%; }
  .pin-input { font-size: 1.375rem; letter-spacing: 0.3em; }
}

/* ==========================================================================
   Acceso: entrar con el PIN y crearlo
   ==========================================================================
   Las dos pantallas comparten marco (`_acceso-marco.html`): imagen de fondo,
   velo, resplandor y una tarjeta estrecha encima. Son la única parte del panel
   con una foto detrás, y es a propósito: aquí no se gestiona nada, sólo se
   teclean seis dígitos, así que puede permitirse ser una portada. */

.acceso {
  position: relative;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--e5) var(--e4);
  overflow: hidden;
}

/* La imagen, quieta y en su propia capa. Lo que se mueve es la lluvia de
   encima, así que ésta no necesita ni escalado ni margen de sobra. */
.acceso-fondo {
  position: absolute;
  inset: 0;
  /* La de la casa va en variable **para poder quedarse de segunda capa** bajo
     la del vendedor: el marco escribe `url(la suya), var(--fondo-casa)` y, si
     la suya no carga, el navegador pinta ésta. El respaldo lo hace el CSS y no
     hay que salir a comprobar nada desde el servidor. */
  background-image: var(--fondo-casa);
  background-size: cover;
  background-position: center;
}
.acceso { --fondo-casa: url('/static/img/limgo-wlpp-accs.jpg'); }

/* --- La lluvia -----------------------------------------------------------
   **Tres elementos, y de cada uno salen decenas de gotas.** El truco es que
   cada capa no es una gota sino un patrón que se repite: el fondo lleva unas
   pocas gotas dibujadas con `radial-gradient` dentro de una baldosa de N
   píxeles, `background-repeat` la cubre entera y la animación baja la capa
   **exactamente una baldosa**. Al terminar, el dibujo coincide con el de
   partida, así que el bucle no se ve y no hace falta reponer nada.

   Se hace así y no con un nodo por gota por lo que cuesta: cuarenta divs
   animados son cuarenta cajas que el navegador tiene que mantener, y esto es
   una pantalla donde sólo se teclea un PIN, a menudo en un móvil. Con tres
   capas el compositor mueve tres texturas ya pintadas y la CPU no hace nada.
   Por lo mismo se anima **sólo `transform`**: ni `top`, que reharía la
   maquetación, ni `background-position`, que repinta en cada fotograma.

   Las tres van a velocidades distintas (26, 38 y 54 s) y con baldosas de
   tamaños que no son múltiplos entre sí: así no se sincronizan y no aparece la
   cuadrícula. La de delante es la más grande, la más opaca y la más rápida; la
   del fondo, al revés. Eso es toda la profundidad que hace falta.

   Los retardos son **negativos** para que al cargar la página la lluvia ya
   esté cayendo: empezando en cero, las tres capas arrancarían alineadas arriba
   y se vería el patrón. */
.acceso-lluvia {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.acceso-gotas {
  position: absolute;
  left: 0;
  right: 0;
  /* Una baldosa de más por arriba: es el hueco por el que entra lo que va
     bajando, y sin él se vería aparecer la primera fila de la nada. */
  top: calc(var(--baldosa) * -1);
  bottom: 0;
  background-repeat: repeat;
  animation: acceso-caer var(--caida) linear infinite;
  will-change: transform;
}

/* La baldosa lleva tres gotas en sitios que no se alinean entre sí. Son
   elipses estiradas —1,5 de ancho por 7 de alto— porque una gota redonda
   cayendo se lee como una mota de polvo, no como lluvia. */
.acceso-gotas--1 {
  --baldosa: 160px;
  --caida: 26s;
  animation-delay: -7s;
  opacity: 0.55;
  background-size: var(--baldosa) var(--baldosa);
  background-image:
    radial-gradient(1.5px 7px at 18% 12%, var(--acento-alto), transparent),
    radial-gradient(1.5px 7px at 63% 47%, var(--acento-alto), transparent),
    radial-gradient(1.5px 7px at 87% 78%, var(--acento), transparent);
}
.acceso-gotas--2 {
  --baldosa: 230px;
  --caida: 38s;
  animation-delay: -19s;
  opacity: 0.38;
  background-size: var(--baldosa) var(--baldosa);
  background-image:
    radial-gradient(1px 6px at 34% 22%, var(--acento-alto), transparent),
    radial-gradient(1px 6px at 71% 61%, var(--acento), transparent),
    radial-gradient(1px 6px at 9% 88%, var(--acento), transparent);
}
.acceso-gotas--3 {
  --baldosa: 310px;
  --caida: 54s;
  animation-delay: -33s;
  opacity: 0.24;
  background-size: var(--baldosa) var(--baldosa);
  background-image:
    radial-gradient(1px 5px at 52% 8%, var(--acento), transparent),
    radial-gradient(1px 5px at 24% 55%, var(--acento), transparent),
    radial-gradient(1px 5px at 90% 33%, var(--acento-alto), transparent);
}

/* Baja una baldosa justa y vuelve a empezar. Como el patrón se repite, el
   fotograma final es idéntico al primero y el salto no existe. */
@keyframes acceso-caer {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, var(--baldosa), 0); }
}

/* El velo, en otra capa para poder oscurecer el centro más que los bordes sin
   tocar la imagen.

   El 0,72 del centro **está medido y no elegido a ojo**: la zona donde cae la
   tarjeta es la parte clara de la foto —L* medio 58, con blancos puros
   dentro— y ese velo deja el peor caso, el blanco puro, en L* 34. La tarjeta
   (`--superficie`, L* 11) queda ahí a 2,06:1, que es **más separación que la
   que hay entre dos escalones cualesquiera de la escalera de superficies**
   (1,17 a 1,31). O sea: se lee como una placa, no como una mancha.

   Hacia fuera baja a 0,42 para que la foto siga viva en los bordes. Y ojo con
   subirlo: por encima de 0,85 en el centro la tarjeta se funde con el fondo,
   porque cuanto más oscuro el velo MENOS destaca una tarjeta que ya es oscura.
   Si se toca, se rehace la cuenta. */
.acceso-velo {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    120% 90% at 50% 48%,
    color-mix(in srgb, var(--fondo) 72%, transparent) 0%,
    color-mix(in srgb, var(--fondo) 62%, transparent) 45%,
    color-mix(in srgb, var(--fondo) 42%, transparent) 100%
  );
}

.acceso-centro {
  position: relative;
  width: 100%;
  max-width: 340px;
  display: grid;
  place-items: center;
}

/* El mismo recurso que la pieza de la portada: un borrón morado detrás que
   despega la tarjeta del fondo sin necesidad de una sombra —que además está
   prohibida—. Aquí el color no es de rareza sino el de la casa. */
.acceso-brillo {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 115%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background-image: var(--grad-acento);
  filter: blur(64px);
  opacity: 0.35;
  pointer-events: none;
}

/* Compacta: aquí se teclean seis dígitos y se pulsa un botón. El `--e5` de
   `.pila` dejaba una tarjeta con más aire que contenido. */
.acceso-tarjeta {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  padding: var(--e6) var(--e5);
  /* **Vuelve a declarar el color, y es lo que hace que el tema claro exista de
     verdad.** El bloque `[data-tema="claro"]` redefine los tokens, pero el
     `color: var(--texto)` que pinta el cuerpo está declarado en `body`: allí
     `var(--texto)` ya se resolvió con el valor del tema oscuro, y lo que baja
     por herencia es ese color ya calculado, no la variable. Sin esta línea,
     cualquier texto de dentro que no traiga `color` propio sale en el gris
     claro del panel sobre una tarjeta blanca. Le pasaba a `.exito`. */
  color: var(--texto);
}
.acceso-tarjeta h1 { font-size: 1.375rem; }
.acceso-marca { font-size: 1.25rem; }
.acceso-marca .marca-logo { width: 32px; height: 32px; }
.acceso-entrada { margin: 0; }
/* La letra pequeña, al final y separada del formulario: es una nota, no un
   paso más. */
.acceso-ayuda { margin: 0; color: var(--texto-tenue); }

.acceso-form {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
.acceso-form .boton { margin-top: var(--e2); }

/* --- Las seis casillas ---------------------------------------------------
   **El marcado que manda el servidor es UN campo**, con su `name`, su
   `pattern` y su `required`. Las casillas las monta el guion encima si hay
   JavaScript, y entonces esconde el campo de verdad y le escribe dentro lo que
   se teclea. Sin JavaScript no hay casillas y queda el campo de siempre, que
   funciona. Es la misma regla del resto del panel: lo que llega del servidor
   funciona, y el guion sólo lo mejora. */
.pin-casillas {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--e2);
}

.pin-casilla {
  width: 100%;
  min-width: 0;
  padding: var(--e3) 0;
  text-align: center;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 1.375rem;
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-alto);
  transition: border-color var(--t-rapida), background-color var(--t-rapida);
}
.pin-casilla:focus {
  background-color: var(--superficie-3);
  border-color: var(--acento);
  outline: none;
}
/* Una casilla con dígito se marca también con el borde, no sólo con el punto:
   de un vistazo se ve por dónde va el PIN sin tener que leerlo. */
.pin-casilla[data-lleno] { border-color: var(--borde-alto); }

/* El campo de verdad, una vez montadas las casillas. **No se le pone
   `hidden`** y el matiz importa: un campo escondido que además es obligatorio
   deja al navegador sin nada que enfocar al validar, y el envío se queda
   parado sin decir por qué. El guion le quita el `required` al esconderlo y se
   lo pasa a las casillas, que sí se ven. */
.pin-input[data-sustituido] { display: none; }

@media (max-width: 380px) {
  .acceso { padding: var(--e4) var(--e3); }
  .acceso-tarjeta { padding: var(--e5) var(--e4); }
  .pin-casillas { gap: var(--e1); }
  .pin-casilla { font-size: 1.125rem; padding: var(--e2) 0; }
}

/* En pantalla ancha la tarjeta crece. A 340px se quedaba flotando en medio de
   una foto de 1920 como una nota pegada, y lo que se teclea aquí —seis dígitos
   grandes— pedía sitio.

   Crece **hasta 420 y ahí se planta**: pasado ese ancho, seis casillas y un
   botón dejan de leerse como un formulario corto y empiezan a parecer una
   pantalla de trámite. Lo que llena el espacio es el tamaño de lo de dentro,
   no el ancho de la caja.

   Va en `min-width` y no tocando la regla base: el móvil se queda exactamente
   como estaba, que es donde ya cuadraba. */
@media (min-width: 600px) {
  .acceso-centro { max-width: 420px; }
  .acceso-tarjeta { gap: var(--e5); padding: var(--e8) var(--e6); }
  .acceso-tarjeta h1 { font-size: 1.75rem; }
  .acceso-marca { font-size: 1.5rem; }
  .acceso-marca .marca-logo { width: 40px; height: 40px; }
  .acceso-entrada, .acceso-form label { font-size: 0.9375rem; }

  /* 420 de tarjeta menos 24+24 de relleno y cinco huecos de 8 son 52 por
     casilla: un dígito de 28px cabe con aire de sobra. */
  .pin-casilla { font-size: 1.75rem; padding: var(--e4) 0; }

  /* El botón, del ancho de las casillas y con la altura de lo que remata una
     pantalla, no de un botón de fila. */
  .acceso-form .boton {
    width: 100%;
    padding: 14px var(--e5);
    font-size: 1.0625rem;
  }
}

/* Quieta. `prefers-reduced-motion` se respeta de verdad (CLAUDE.md): se para
   la lluvia y se queda donde esté. No se esconde: las gotas quietas siguen
   siendo parte del fondo, y lo que molesta es el movimiento, no el punto. */
@media (prefers-reduced-motion: reduce) {
  .acceso-gotas { animation: none; }
}

/* ==========================================================================
   Portada pública (/)
   ==========================================================================
   La única página del sitio que ve alguien que no ha entrado. No comparte
   maquetación con el panel —no hay barra lateral ni cabecera de sección— pero
   sí la paleta, las tres familias y el repertorio de botones: es la misma
   casa vista desde fuera. */

.portada {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--e8);
  padding: var(--e5) var(--e6) var(--e8);
  max-width: 1080px;
  margin: 0 auto;
}

/* Marca a la izquierda y la única acción a la derecha. Sin navegación: no hay
   más páginas públicas a las que ir. */
.portada-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e4);
  padding-bottom: var(--e4);
  border-bottom: 1px solid var(--borde);
}
.portada-alto .marca { font-size: 1.5rem; }
.portada-alto .marca-logo { width: 32px; height: 32px; }

/* Entre secciones, el doble del paso más grande: cada una se abre con su
   separador, y una línea de lado a lado pegada a lo de encima se lee como el
   borde de lo anterior, no como el principio de lo siguiente. */
.portada-cuerpo {
  display: flex;
  flex-direction: column;
  gap: calc(var(--e8) * 2);
  min-width: 0;
}

/* --- El separador de sección -------------------------------------------
   Una línea fina de lado a lado y, en el centro, una píldora con el icono y
   el nombre. **Es el propio `h2`**: las dos medias líneas son su `::before` y
   su `::after`, y la píldora es el texto. Así el título sigue siendo un
   título y no hay un `<hr>` de adorno al lado.

   La línea es `--borde`, el filo de una superficie: separa, no se pulsa. La
   píldora va un escalón arriba (`--superficie-2`), con el texto en crema
   —es un título— y el icono en el morado de reposo, el mismo que numera los
   pasos: marca, no invita a pulsar. */
.portada-seccion { display: flex; flex-direction: column; gap: var(--e5); }
.portada-separador {
  display: flex;
  align-items: center;
  gap: var(--e4);
  margin: 0;
  font-size: 1.125rem;
}
.portada-separador::before,
.portada-separador::after {
  content: "";
  flex: 1 1 0;
  height: 1px;
  background-color: var(--borde);
}
.portada-separador-pildora {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  flex: none;
  padding: var(--e2) var(--e4);
  border: 1px solid var(--borde);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
}
.portada-separador-pildora .icono { color: var(--acento); }

/* --- Presentación: texto y pieza ---------------------------------------- */

/* Dos columnas, y la del texto manda: es lo que hay que leer. La pieza ocupa
   lo que sobre y no baja de 260, que es donde una figura deja de reconocerse.
   `align-items: center` para que la figura quede a la altura del párrafo y no
   colgando del título. */
.portada-presenta {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(260px, 2fr);
  align-items: center;
  gap: var(--e8);
}

.portada-texto {
  display: flex;
  flex-direction: column;
  gap: var(--e5);
  min-width: 0;
}

/* El titular. Es el único sitio del panel donde Righteous sale a este tamaño,
   y aun así se queda en `clamp`: a 380px un 2,5rem parte la primera palabra.
   `text-wrap: balance` reparte las líneas en vez de dejar una suelta abajo. */
.portada-texto h1 {
  font-size: clamp(1.75rem, 1.1rem + 2.6vw, 2.75rem);
  line-height: 1.15;
  text-wrap: balance;
}
/* En mayúsculas en pantalla y sólo en pantalla: el marcado va en minúsculas
   para que un lector de pantalla lo lea como una frase. */
.portada-titular { text-transform: uppercase; }
/* Cada tramo de oro en un renglón: partido, «SIN» al final de una línea y
   «ENTRAR» al principio de la siguiente se leían como dos cosas, y el
   degradado de cada mitad empezaba de cero. */
.portada-oro { white-space: nowrap; }

/* Un escalón por encima del cuerpo normal, y en `--texto-suave`: el titular ya
   se lleva el crema, y dos cremas seguidos no dejan ver cuál manda. */
.portada-entradilla {
  margin: 0;
  max-width: 52ch;
  font-size: 1.0625rem;
  color: var(--texto-suave);
}

.portada-acciones { display: flex; flex-wrap: wrap; gap: var(--e3); }

/* --- La pieza del día ---------------------------------------------------- */

/* Sin tarjeta, sin borde y sin fondo: lo que la separa del fondo es el
   resplandor de detrás. Es la excepción al "todo va en una superficie" y es a
   propósito — una figura recortada flotando dice "esto se regala" mucho mejor
   que la misma figura dentro de una caja. */
.portada-pieza {
  position: relative;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e3);
  min-width: 0;
}

/* El resplandor. **No es una sombra y no puede serlo**: la sombra de caja está
   prohibida en todo el panel y hay un test que lo vigila —que además no admite
   ni el nombre de la propiedad escrito en un comentario, así que ni eso—. Es un
   `div` vacío detrás de la imagen, con el degradado de la rareza, redondeado y
   desenfocado.

   El desenfoque es enorme a propósito (56px sobre una caja del 70%): lo que
   tiene que quedar es un halo de color sin forma reconocible, no un círculo
   con los bordes suaves. Y al 55% de opacidad, porque a plena potencia el
   color se come el fondo y la figura pierde el recorte.

   Se le quita el filtro con `prefers-reduced-motion`? No: no se mueve. Un
   desenfoque estático no marea a nadie. */
.portada-brillo {
  position: absolute;
  left: 50%;
  top: 45%;
  width: 70%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* Sin clase de rareza —una oferta sin rareza conocida— el halo va al morado
     del panel, que es el color de la casa. Lo pisa `.brillo--<rareza>`. */
  background-image: var(--grad-acento);
  filter: blur(56px);
  opacity: 0.55;
  /* Detrás de la imagen y fuera del alcance del ratón: no es contenido. */
  z-index: 0;
  pointer-events: none;
}

/* Por encima del halo. Sin fondo ni recorte: llega con transparencia y así se
   queda, que es lo que la hace flotar. */
.portada-render {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 380px;
  aspect-ratio: 1;
  object-fit: contain;
  display: block;
}

.portada-pie {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e1);
  text-align: center;
  min-width: 0;
}
/* El nombre en crema y el precio debajo: la misma jerarquía que en la tarjeta
   de tienda, hecha con el tamaño y no con el color. */
.portada-nombre {
  margin: 0;
  font-weight: 600;
  color: var(--texto-alto);
  overflow-wrap: anywhere;
}
.portada-precio {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--e2);
  color: var(--texto-alto);
}

/* --- Cómo funciona ------------------------------------------------------- */

.portada-pasos { display: flex; flex-direction: column; gap: var(--e5); }

/* Tres pasos en fila y unidos por flechas; apilados en móvil, con la flecha
   hacia abajo. Numerados por el navegador —son un `<ol>`— y la cifra va en su
   sitio, delante del título de cada uno.

   **Tres columnas o una, nunca dos**: era `auto-fit`, y entre el móvil y el
   escritorio salían dos arriba y una abajo, con una flecha apuntando a nada.
   El hueco es `--e8` porque ahí dentro va la flecha. */
.pasos-portada {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: paso-portada;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--e8);
}
@media (max-width: 860px) {
  .pasos-portada { grid-template-columns: minmax(0, 1fr); }
}

/* La flecha, centrada en el hueco que sigue a la tarjeta. Va dentro del `li`
   y se saca con `position: absolute`: un hijo más del `<ol>` sería un paso
   más para el contador y para un lector de pantalla. Las dos van en el
   marcado y se enseña la que toca. En `--texto-suave`, que es lo que llega a
   3:1 como icono sobre el fondo, y no en morado: no se pulsa. */
.paso-flecha {
  position: absolute;
  display: flex;
  color: var(--texto-suave);
  top: 50%;
  right: calc(var(--e8) / -2);
  transform: translate(50%, -50%);
}
.paso-flecha .icono:last-child { display: none; }
@media (max-width: 860px) {
  .paso-flecha {
    top: auto;
    right: auto;
    bottom: calc(var(--e8) / -2);
    left: 50%;
    transform: translate(-50%, 50%);
  }
  .paso-flecha .icono:first-child { display: none; }
  .paso-flecha .icono:last-child { display: inline-block; }
}
.pasos-portada li {
  position: relative;
  counter-increment: paso-portada;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding: var(--e5);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  min-width: 0;
}
.pasos-portada h3 {
  display: flex;
  align-items: baseline;
  gap: var(--e2);
  font-size: 1.0625rem;
}
/* La cifra del paso, delante del título. Va en el morado de reposo y no en el
   de acción: numera, no invita a pulsar. */
.pasos-portada h3::before {
  content: counter(paso-portada);
  flex: none;
  color: var(--acento);
  font-variant-numeric: tabular-nums;
}
.pasos-portada p { margin: 0; }

/* --- La documentación de la API (`/api/docs`) ----------------------------
   El marco de la portada con una columna de lectura. **Sin un adorno de más**:
   es un documento que se consulta, no una página que vende. */
.api-cuerpo { gap: var(--e6); max-width: 860px; }
.api-bloque {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  min-width: 0;
}
.api-bloque h2 {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding-top: var(--e4);
  border-top: 1px solid var(--borde);
}
.api-bloque h3 { font-size: 1rem; margin-top: var(--e2); }
.api-bloque p { margin: 0; }

/* **Righteous, sólo en la marca y en el `h1`.** Los títulos de sección van
   en la letra del texto en seminegrita: esto es un documento que se consulta,
   y una tipografía de rótulo en cada `h2` lo convertía en un cartel. Siguen
   en crema, que es lo que los separa del párrafo. */
.api-docs h2,
.api-docs h3 {
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  font-weight: 600;
}
.api-docs h2 { font-size: 1.1875rem; }

/* La ficha de la cabecera: dos columnas, rótulo y dato. En el móvil, el
   rótulo encima del dato: en dos columnas a 390 px la URL se partía en
   trozos de cuatro letras. */
.api-ficha {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--e3) var(--e5);
  margin: 0;
  padding: var(--e4) var(--e5);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}
.api-ficha div { display: contents; }
.api-ficha dt { font-size: 0.875rem; color: var(--texto-suave); }
/* El dato fluye como texto, no como fila flex: con `gap`, «openapi.json» y
   la coma de detrás quedaban separadas. */
.api-ficha dd { margin: 0; min-width: 0; }
@media (max-width: 560px) {
  .api-ficha { grid-template-columns: minmax(0, 1fr); gap: var(--e1); }
  .api-ficha div + div dt { margin-top: var(--e3); }
}
/* La dirección base: entera, y de un toque se selecciona entera
   (`user-select: all`). **Se parte por donde haga falta** en vez de
   cortarse: es una URL, y cortada no se podría copiar. */
.api-url {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.875rem;
  color: var(--texto-alto);
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}
.api-plan {
  display: flex;
  align-items: center;
  gap: var(--e2);
  color: var(--texto-alto);
}
.api-plan .icono { color: var(--acento); }

/* Cada ruta, una tarjeta. El `h2` es la firma —`POST` y la ruta— y no
   lleva la raya de encima de los demás bloques: la tarjeta ya separa. */
.api-ruta {
  padding: var(--e5);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}
.api-ruta > h2.api-firma { padding-top: 0; border-top: 0; }
.api-frase { color: var(--texto); }

/* `POST /api/v2/…`: el método en su pastilla y la ruta en monoespaciada. */
.api-firma { display: flex; flex-wrap: wrap; align-items: center; gap: var(--e2); }
.api-firma code {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  color: var(--texto-alto);
  overflow-wrap: anywhere;
}

/* El ejemplo, plegado. Un `<details>`: se abre sin JavaScript, que la página
   no admite. El `summary` va en el morado que se lee —se pulsa— y su flecha
   gira al abrir. */
.api-ejemplo > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  list-style: none;
  cursor: pointer;
  color: var(--acento);
  font-weight: 500;
}
.api-ejemplo > summary::-webkit-details-marker { display: none; }
.api-ejemplo > summary:hover { color: var(--acento-alto); }
.api-ejemplo > summary:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
  border-radius: var(--r-menudo);
}
.api-ejemplo > summary .icono { transition: transform var(--t-rapida); }
.api-ejemplo[open] > summary .icono { transform: rotate(180deg); }
/* Margen y no `gap`: el contenido de un `<details>` va en una ranura propia
   y el `display: flex` del `<details>` no lo reparte en todos los
   navegadores. */
.api-ejemplo .api-codigo { margin-top: var(--e3); }
@media (prefers-reduced-motion: reduce) {
  .api-ejemplo > summary .icono { transition: none; }
}
.api-metodo {
  padding: 2px var(--e2);
  border: 1px solid var(--acento-hondo);
  border-radius: var(--r-pastilla);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
  font-size: 0.8125rem;
  font-weight: 600;
}

/* Un bloque de código: se desplaza **él** en horizontal, nunca la página. */
.api-codigo {
  margin: 0;
  padding: var(--e3) var(--e4);
  overflow-x: auto;
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--texto);
}

/* Las tablas, igual: en el móvil se desplaza el marco y no la página. */
.api-tabla-marco { overflow-x: auto; min-width: 0; }
.api-tabla {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}
.api-tabla th,
.api-tabla td {
  padding: var(--e2) var(--e3);
  border-bottom: 1px solid var(--borde);
  text-align: left;
  vertical-align: top;
}
.api-tabla th { font-size: 0.8125rem; font-weight: 600; color: var(--texto-tenue); }
.api-tabla td:first-child { white-space: nowrap; }

.api-lista { margin: 0; padding-left: var(--e5); display: grid; gap: var(--e2); }

/* Los errores de una ruta: una fila de claves que llevan a su fila de la
   tabla. */
.api-errores {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}
.api-errores li { display: inline-flex; align-items: center; gap: var(--e1); }
.api-errores .cifras { font-size: 0.8125rem; color: var(--texto-suave); }

/* El código de una licencia comprada en la landing, en su página de estado: lo
   único grande de la página, con su «Copiar» al lado. */
.licencia-codigo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  padding: var(--e3) var(--e4);
  border: 1px solid var(--acento-hondo);
  border-radius: var(--r-control);
  background-color: var(--acento-tenue);
}
.licencia-codigo-texto {
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  color: var(--texto-alto);
  overflow-wrap: anywhere;
}
.licencia-pasos { margin: 0; padding-left: var(--e5); display: grid; gap: var(--e1); }
/* Un botón o un título con lo que lo explica pegado debajo: el `gap` de la
   tarjeta los separaría como si fueran dos cosas. */
.licencia-bloque { display: flex; flex-direction: column; gap: var(--e2); }
.licencia-bloque p { margin: 0; }

/* La sección «Planes» de la portada: las fichas del diálogo de mejorar plan
   (`.planes-rejilla`, `.plan-oferta--N`), con su precio, su botón al pie y el
   selector de duración arriba, que sólo aparece con JavaScript. */
/* El recuadro grande que la envuelve: una superficie, con las fichas un
   escalón más arriba (`.plan-oferta` va en `--superficie-2`). */
.portada-planes {
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  padding: var(--e6);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}
@media (max-width: 480px) {
  .portada-planes { padding: var(--e4); }
  /* Apiladas, las tres fichas suman una treintena de líneas: en el móvil se
     aprietan el hueco entre ellas y el relleno, no la letra. */
  .plan-web.plan-oferta { padding: var(--e3) var(--e4); gap: var(--e2); }
  .plan-web .ventajas { gap: var(--e1); }
}
/* Sólo el selector de duración, a la derecha: el título ya es el separador
   de encima del recuadro. */
.portada-planes-alto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--e3);
}
.portada-planes-alto:not(:has(:not([hidden]))) { display: none; }
.planes-duracion { margin-top: 0; }
.planes-duracion[hidden] { display: none; }
.ck-segmento-opciones[hidden] { display: none; }
.plan-web--destacado { border-color: var(--acento-hondo); }
.plan-web-precio {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  font-size: 1.125rem;
}
.plan-web-precio .cifras { color: var(--texto-alto); font-weight: 600; }
.plan-web-descuento[hidden] { display: none; }
.plan-web-form {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  margin-top: auto;
}
.plan-web-checkout { display: flex; align-items: center; gap: var(--e2); cursor: pointer; }
.plan-web-checkout input { accent-color: var(--acento-hondo); }
.plan-web-boton { margin-top: auto; justify-content: center; }
.plan-web-form .plan-web-boton { margin-top: 0; }

/* --- El pie --------------------------------------------------------------
   Los enlaces arriba y el aviso legal debajo, separados de lo de encima por la
   misma línea fina que abre las secciones. */
.portada-final {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  margin-top: var(--e8);
  padding-top: var(--e6);
  border-top: 1px solid var(--borde);
}
.portada-enlaces {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2) var(--e6);
}
/* Son enlaces: van en el morado que se lee, y el icono con ellos. */
.portada-enlaces a {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  color: var(--acento);
  font-weight: 500;
  text-decoration: none;
}
.portada-enlaces a:hover { color: var(--acento-alto); text-decoration: underline; }

/* **El aviso legal: pequeño, pero que se lea.** 0,8125rem —el tamaño de los
   metadatos del panel— y en `--texto-suave`, que llega a AA sobre las cuatro
   superficies y sobre el fondo; `--texto-tenue` también llegaría sobre el
   fondo, pero aquí no se baja de lo que se lee de verdad. Lo comparten la
   portada y `/comprar` (`_aviso-legal.html`). */
.aviso-legal {
  margin: 0;
  max-width: 72ch;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--texto-suave);
}

@media (max-width: 860px) {
  /* Apiladas: el texto primero, que es lo que explica qué es esto. La pieza
     baja debajo y se centra sola. */
  .portada-presenta {
    grid-template-columns: 1fr;
    gap: var(--e6);
  }
  .portada { padding: var(--e4) var(--e4) var(--e6); gap: var(--e6); }
  .portada-cuerpo { gap: calc(var(--e8) + var(--e4)); }
}

@media (max-width: 480px) {
  .portada-alto .marca { font-size: 1.25rem; }
  /* De lado a lado: en un móvil estrecho dos botones a medias no se aciertan. */
  .portada-acciones .boton { width: 100%; justify-content: center; }
  /* El halo se encoge con la figura, o se desborda de la pantalla. */
  .portada-brillo { filter: blur(40px); }
}

/* ==========================================================================
   Panel de cuentas: barra lateral + rejilla de tarjetas
   ========================================================================== */

/* **256px, y la cuenta es de los rótulos del menú.** Con 232 se partían
   cuatro —«Código de creador», «Global Friends Link», «Global Store Link» en
   tres líneas, con su chevron, y «Verificador de pagos»—. El más ancho es
   «Verificador de pagos» en negrita, que es como va cuando es la sección
   activa: 132px; y Global Store Link necesita 146 entre rótulo, hueco y
   chevron. Con 256 a cada rótulo le quedan 152 y al de Global Store Link 156.
   Medido con la IBM Plex Sans de verdad a 15px.

   Se eligió esto y no la letra más pequeña —a 14px «Global Friends Link»
   seguía sin caber— ni recortar los nombres. Lo vigila
   `test_ningun_rotulo_del_menu_se_parte`. */
.disposicion {
  display: grid;
  grid-template-columns: 256px 1fr;
  min-height: 100vh;
}

/* --- Barra lateral ------------------------------------------------------ */
.lateral {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--e6);
  padding: var(--e6) var(--e5);
  /* **La barra se desplaza por dentro, y sin esto el final no se alcanza.**

     Es `sticky` con `height: 100vh`, así que se queda clavada mientras la
     página se desplaza: lo que no cabe en la ventana **no llega nunca**, porque
     el scroll de la página mueve el contenido de al lado y no el suyo. Medido
     en una ventana de 900px de alto: la barra mide 1.361px en Global Store
     Link y en Clientes —son las dos que enseñan «Clientes» colgando—, 1.299 en
     Códigos, CONF CHECKOUT y Global Friends Link, y 1.106 en el Historial. En
     las seis, «Cerrar sesión» cae fuera de la pantalla.

     **No es de una página**: es de todas, y se nota antes en las más altas. Y
     no era un `overflow: hidden` que se dejara un diálogo — `body` y `html`
     están en `visible` en las seis.

     **Este mismo fallo ya se arregló abajo, para el cajón de móvil** (ver
     `.con-js .lateral` en el bloque de 860px, con su comentario): allí el
     síntoma era idéntico —«Cerrar sesión» sin alcanzar en ningún tamaño— y la
     columna de escritorio se quedó sin el arreglo. La lección es la de aquel
     comentario, al revés: una regla pensada para el móvil que no sube al
     escritorio.

     `contain` para que al llegar al final no siga desplazándose la página
     detrás, que convierte un gesto en dos cosas. */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  background-color: var(--fondo);
  background-image: linear-gradient(196deg,
    color-mix(in srgb, var(--acento-tenue) 55%, transparent) 0%,
    transparent 42%);
  /* La barra y la página comparten el negro del fondo, así que el borde es lo
     único que las separa: por eso va en `--borde-alto` (3,63:1 contra los dos,
     frente a 1,87:1 del `--borde`) y a 2px. Un pelo de `--borde` desaparecía. */
  border-right: 2px solid var(--borde-alto);
}

/* Marca y versión en una sola línea, alineadas por la base: la versión es un
   apéndice del nombre, no un segundo renglón. */
.lateral-marca {
  display: flex;
  align-items: baseline;
  gap: var(--e2);
  min-width: 0;
}

.marca {
  display: flex;
  align-items: center;
  gap: var(--e2);
  font-family: 'Righteous', system-ui, sans-serif;
  font-weight: 400;
  font-size: 1.0625rem;
  letter-spacing: 0.01em;
  color: var(--texto-alto);
}
.marca .icono { color: var(--acento); }

/* El logo de LIMGO es una imagen de marca, como el icono de V-Bucks: no se
   tiñe ni se sustituye por un glifo. El dibujo no es cuadrado y se encaja
   dentro de una caja cuadrada con `contain`, que es lo que mantiene cuadrada
   la sangría de la versión. */
.marca-logo {
  display: block;
  width: 20px;
  height: 20px;
  flex: none;
  object-fit: contain;
}

/* La marca de la barra va al doble que la de una tarjeta de entrada: es el
   nombre del panel, no un antetítulo. Se dobla aquí y no en `.marca` porque
   ese mismo bloque lo usan las pantallas de acceso, donde acompaña al título
   y no manda. */
.lateral-marca .marca { font-size: 2.125rem; }
.lateral-marca .marca-logo { width: 32px; height: 32px; }

/* La versión, pegada detrás del nombre y en tono apagado: es un dato de
   contexto, no parte de la marca. Sin sangría, porque ya no cuelga de nada. */
.version {
  font-size: 0.8125rem;
  color: var(--texto-tenue);
}

/* --- Dashboard ----------------------------------------------------------
   La fila de cuatro cifras de la cuenta conectada. Son cajas pequeñas y no
   tarjetas de métrica: la cifra va **dentro de la frase que la explica** y en
   el mismo cuerpo de texto que la rodea (CLAUDE.md, Cifras). Nada de números
   gigantes con un rótulo colgando debajo — eso era la v2 y se quitó porque
   ocupaba una pantalla para decir cuatro datos. */
.cifras-fila {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--e3);
}

.cifra-caja {
  position: relative;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e1);
  padding: var(--e3) var(--e4);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  /* El filo de color va a la izquierda, donde empieza a leerse la frase, y es
     el propio borde de la caja: ningún elemento nuevo. Va **después** del
     `border`, que si no lo pisaría. */
  border-left: 3px solid var(--borde-alto);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  font-size: 0.875rem;
}
/* La cifra sí manda dentro de la frase, y lo hace con el color y el peso, no
   con el tamaño: así no descuadra la línea. */
.cifra-caja .cifras, .cifra-caja .mono { color: var(--texto-alto); font-weight: 600; }

/* Cada cifra con el token que significa lo que es. Ninguno de estos colores
   marca nada accionable: son texto y un filo, no controles.
     - oro     lo que es dinero del juego;
     - verde   lo que queda disponible, y ámbar cuando se acabó;
     - y el de apoyo no lleva color: se queda en el filo neutro y baja de crema
       a `--texto`, que ya es un escalón menos de peso. */
.cifra-caja--vbucks { border-left-color: var(--vbucks); }
.cifra-caja--vbucks .mono { color: var(--vbucks-alto); }

.cifra-caja--slots { border-left-color: var(--ok); }
.cifra-caja--slots .cifras { color: var(--ok-alto); }
/* Sin slots no es un adorno rojo: es la única de las cuatro que cambia lo que
   se puede hacer hoy. El texto de al lado sigue diciendo "0 de 5". */
.cifra-caja--apurado { border-left-color: var(--aviso); }
.cifra-caja--apurado .cifras { color: var(--aviso-alto); }

/* El dato de apoyo: sin color propio. Mantiene el filo neutro de `.cifra-caja`
   y su cifra baja de `--texto-alto` a `--texto` —11,81:1 contra los 15,88:1 de
   las otras—, que es lo que la pone medio escalón por detrás sin inventarle un
   tono. */
.cifra-caja--amigos .cifras { color: var(--texto); }

/* --- Estadísticas SAC: cuatro cifras en tarjeta -------------------------
   **Es la excepción a "nada de tarjetas de métrica"**, y está acotada a este
   bloque (CLAUDE.md, Cifras). El porqué: en `.cifra-caja`, justo arriba, la
   cifra va dentro de la frase que la explica, y eso funciona porque cada una
   explica algo distinto. Aquí el bloque **entero** es un resumen de cifras y no
   hace nada más, así que la frase que las envolvería sería la misma cuatro
   veces y el rótulo dice lo mismo en tres palabras. En el resto del panel sigue
   prohibido, y sigue prohibida la cifra gigante: 1,375rem es un escalón por
   encima del cuerpo, no un cartel.

   Lo que no cambia es el vocabulario de color de su vecina: el filo de la
   izquierda lleva el token base y la cifra su variante `-alto`, y ninguno de los
   dos tiñe nada accionable. */
.sac-cifras {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--e3);
}

.sac-cifra {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding: var(--e3) var(--e4);
  background-color: var(--superficie-2);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  /* Como en `.cifra-caja`: el filo es el propio borde y va **después** del
     `border`, que si no lo pisaría. */
  border-left: 3px solid var(--borde-alto);
  border-radius: var(--r-control);
}

/* El rótulo **acompaña** al dato, no encabeza un bloque, así que se queda en
   `--texto-tenue` y no en crema (CLAUDE.md, Paleta). Llega a AA sobre
   `--superficie-2`, que es justo donde se apoya. Mismo tamaño que `.ficha dt`,
   que hace este mismo trabajo dos bloques más arriba. */
.sac-rotulo { font-size: 0.75rem; color: var(--texto-tenue); }

.sac-dato {
  margin: 0;  /* el `dd` del navegador viene con sangría */
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e1);
  font-size: 1.375rem;
  font-weight: 600;
  color: var(--texto-alto);
}
/* De IBM Plex Mono sólo hay 400 y 500. Con `font-synthesis-weight: none` pedir
   600 no da un 600: da el 500 y encima disimula que no existe. Se pide el que
   hay. */
.sac-dato .mono { font-weight: 500; }

/* La unidad no es la cifra: detrás, pequeña y apagada. Es el mismo trabajo que
   hace el icono de los V-Bucks —acompañar sin enterrar el número—. */
.sac-unidad { font-size: 0.875rem; font-weight: 400; color: var(--texto-tenue); }

/* Cada dato con el token que significa lo que es:
     - oro     los V-Bucks, que son dinero del juego y no un estado;
     - crema   los dos resultados del mes, que es el token de las cifras;
     - y la tasa **no es un resultado**: es el dato de contexto con el que se
       calculan los otros dos, así que se queda con el filo neutro, baja de
       crema a `--texto` y pierde un escalón de tamaño. Igual que
       `.cifra-caja--amigos`: un dato de apoyo no necesita color propio.

   **El ámbar de energía no entra aquí, y no es un olvido.** `--energia` y
   `--vbucks` no coinciden dentro de una misma pieza (CLAUDE.md, Paleta), y esta
   fila es una pieza: al lado del oro, a la misma altura y al mismo tamaño, es
   exactamente la confusión de reojo que esa regla evita. El USD destaca por ser
   crema sobre un rótulo tenue, no por un tono propio. */
.sac-cifra--vbucks { border-left-color: var(--vbucks); }
.sac-cifra--vbucks .mono { color: var(--vbucks-alto); }

.sac-cifra--tasa .sac-dato {
  color: var(--texto);
  font-size: 1.125rem;
  font-weight: 500;
}

/* Dos columnas, no una: cuatro cajas de una línea apiladas dejan el bloque
   larguísimo para decir cuatro números. Es el mismo criterio que la rejilla de
   cuentas, que también se queda en dos. */
@media (max-width: 720px) {
  .sac-cifras { grid-template-columns: repeat(2, 1fr); }
}

/* Los meses cerrados, debajo de las cifras del mes en curso. **Es la única
   tabla del panel**, y lo es porque aquí sí hay lo que pide una tabla: cuatro
   columnas que se comparan entre filas. Las cifras del mes en curso no lo son
   —son cuatro datos distintos, no cuatro medidas de lo mismo— y por eso son
   cajas y no una fila más de ésta.

   Sin caja ni escalón de superficie: se apoya en la `--superficie` de la
   tarjeta y lo que la separa de las cifras de arriba es el filo de la cabecera.
   Un tercer nivel de recuadro dentro del mismo bloque sería una caja dentro de
   una caja dentro de una tarjeta. */
.sac-meses,
.compras {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

/* Los rótulos de columna: el mismo trabajo y el mismo tono que `.sac-rotulo`
   —acompañan a un dato, no encabezan un bloque—, y en minúsculas, que es como
   está escrito el resto del panel. */
.sac-meses th,
.compras th {
  font-weight: 500;
  font-size: 0.75rem;
  color: var(--texto-tenue);
  text-align: right;
  padding: var(--e2) 0 var(--e2) var(--e3);
  border-bottom: 1px solid var(--borde);
  white-space: nowrap;
}

/* El mes es la cabecera de su fila: a la izquierda, y en el color del cuerpo,
   porque es lo que se lee primero para saber de qué fila se habla. */
.sac-meses tbody th,
.compras tbody th {
  text-align: left;
  padding-left: 0;
  color: var(--texto);
  border-bottom-color: transparent;
}

.sac-meses td,
.compras td {
  padding: var(--e2) 0 var(--e2) var(--e3);
  /* A la derecha y con `tabular-nums` en la cifra: así las unidades quedan una
     debajo de otra y dos meses se comparan de un vistazo, que es para lo que
     está la tabla. */
  text-align: right;
  color: var(--texto-alto);
  white-space: nowrap;
}

/* El icono de los V-Bucks es `display: block` en todo el panel, porque en
   todos los demás sitios vive dentro de una caja flexible. Aquí va en el flujo
   de texto de una celda, así que se pasa a `inline-block` o se lleva la cifra
   a la línea siguiente. El desplazamiento vertical lo centra con los números,
   que no llegan tan abajo como la caja del icono. */
.sac-meses .vbuck { display: inline-block; vertical-align: -3px; }
.sac-meses .mono { color: var(--vbucks-alto); }

/* La tabla de compras reales es la MISMA tabla que la de meses —tres columnas
   comparables, rótulo arriba una vez— así que comparte todas las reglas de
   arriba en vez de copiarlas. Lo único suyo es esto:

   el nombre de un producto de Epic puede ser largo ("C7 Season 4 Battle Pass
   Gift Token") y no es una cifra, así que parte de línea en vez de empujar la
   tabla a lo ancho. Los números de las otras dos columnas siguen sin partirse,
   que es lo que las mantiene alineadas. */
.compras tbody th { white-space: normal; }

/* La última fila no necesita filo: no separa de nada. */
.sac-meses tbody tr:last-child th,
.sac-meses tbody tr:last-child td,
.compras tbody tr:last-child th,
.compras tbody tr:last-child td { border-bottom: 0; }

@media (max-width: 380px) {
  /* Cuatro columnas de números no caben en 380 sin apretar los rótulos hasta
     partirlos. Se cede el aire lateral, que es lo único de aquí que no informa. */
  .sac-meses th, .sac-meses td,
  .compras th, .compras td { padding-left: var(--e2); }
}

/* Los dos bloques de la cuenta, lado a lado. `auto-fit` con un mínimo los
   apila solos en cuanto no caben: una consulta de medios más sería otra cifra
   que mantener a mano. `align-items: start` para que el más corto no se estire
   al alto del otro. */
.tablero-dos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  align-items: start;
  gap: var(--e5);
}

/* Los títulos de sección del Dashboard, con una marca teal delante. Es la
   `--r-pastilla` que CLAUDE.md reserva para las marcas de sección, y va sólo
   aquí: el resto de pantallas no la piden.

   El teal marca, no invita a pulsar: es un pseudoelemento de 3px sin foco, sin
   hover y sin nada dentro. */
.tablero h2 {
  display: flex;
  align-items: center;
  gap: var(--e2);
  font-size: 1.25rem;
}
.tablero h2::before {
  content: "";
  flex: none;
  width: 3px;
  height: 1.1em;
  border-radius: var(--r-pastilla);
  background-image: var(--grad-acento);
}

/* La ficha del bloque "Estado de la cuenta": rótulo encima, valor debajo. En
   dos columnas, un nombre de cuenta largo se parte contra un rótulo de una
   línea. */
.ficha {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  margin: 0;
}
.ficha dt { font-size: 0.75rem; color: var(--texto-tenue); }
.ficha dd {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--e1);
  overflow-wrap: anywhere;
}

/* La tarjeta «Tu plan»: lo que tiene a la izquierda y las acciones a la
   derecha, que se apilan debajo en cuanto no caben. Las acciones van en
   columna: una principal y dos secundarias, del mismo ancho, para que no se
   amontonen en una fila que se parte por donde caiga. */
.plan-columnas {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--e4) var(--e6);
}
.plan-columnas > .tu-plan-datos { flex: 1 1 280px; }
.plan-acciones {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  flex: 0 1 240px;
}
.plan-acciones .boton { justify-content: center; }
/* El aviso del plan vencido o a punto de vencer: arriba, antes que nada. */
.tu-plan-aviso { margin: 0; display: flex; align-items: flex-start; gap: var(--e2); }
@media (max-width: 560px) {
  /* En el móvil las acciones ocupan el ancho entero, debajo de los datos. */
  .plan-acciones { flex-basis: 100%; }
}

/* Lo que tiene ahora: el vencimiento, el Checkout y las cuentas, en frases y
   con su medidor debajo cuando la cifra tiene techo. */
.tu-plan-datos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e3) var(--e6);
}
.tu-plan-datos .medidor { margin-top: var(--e1); }
/* Una frase con su icono delante. El texto en el cuerpo y la cifra en crema
   (CLAUDE.md, Cifras); en ámbar sólo cuando apura, y con la frase diciéndolo. */
.tu-plan-dato {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  color: var(--texto);
}
.tu-plan-dato .cifras { color: var(--texto-alto); }
.tu-plan-dato--apura,
.tu-plan-dato--apura .cifras { color: var(--aviso-alto); }

/* El diálogo de comprar el plan: las opciones son radios con su texto al
   lado, en una lista; la elegida se reconoce por el radio y por el borde. */
.compra-opciones {
  display: grid;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  border: 0;
}
.compra-opciones legend { margin-bottom: var(--e2); font-weight: 500; }
.compra-opcion {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  cursor: pointer;
}
.compra-opcion:hover { border-color: var(--borde-alto); }
.compra-opcion:has(input:checked) { border-color: var(--acento); }
.compra-opcion input { accent-color: var(--acento-hondo); }
.compra-opcion-precio { margin-left: auto; color: var(--texto-alto); white-space: nowrap; }
/* `/comprar` por pasos: tres bloques en un formulario. Cada paso lleva su
   número delante del título, en el morado de reposo como los de la portada:
   numera, no invita a pulsar. */
.compra-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  align-self: flex-start;
}
.compra-pasos { display: flex; flex-direction: column; gap: var(--e6); }
.compra-paso {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
.compra-paso[hidden] { display: none; }
.compra-paso-titulo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: 0;
  margin-bottom: var(--e1);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--texto-alto);
}
.compra-paso-n { color: var(--acento); font-variant-numeric: tabular-nums; }
/* El plan elegido, marcado en claro: borde de acento y sus beneficios
   desplegados; el otro, plegado a una línea. */
.compra-plan {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  padding: var(--e3) var(--e4);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  cursor: pointer;
}
.compra-plan:hover { border-color: var(--acento-alto); }
.compra-plan:has(input:checked) { border: 2px solid var(--acento); padding: calc(var(--e3) - 1px) calc(var(--e4) - 1px); }
.compra-plan-alto { display: flex; align-items: center; gap: var(--e3); }
.compra-plan-alto input { accent-color: var(--acento-hondo); }
.compra-plan-nombre { font-weight: 600; color: var(--texto-alto); }
.ventajas.compra-plan-ventajas { display: none; }
.compra-plan:has(input:checked) .ventajas.compra-plan-ventajas { display: flex; }
.compra-ahorro[hidden] { display: none; }
.compra-checkout[hidden] { display: none; }
.compra-siguiente[hidden] { display: none; }
/* --- `/comprar` en ancho: dos columnas -----------------------------------
   La tarjeta crece sólo aquí (`acceso-centro--ancho`, que pone la plantilla
   en el marco): las de acceso se quedan en sus 420. A 900 caben las dos
   columnas sin apretar las ventajas del plan; por debajo, una sola, como en
   el móvil. */
@media (min-width: 600px) {
  .acceso-centro.acceso-centro--ancho { max-width: 560px; }
}
@media (min-width: 900px) {
  .acceso-centro.acceso-centro--ancho { max-width: 1040px; }
  .compra-columnas {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--e8);
    align-items: start;
  }
  /* El resumen, a la vista al bajar. La columna se estira a la altura de la
     fila para que tenga por dónde deslizarse cuando el plan es más largo, y
     lleva `top` **y** `bottom`: se pega arriba al bajar por un plan largo, y
     abajo cuando lo largo es la propia columna —la duración con los
     beneficios del Checkout— en una ventana baja. */
  /* `.acceso` recorta con `overflow: hidden`, y eso lo convierte en el
     contenedor de scroll del `sticky`, que entonces no se pega a nada.
     `clip` recorta igual sin serlo. Sólo aquí: las de acceso siguen igual. */
  .acceso:has(.acceso-centro--ancho) { overflow: clip; }
  .compra-lado { align-self: stretch; }
  .compra-resumen {
    position: sticky;
    top: var(--e4);
    bottom: var(--e4);
  }
}
.compra-columnas { display: grid; gap: var(--e6); }
.compra-lado { display: flex; flex-direction: column; gap: var(--e6); min-width: 0; }

/* El resumen de la derecha: lo elegido y el total, con «Siguiente» debajo. Un
   escalón de superficie por encima de la tarjeta, como los campos. */
.compra-resumen {
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  padding: var(--e4);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.compra-total {
  padding-top: var(--e2);
  border-top: 1px solid var(--borde);
}
.compra-total dt { color: var(--texto-alto); }
.compra-comision { margin: calc(-1 * var(--e2)) 0 0; }
.compra-resumen [data-resumen-checkout][hidden] { display: none; }
.compra-siguiente { width: 100%; justify-content: center; }

/* El paso 3. Con `showModal()` es el diálogo de siempre (`dialog.cajon`).
   **Sin él** —sin JavaScript, o un navegador que no lo sepa abrir— se pinta
   como una sección más debajo de las columnas: la clase la pone el guion sólo
   cuando el diálogo de verdad existe. Gana a `dialog.cajon:not([open])` por
   especificidad, que es lo que lo dejaría escondido. */
html:not(.compra-con-dialogo) dialog.compra-dialogo {
  position: static;
  display: flex;
  inset: auto;
  width: auto;
  max-height: none;
  margin: var(--e6) 0 0;
  padding: var(--e5);
  animation: none;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
}
.compra-linea {
  font-weight: 600;
  color: var(--texto-alto);
}
.compra-pasarelas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--e2);
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
/* Cada pasarela, una tarjeta con su precio final: marcada como el plan
   elegido, con el filo de acento a 2px. */
.compra-pasarela {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  /* El precio, al pie: con una tarjeta de dos renglones al lado de otra de
     uno, los dos precios quedan a la misma altura. */
  align-content: space-between;
  gap: var(--e1) var(--e2);
  padding: var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  cursor: pointer;
}
.compra-pasarela:hover { border-color: var(--acento-alto); }
.compra-pasarela:has(input:checked) {
  border: 2px solid var(--acento);
  padding: calc(var(--e3) - 1px);
}
.compra-pasarela input { accent-color: var(--acento-hondo); margin: 0; }
.compra-pasarela-icono { display: inline-flex; color: var(--texto-alto); }
.compra-pasarela-texto { display: flex; flex-direction: column; min-width: 0; }
.compra-pasarela-nombre { font-weight: 600; color: var(--texto-alto); }
.compra-pasarela .compra-opcion-precio {
  grid-column: 1 / -1;
  margin: var(--e1) 0 0;
  font-size: 1.0625rem;
  font-weight: 600;
}
.compra-codigo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  font-size: 0.875rem;
  color: var(--texto-suave);
}
.compra-codigo .icono { flex: none; color: var(--acento); }
.compra-pagar { width: 100%; justify-content: center; }

/* El aviso legal de `/comprar`: una frase centrada y el resto en «Leer más»,
   un `<details>` sin JavaScript. Mismo tamaño y tono que el pie de la portada
   (`.aviso-legal`); el «Leer más» es lo único que se pulsa, y va en el acento. */
.aviso-legal--plegable {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e1);
  max-width: none;
  text-align: center;
}
.aviso-legal--plegable .aviso-legal { margin-inline: auto; }
.aviso-legal-mas summary {
  display: inline-block;
  font-size: 0.8125rem;
  color: var(--acento);
  cursor: pointer;
  list-style: none;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.aviso-legal-mas summary::-webkit-details-marker { display: none; }
.aviso-legal-mas summary:hover { color: var(--acento-alto); }
.aviso-legal-mas summary:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
  border-radius: var(--r-menudo);
}
.aviso-legal-mas[open] summary { margin-bottom: var(--e2); }

/* Una sola pasarela: se dice cuál, sin casilla que marcar. */
.compra-opcion--unica { margin: 0; cursor: default; border-color: var(--acento); }
.compra-opcion--unica:hover { border-color: var(--acento); }

/* «+ Checkout»: sus beneficios se ven **siempre**, atenuados mientras la
   casilla está sin marcar y con el color y el check verde del resto al
   marcarla (`:has()`, sin JavaScript). El color no es el único canal: lo dicen
   la casilla y el precio del botón, y la lista está enlazada a la casilla con
   `aria-describedby`.

   Van en su propia caja de `--superficie-2`: el tenue sobre la ficha DELUX
   (18% de energía) se queda en 4,08:1, y sobre esta superficie pasa AA. Con
   `.ventajas` delante: esa regla va después y, con la misma especificidad, le
   ganaría. */
.ck-desplegable { display: grid; gap: var(--e2); }
.ventajas.ck-beneficios {
  padding: var(--e2) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
}
.ventajas.ck-beneficios li,
.ventajas.ck-beneficios .icono {
  color: var(--texto-tenue);
  transition: color var(--t-rapida);
}
.ck-desplegable:has(input:checked) .ventajas.ck-beneficios li { color: var(--texto-suave); }
.ck-desplegable:has(input:checked) .ventajas.ck-beneficios .icono { color: var(--ok-alto); }
.compra-frases {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--e2);
}
.compra-frases li { display: flex; gap: var(--e2); align-items: flex-start; }
.compra-desglose { margin: 0; display: grid; gap: var(--e1); }
.compra-desglose div { display: flex; justify-content: space-between; gap: var(--e4); }
.compra-desglose dt { color: var(--texto-tenue); }
.compra-desglose dd { margin: 0; white-space: nowrap; }

/* Las ventajas, con su marca verde delante. Es una lista de lo que hay, no de
   lo que falta: nunca lleva cruces ni ventajas tachadas de otro plan. */
.ventajas {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  min-width: 0;
}
.ventajas li {
  display: flex;
  align-items: center;
  gap: var(--e2);
  color: var(--texto-suave);
}
.ventajas .icono {
  color: var(--ok-alto);
  font-size: 16px; width: 16px; height: 16px;
  flex: none;
}

/* Los tres planes, en columna y a la vez. Se apilan solos cuando no caben:
   `auto-fit` con un mínimo, sin una consulta de medios que mantener aparte. */
.planes-rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  align-items: stretch;
  gap: var(--e4);
}

/* La ficha de un plan. Espacio de sobra para más ventajas: la lista crece
   hacia abajo sin tocar nada, y `align-items: stretch` mantiene las tres del
   mismo alto aunque una tenga más. */
.plan-oferta {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  padding: var(--e4);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie-2);
}

/* El degradado de cada nivel, con los tokens de siempre y en el mismo eje de
   163° que el resto de superficies: es **luz, no color**, y no desplaza el
   tono más de un escalón (CLAUDE.md, Degradados).

   El 18% está medido contra el extremo más claro, que es el peor caso:
   `--texto-suave` —el color de la lista de ventajas— deja 5,13:1 en FULL y
   5,60:1 en DELUX, por encima del 4,5:1 de AA. Por debajo de `--texto-suave`
   no baja nada aquí dentro: `--texto-tenue` no pasaría.

   DELUX es el escalón de arriba y es lo que tiene que destacar, así que se
   tiñe con `--energia` y no con el violeta. Sigue sin ser accionable: es el
   fondo de una ficha, y lo que se pulsa dentro es su botón teal. */
.plan-oferta--0 {
  background-image: linear-gradient(163deg, var(--superficie-3), var(--superficie-2));
}
.plan-oferta--1 {
  background-image: linear-gradient(163deg,
    color-mix(in srgb, var(--ok) 18%, var(--superficie-2)), var(--superficie-2));
}
.plan-oferta--2 {
  background-image: linear-gradient(163deg,
    color-mix(in srgb, var(--energia) 18%, var(--superficie-2)), var(--superficie-2));
}

/* El que ya se tiene, con el borde marcado y la pastilla "Actual" al lado: el
   color no es el único canal. */
.plan-oferta--vigente {
  border-color: var(--ok);
  border-width: 2px;
  /* El borde crece un pixel por cada lado: se compensa en el relleno para que
     la ficha no salte de tamaño respecto a las otras dos. */
  padding: calc(var(--e4) - 1px);
}

.plan-oferta-alto {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e2);
}
.plan-oferta h3 { margin: 0; font-size: 1.125rem; }
/* El botón de comprarlo, al pie de la ficha aunque la lista sea corta. */
.plan-oferta-boton { margin-top: auto; justify-content: center; }
/* El icono del plan, más grande que los de interfaz porque aquí hace de cara
   y no de rótulo. Al subir de 20px sube también la caja. */
.plan-oferta-icono { display: inline-flex; color: var(--texto); }
.plan-oferta-icono .icono { font-size: 28px; width: 28px; height: 28px; }


/* El canje, debajo de los tres y separado por una línea: es otra cosa, no una
   cuarta ficha. */
.planes-canje {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding-top: var(--e4);
  border-top: 1px solid var(--borde);
}
.planes-canje h3 { margin: 0; font-size: 1rem; }
.planes-canje p { margin: 0; }

/* La pastilla del plan, por nivel. LITE es el suelo y va neutra —no hay nada
   que celebrar ni que avisar—, FULL en verde porque hay licencia viva, y DELUX
   en el magenta de energía, que es el color de lo que destaca y marca el
   escalón de arriba sin parecer accionable. El nombre del plan va escrito dentro, así que el color
   no es el único canal. */
.etiqueta--plan-1 {
  border-color: color-mix(in srgb, var(--ok) 55%, transparent);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  color: var(--ok-alto);
}
.etiqueta--plan-2 {
  border-color: color-mix(in srgb, var(--energia) 55%, transparent);
  background-color: color-mix(in srgb, var(--energia) 14%, transparent);
  color: var(--energia-alto);
}

/* Los días que le quedan a la licencia: verde con margen, ámbar por debajo de
   una semana. Al lado va escrito cuántos días son y hasta cuándo, que es lo
   que informa a quien no distinga los dos tonos. */
.licencia {
  display: flex;
  align-items: center;
  gap: var(--e2);
  color: var(--ok-alto);
}
.licencia--apura { color: var(--aviso-alto); }

/* Los cinco slots, uno por fila. En rejilla de cinco columnas no cabe la ficha
   de lo que ocupa cada uno —artículo, destinatario, fecha y cuenta atrás—, y
   ésa es la información por la que se mira este bloque. */
.slots {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}

.slot {
  display: flex;
  align-items: flex-start;
  gap: var(--e3);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}

/* Libre en verde, ocupado en ámbar, y el filo del borde repite el mismo color
   para que la lista se lea de un vistazo sin contar iconos.

   **El color no es el único canal**: al lado va escrito "Libre", o el nombre
   del artículo que lo ocupa. */
.slot { border-left: 3px solid var(--ok); }
.slot-marca { color: var(--ok-alto); }

.slot--ocupado { border-left-color: var(--aviso); }
.slot--ocupado .slot-marca { color: var(--aviso-alto); }

.slot-cuerpo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.slot-cuerpo p { margin: 0; }
.slot-alto { font-weight: 600; overflow-wrap: anywhere; }
/* Baja de 20px, así que baja también la caja o el glifo queda descentrado. */
.slot-cuerpo .icono { font-size: 14px; width: 14px; height: 14px; }

/* --- Cuenta conectada, en la barra lateral ------------------------------- */

/* Va arriba del todo, entre dos líneas: lo que hay debajo —amigos, regalos,
   compras— es de esta cuenta y de ninguna otra, así que se dice antes de
   ofrecer ningún sitio al que ir. */
.lateral-cuenta {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding: var(--e3) 0;
  border-top: 1px solid var(--borde);
  border-bottom: 1px solid var(--borde);
}

.lateral-quien {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
}

.lateral-nick { font-weight: 600; overflow-wrap: anywhere; min-width: 0; }

/* V-Bucks y slots de regalo, en fila y en pastilla. Eran dos renglones con su
   rótulo a la izquierda y la cifra a la derecha, y el rótulo se llevaba media
   barra para decir "Slots de regalo". En pastilla el icono hace de rótulo y las
   dos cifras se leen juntas, que es como se miran. */
.indicadores {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}

/* La pastilla. El fondo tiñe **lo que haya detrás** (`transparent` en el
   `color-mix`) en vez de tapar: la barra lleva un halo teal arriba y una
   pastilla opaca ahí se recortaría del fondo.

   El texto va en `--texto`, no en `--texto-tenue`: es una cifra que se
   consulta, y sobre un fondo teñido el tenue se queda corto. */
.dato {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 3px var(--e2);
  border-radius: var(--r-pastilla);
  font-size: 0.8125rem;
  color: var(--texto-alto);
}
/* Las dos marcas, del mismo tamaño: una moneda de 18px al lado de un regalo de
   16px se lee como un descuadre. Al bajar el glifo se baja también su caja, o
   queda descentrado (CLAUDE.md, Iconos). */
.dato .icono { font-size: 16px; width: 16px; height: 16px; }
.dato .vbuck { width: 16px; height: 16px; align-self: center; }

/* El oro de los V-Bucks, con su token propio: `--vbucks` es identidad de la
   moneda y `--aviso` es "al límite". Ver el porqué en la paleta.

   El 18% está medido, no elegido a ojo: sobre la barra ya aclarada por su halo
   —el peor caso— deja 10,9:1 con el crema, muy por encima del 4,5:1 de AA. */
.dato--vbucks { background-color: color-mix(in srgb, var(--vbucks) 18%, transparent); }

/* Las fichas de regalo de pase, en neutro y a propósito. No son moneda del
   juego —eso es `--vbucks`, y una ficha no se compra ni se cambia por
   V-Bucks—, no son un estado y no se pulsan, así que ninguna de las familias
   con significado les corresponde. Tiñe `--borde-alto`, que es el neutro que
   ya vale como filo de control, al 30%: sobre la barra son 13,9:1 con el
   crema, y 11,8:1 si se mide sobre `--superficie`, que es el peor caso de las
   tres pastillas. */
.dato--fichas {
  background-color: color-mix(in srgb, var(--borde-alto) 30%, transparent);
}

/* Y el teal para los slots. **No es accionable y no lo parece**: no tiene
   hover, ni cursor, ni borde de control. El teal sigue siendo el color de lo
   que se pulsa; esto es un dato dentro de un bloque donde no hay nada que
   pulsar. Por eso tira de `--acento-hondo` y no de `--acento`: el teal de
   acción, relleno detrás de una cifra, la haría parecer un botón.
   Al 22% sobre la barra con halo son 11,4:1 con el crema. */
.dato--regalos {
  background-color: color-mix(in srgb, var(--acento-hondo) 22%, transparent);
}

/* En las tarjetas de entrada la marca hace de antetítulo: acompaña al título,
   no compite con él. */
.tarjeta .marca { font-size: 0.875rem; color: var(--texto-suave); }

.nav { display: flex; flex-direction: column; gap: var(--e2); }

/* **Una entrada del menú, y sólo `.nav a`.** Era `.nav a, .nav span`, y ese
   `.nav span` atrapaba **cualquier** `<span>` de dentro de un enlace: el icono
   —que fue un `<span>` mientras fue de Material— y el rótulo. Los dos
   heredaban 8px y 12px de relleno que no eran de ellos, y la entrada que
   llevaba un icono de otro tipo —el `<svg>` del Verificador— no los recibía y
   salía corrida a la izquierda.

   Ahora las medidas son de la entrada y están escritas: el icono a 20px del
   borde, el rótulo 12 más allá —el hueco de icono a texto de todo el panel—
   y la fila de 54 de alto con 16 arriba y abajo. Cualquier icono, sea del
   tipo que sea, cae en el mismo sitio. */
.nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e4) var(--e3) var(--e4) var(--e5);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  text-decoration: none;
  font-size: 0.9375rem;
  transition: background-color var(--t-rapida), color var(--t-rapida);
}

/* Marca de sección: una barrita que crece desde el centro al pasar por encima
   y se queda entera en la sección activa. Más gruesa y más alta que en v2 (era
   2px y 60%): con el fondo teñido de la activa competían por decir lo mismo
   demasiado flojo, y así la marca se ve desde el otro lado de la barra.

   Va en crema y no en el teal de acción: sobre el fondo teñido de la sección
   activa el teal saturado se pasaba de intensidad, y aquí no hace falta —lo
   accionable ya lo dice el enlace entero—. El crema marca sin gritar. */
.nav a::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: 3px; height: 72%;
  border-radius: var(--r-pastilla);
  background: var(--texto-alto);
  transform: translateY(-50%) scaleY(0);
  transition: transform var(--t-media);
}



/* --- Un grupo con hijos -------------------------------------------------- */

/* El padre lleva su flecha a la derecha del todo, que es lo que dice que hay
   algo dentro. `margin-left: auto` y no un hueco fijo: el nombre de la sección
   puede crecer. */
/* La flecha va **fuera del flujo**, pegada a la derecha y con su hueco
   reservado en el `padding`. En el flujo sumaba otro `gap` y, con un rótulo de
   tres palabras como «Global Friends Link», no cabía en los 256px de la barra
   y el rótulo se partía en dos líneas. Medido en negrita —la entrada activa—:
   ese rótulo pide 165px y aquí le quedan 168; un rótulo de padre más largo
   no cabe, y lo vigila `test_ningun_rotulo_del_menu_se_parte`. `translate` y no `transform` para el
   centrado: el giro de abierta ya usa `transform`. */
.nav a.nav-padre { padding-right: calc(var(--e1) + 18px + var(--e1)); }
.nav-flecha {
  position: absolute;
  top: 50%;
  right: var(--e1);
  translate: 0 -50%;
  display: inline-flex;
  color: var(--texto-tenue);
  transition: transform var(--t-media);
}
.nav-flecha .icono { font-size: 18px; width: 18px; height: 18px; }
/* Girada cuando el grupo está abierto. Es el mismo gesto que el chevron de una
   moneda del Verificador, para que un desplegable se lea igual en todo el
   panel. */
.nav-flecha--abierta { transform: rotate(180deg); }

/* **La línea que ata los hijos a su padre.** Va en la caja de los hijos y no
   en cada uno: con un borde por enlace, el hueco entre dos filas partiría la
   línea en trozos y dejaría de leerse como una sola.

   El `margin-left` la pone bajo el icono del padre, que es de donde parece
   colgar. */
.nav-hijos {
  display: flex;
  flex-direction: column;
  margin-left: calc(var(--e3) + 10px);
  padding-left: var(--e3);
  border-left: 1px solid var(--borde);
}

/* Una entrada **sangrada bajo la de arriba**: no es una sección hermana, es
   una parte de ella. Hoy sólo la de Clientes, debajo del Global Store Link.

   Y el icono, un punto más pequeño: es lo que termina de decir que depende de
   la de arriba sin escribirlo en ninguna parte. */
.nav a.nav-sub .icono { font-size: 18px; width: 18px; height: 18px; }

/* --- Tienda Gift en oro, Historial en plata --------------------------------

   **Y las dos palabras de oro del titular de la portada** (`.portada-oro`:
   «regalos» y «Sin entrar»), con el mismo metal, el mismo brillo y la segunda
   desfasada 2 s, como la plata aquí. Es lo único fuera del menú que lee estos
   tokens, y está escrito en `test_paleta.py`. No es accionable: es un rótulo,
   y el titular no se pulsa.

   El icono en el tono del metal, y el rótulo con un degradado de dos tonos
   **y un brillo que lo cruza cada 4 s**: una banda clara que entra por la
   izquierda, atraviesa en 1,2 s y espera fuera. La de plata va desfasada 2 s
   para que las dos no destellen a la vez.

   Lo que se anima es `background-position`, que es de lo barato
   (CLAUDE.md, Microinteracciones). Con `prefers-reduced-motion` no hay banda:
   se queda el degradado, que es lo que distingue las dos entradas.

   **La activa se sigue viendo**: el rótulo no toma el morado de la sección
   activa —el degradado manda—, pero conserva el fondo teñido, la barrita
   crema y la negrita, que son tres canales. */
.nav a.nav-oro,
.portada-oro {
  --metal: var(--nav-oro);
  --metal-alto: var(--nav-oro-alto);
  --metal-brillo: var(--nav-oro-brillo);
}
.nav a.nav-plata {
  --metal: var(--nav-plata);
  --metal-alto: var(--nav-plata-alto);
  --metal-brillo: var(--nav-plata-brillo);
}
.nav a.nav-oro > .icono,
.nav a.nav-plata > .icono { color: var(--metal-alto); }
/* Sin `background-clip: text`, el rótulo en el tono claro del metal: un texto
   transparente sin su fondo recortado no se leería. */
.nav a.nav-oro > span,
.nav a.nav-plata > span,
.portada-oro { color: var(--metal-alto); }
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .nav a.nav-oro > span,
  .nav a.nav-plata > span,
  .portada-oro {
    /* Dos capas: arriba la banda del brillo, del doble y medio del ancho para
       poder dejarla fuera a los dos lados; abajo el degradado del metal. */
    background-image:
      linear-gradient(100deg, transparent 38%, var(--metal-brillo) 50%,
        transparent 62%),
      linear-gradient(90deg, var(--metal), var(--metal-alto) 50%,
        var(--metal));
    background-size: 250% 100%, 100% 100%;
    background-repeat: no-repeat;
    background-position: 100% 0, 0 0;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    animation: nav-brillo 4s var(--curva) infinite;
  }
  .nav a.nav-plata > span,
  .portada-oro--tarde { animation-delay: 2s; }
}
/* De 100% (la banda fuera por la izquierda) a 0% (fuera por la derecha) en el
   primer 30% del ciclo, y el resto, quieta fuera. */
@keyframes nav-brillo {
  0% { background-position: 100% 0, 0 0; }
  30%, 100% { background-position: 0% 0, 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .nav a.nav-oro > span,
  .nav a.nav-plata > span,
  .portada-oro { animation: none; }
}

.nav a:hover { background-color: var(--superficie-2); color: var(--texto); }
.nav a:hover::before { transform: translateY(-50%) scaleY(1); }

.nav .activo {
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
  font-weight: 600;
}
.nav .activo::before { transform: translateY(-50%) scaleY(1); }


.nav-titulo {
  padding: 0 var(--e3);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  /* Rótulo que encabeza un bloque, así que va en crema. La distinción con el
     tenue es de trabajo, no de tamaño: lo que **encabeza** va en crema, lo que
     **acompaña** a un dato (`.ficha dt`, `.nav-pronto`) se queda en tenue. */
  color: var(--texto-alto);
}

/* Los grupos de la navegación. El hueco entre secciones lo pone el rótulo,
   no un `gap` mayor: así el título se lee pegado a lo que encabeza. */
.nav-grupo { display: flex; flex-direction: column; gap: var(--e2); }
.nav-titulo:not(:first-child) { margin-top: var(--e4); }

/* La marca de "todavía no existe". Va escrita y no sólo en el gris: el color
   por sí solo no lo dice, y `aria-disabled` no lo lee todo el mundo. */
.nav .nav-pronto {
  margin-left: auto;
  padding: 2px var(--e2);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--texto-tenue);
}

.lateral-pie { margin-top: auto; display: grid; justify-items: start; gap: var(--e2); }
.lateral-soporte[hidden] { display: none; }
/* El diálogo «Soporte»: la tarjeta de Redes (`.fn-redes`) dentro de un cajón.
   El aspa arriba a la derecha, sin título propio: el título es el de la
   tarjeta. */
.soporte-dialogo { max-width: 560px; }
.soporte-dialogo .soporte-alto { display: flex; justify-content: flex-end; }

/* --- Área principal ----------------------------------------------------- */
.principal { padding: var(--e8) var(--e6); min-width: 0; }

.cabecera {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--e4);
  flex-wrap: wrap;
  padding-bottom: var(--e5);
  border-bottom: 1px solid var(--borde);
  margin-bottom: var(--e8);
}
/* Las acciones de una cabecera: los botones en fila y lo que los explica
   debajo, **del ancho de la columna y no de la página**. Sin el tope, una frase
   larga hacía saltar la columna a su propia línea y el botón apagado se
   estiraba de punta a punta. */
.cabecera-acciones {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--e2);
  max-width: 26rem;
  text-align: right;
}
.cabecera-acciones p { margin: 0; }
.cabecera-botones {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--e2);
}
/* El total de V-Bucks de «Mis cuentas», a la izquierda de los botones. Es la
   excepción a «Cifras» que decidió el dueño (CLAUDE.md): cifra arriba y rótulo
   pequeño debajo. **Mide lo que los botones y no se parece a ninguno**: la
   fila lo estira a su altura (`stretch`), y lleva el borde de una superficie
   —`--borde`, no el `--borde-alto` de un control—, sin degradado de acento,
   sin hover y sin cursor. Ni color de acción ni oro: el dorado es del icono. */
.total-vbucks {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 2px;
  margin: 0;
  padding: var(--e1) var(--e3);
  background-color: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  line-height: 1.15;
  text-align: left;
}
.total-vbucks-cifra { display: flex; align-items: center; gap: var(--e1); }
.total-vbucks-cifra .mono {
  color: var(--texto-alto);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.total-vbucks-rotulo { color: var(--texto-tenue); font-size: 0.75rem; }

@media (max-width: 40rem) {
  .cabecera-acciones { align-items: stretch; max-width: none; text-align: left; }
  .cabecera-botones { justify-content: flex-start; }
}

/* De cuándo es la tienda que se está mirando: cuándo se comprobó contra la
   API, a qué rotación pertenece, quién contestó y su hash. Metadato puro, así
   que `--texto-tenue` y un escalón por debajo del cuerpo; el hash va en
   monoespaciada porque es un identificador y no un recuento (CLAUDE.md,
   "Cifras"). No es accionable y no lleva color de acento. */
.tienda-leida {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e1) var(--e4);
  margin: var(--e1) 0 0;
  font-size: 0.8125rem;
  color: var(--texto-tenue);
}
.tienda-leida-dato {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
}
.tienda-leida-dato .icono { width: 16px; height: 16px; }

/* La línea del admin: de dónde salió la tienda y su huella. **No se pinta
   para un vendedor** —la decide `es_admin` en la plantilla, no una clase que
   la esconda—, y lleva la etiqueta escrita para que quien la vea sepa que los
   demás no la ven. */
.tienda-leida--admin { gap: var(--e1) var(--e2); }

/* Cuando lo que se enseña no se ha podido comprobar contra la API. Va en el
   ámbar de aviso —"al límite", que es lo que significa— y **no es el único
   canal**: la palabra "Sin comprobar" está escrita, y al lado sigue diciendo
   de cuándo es. */
.tienda-leida-aviso {
  color: var(--aviso-alto);
  font-weight: 500;
}


/* --- Clientes ------------------------------------------------------------ */

.cl-sub { margin: var(--e1) 0 0; }

.cl-buscador { flex: 0 1 18rem; min-width: 0; }
.cl-buscador .ck-input { width: 100%; }

/* La tabla se desplaza **dentro de su caja**: seis columnas no caben en un
   móvil, y sin esto la que se iría a lo ancho sería la página entera
   (CLAUDE.md, Responsive). */
.cl-tabla-caja { overflow-x: auto; }

.cl-tabla {
  width: 100%;
  border-collapse: collapse;
  white-space: nowrap;
}

.cl-tabla th, .cl-tabla td {
  padding: var(--e2) var(--e3);
  text-align: left;
  border-bottom: 1px solid var(--borde);
}

.cl-tabla thead th {
  padding: 0;
  color: var(--texto-tenue);
  font-size: 0.75rem;
  font-weight: 500;
}

/* La cabecera que ordena es un botón de verdad, así que hay que quitarle la
   cara de botón y dejarle la de cabecera. */
.cl-tabla thead button {
  display: flex;
  align-items: center;
  gap: var(--e1);
  width: 100%;
  padding: var(--e2) var(--e3);
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.cl-tabla thead button:hover { color: var(--texto); }
.cl-tabla thead button:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* La flecha. **Sólo se ve en la columna por la que se ordena**: una flecha en
   las cinco no dice cuál manda. Y gira, que es lo que distingue los dos
   sentidos sin escribirlos. */
.cl-tabla thead .icono {
  font-size: 18px; width: 18px; height: 18px;
  opacity: 0;
  transition: transform var(--t-rapida), opacity var(--t-rapida);
}
.cl-orden--activo { color: var(--acento); }
.cl-orden--activo .icono { opacity: 1; }
.cl-orden--desc .icono { transform: rotate(180deg); }

/* **La etiqueta baja de línea si no cabe al lado del nombre.** Sin esto, un
   nombre largo Y una etiqueta en la misma fila sumaban 1032 px de tabla y las
   acciones volvían a salirse de la caja a 1280: capar el nombre no basta,
   porque la etiqueta de dedicado son otros 120 px que no se pueden recortar
   —lleva la palabra escrita y ésa es la regla, el color nunca es el único
   canal—.

   Envolviendo, ese caso ocupa dos renglones en vez de ensanchar la columna.
   **Sí hace esa fila más alta** —80 px contra los 52 de las demás, medido— y
   es el precio que se ha elegido pagar: lo pagan sólo las filas que tienen
   nombre largo **y** etiqueta, y la alternativa era peor. Capando la celda
   entera para que las dos cupieran en un renglón, al nombre le quedaban 72 px
   —unos nueve caracteres, «maria.fer…»— y ese nombre es el identificador con
   el que el vendedor reconoce a su cliente. Un renglón de más se ve; un nombre
   recortado a nueve letras no se reconoce. */
.cl-usuario {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e1) var(--e2);
}

/* **El nombre se capa, y es lo que hace que la tabla quepa.** Desde el 3c un
   usuario son hasta 40 caracteres y muchos son un correo; con la tabla en
   `nowrap`, uno solo de ésos se llevaba 276 px de los 1011 que medía la tabla
   y empujaba las acciones fuera de su caja a 1280 px —el ancho de escritorio
   de las escenas—, que es donde está el botón de desactivar.

   Se capa **el nombre y no la celda**: al lado vive la etiqueta de dedicado o
   de desactivado, y capando la celda entera se aplastaban las dos juntas. Con
   el tope en el nombre, la etiqueta se queda como está y lo que cede es lo que
   sobra del correo.

   **El nombre completo no se pierde por ningún lado**: el texto entero sigue
   en el DOM —la elipsis es de pintura, no de contenido, así que un lector de
   pantalla lo lee entero— y además va en el `title`, que es lo que lo enseña
   al pasar por encima. El canal visual no es el único (CLAUDE.md).

   **El tope era 19ch y bajó a 17ch el 12/10/2026** porque el menú lateral
   pasó de 232 a 256 px y a 1280 la caja de la tabla se quedó en 926 px, con
   la tabla pidiendo 930. Con 17ch pide 912: el margen vuelve a ser de 14 px.
   Si se vuelve a apretar, lo siguiente que hay que mirar es «Última compra»
   —fecha y hora en un renglón, 137 px—, no otros dos caracteres del nombre. */
.cl-nombre {
  color: var(--texto-alto);
  display: block;
  max-width: 17ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cl-fecha { color: var(--texto-suave); font-size: 0.8125rem; }

/* La celda de «Vence»: la fecha arriba y los días debajo. Los dos datos y no
   uno — la fecha es lo que se compara con un calendario y los días son lo que
   se ve de un vistazo—, y el de debajo más pequeño porque acompaña. */
.cl-vence { line-height: 1.25; }
.cl-vence-dias {
  display: block;
  color: var(--texto-tenue);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}
/* **Los últimos quince días.** El mismo `--aviso` que la tarjeta de `/fn/` y el
   medidor del plan al llegar al tope: aquí significa lo mismo, «al límite».

   El color **no es el único canal**: al lado va el recuento de días, que ya lo
   dice. Y **no tiñe la fila entera** —eso es de `.cl-fila--apagada`, que es
   otro estado— ni el fondo de la celda: lo que lleva color es el dato.

   Medido sobre `--superficie`, que es donde se apoya la tabla: 8,50:1 el texto
   de la fecha y 8,50:1 el de los días. El panel es oscuro y sólo oscuro, así
   que aquí no hay el segundo tema que sí hay que mirar en la tienda. */
.cl-vence--pronto,
.cl-vence--pronto .cl-vence-dias { color: var(--aviso-alto); }

/* Un cliente desactivado **sigue entero, sólo que apagado**: se baja el tono
   de la fila y se dice con la palabra al lado del nombre. No va en rojo —no es
   un error, es una decisión del vendedor— ni se esconde, que es lo contrario
   de «no es borrar». */
.cl-fila--apagada .cl-nombre,
.cl-fila--apagada td { color: var(--texto-tenue); }

.cl-apagado {
  background-color: var(--superficie-3);
  color: var(--texto-suave);
}

.cl-acciones { display: flex; gap: var(--e2); justify-content: flex-end; }

/* Las dos cifras del diálogo, de sólo lectura y una al lado de la otra. */
.cl-cifras { display: flex; gap: var(--e6); }

/* El PIN recién restablecido. **Se enseña una sola vez**, así que se le da
   sitio: la cifra a un escalón por encima del cuerpo y su botón de copiar
   debajo, como la llave de la API. */
.cl-pin-nuevo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--e2);
  padding: var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
}
.cl-pin-nuevo[hidden] { display: none; }
.cl-pin-valor {
  margin: 0;
  font-size: 1.375rem;
  color: var(--texto-alto);
  word-break: break-all;
}

/* --- El padrón: importar y exportar -------------------------------------- */

/* Las dos acciones de la cabecera. Se envuelven en el móvil, que es donde el
   título ya ocupa el ancho entero. */
.cl-padron {
  display: flex;
  gap: var(--e2);
  flex-wrap: wrap;
}

/* Los tres datos del archivo dentro del diálogo, en fila mientras quepan. La
   misma `.ficha` del resto del panel: el rótulo en `--texto-tenue` y el dato
   debajo. */
.cl-imp-ficha {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e3) var(--e6);
}

/* El `export_id` es un identificador y va en monoespaciada (CLAUDE.md,
   "Cifras"). Se parte donde haga falta: mide 36 caracteres y en un móvil no
   cabe de una pieza. */
.cl-imp-id {
  color: var(--texto-alto);
  word-break: break-all;
}

/* La frase con las dos cifras. Es una frase, no un cartel: el tamaño es el del
   cuerpo y lo que destaca es el peso y el crema de la cifra. */
.cl-imp-resumen {
  margin: 0;
  color: var(--texto);
}
.cl-imp-resumen b { color: var(--texto-alto); }

/* **El icono de los V-Bucks es `display: block` en todo el panel** —en los
   demás sitios vive dentro de una caja flexible— y aquí va en el flujo de una
   frase, así que se pasa a `inline-block` o se lleva la cifra a la línea
   siguiente. Es la misma corrección que ya lleva `.sac-meses .vbuck`, y el
   `white-space: nowrap` de abajo **no la sustituye**: un `block` rompe la línea
   aunque no se pueda envolver. Se vio en la foto de la escena.

   El desplazamiento vertical lo centra con los números, que no llegan tan abajo
   como la caja del icono. */
.cl-imp-resumen .vbuck {
  display: inline-block;
  vertical-align: -3px;
  margin: 0 var(--e1);
}

/* La cifra y su moneda **no se separan**. Es una pieza, no dos palabras: con el
   salto de línea en medio, el icono se quedaba solo en la línea siguiente y el
   número dejaba de decir de qué era. */
.cl-imp-moneda { white-space: nowrap; }

/* Cada uno de los cinco listados de la previa. Nace escondido y lo enciende el
   guion: un título con una lista vacía debajo es ruido. */
.cl-imp-lista {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
}
.cl-imp-lista[hidden] { display: none; }

.cl-imp-titulo {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--texto-alto);
}

.cl-imp-lista ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  /* Un padrón puede traer ciento y pico nombres en un solo listado: se
     desplaza dentro de su caja en vez de estirar el diálogo. */
  max-height: 11rem;
  overflow-y: auto;
}

/* **Envuelve, y ésa es toda la regla.** El nombre y lo que le pasa caben en
   una línea cuando lo que le pasa es corto —una diferencia, una cuenta— y se
   apilan cuando es una frase. Sin el `wrap`, un motivo de ocho palabras al
   lado de un nombre de cuarenta caracteres dejaba el nombre en una columna de
   cinco letras, una por línea; se vio en la foto de la escena y no en ningún
   test. */
.cl-imp-lista li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--e3);
  font-size: 0.8125rem;
  color: var(--texto-suave);
}

/* El nombre del cliente, que es lo que se busca al leer la lista. Parte por
   donde sea **sólo si hace falta** (`anywhere`, no `break-all`): un nombre de
   cuarenta caracteres tiene que poder cortarse, y uno normal no. */
.cl-imp-quien {
  color: var(--texto);
  overflow-wrap: anywhere;
}

/* Lo que le pasa a ese nombre: el motivo, la diferencia, la cuenta que falta.
   Un escalón por debajo y pegado a la derecha; con `margin-left: auto` se va al
   borde cuando comparte línea y ocupa el ancho cuando se baja a la suya.

   Parte por donde sea, por lo mismo que `.cl-imp-quien`: aquí cae el valor de
   una cuenta que no se encontró, y en un padrón real eso es el `accountId` de
   Epic — treinta y dos caracteres sin un sitio por donde cortar. */
.cl-imp-que {
  margin-left: auto;
  color: var(--texto-tenue);
  text-align: right;
  overflow-wrap: anywhere;
}

/* Lo que se pierde si no se hace nada: el balance de un cliente que no entra
   porque su nombre no vale aquí. En `--peligro-alto`, que es el que se lee
   (5,02:1 sobre `--superficie-2`, que es la caja del listado).

   **El color no es el único canal**, como en toda la casa: la misma línea dice
   «nombre demasiado corto» y la ayuda del bloque dice que se renombra en el
   origen y se vuelve a exportar. */
.cl-imp-que--pierde { color: var(--peligro-alto); }

/* --- Pestañas ------------------------------------------------------------ */

/* Enlaces, no botones: cada pestaña es una URL. La activa se marca con el
   filo teal debajo, no sólo con el color del texto. */
.pestanas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e1);
  margin-bottom: var(--e6);
  border-bottom: 1px solid var(--borde);
}

.pestana {
  position: relative;
  padding: var(--e2) var(--e3);
  color: var(--texto-suave);
  text-decoration: none;
  font-size: 0.9375rem;
  border-radius: var(--r-control) var(--r-control) 0 0;
  transition: background-color var(--t-rapida), color var(--t-rapida);
}
.pestana:hover { background-color: var(--superficie-2); color: var(--texto); }

.pestana--activa { color: var(--acento); font-weight: 500; }
.pestana--activa::after {
  content: "";
  position: absolute;
  left: var(--e2); right: var(--e2); bottom: -1px;
  height: 3px;
  border-radius: var(--r-pastilla);
  background: var(--grad-acento);
}

/* --- Lista de amigos ----------------------------------------------------- */

/* La fila se apila: arriba nombre y estado, debajo lo desplegado. */
.lista-amigos .amigo { flex-direction: column; align-items: stretch; }

.amigo-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  flex-wrap: wrap;
}

/* Nombre y botón de plegar van juntos: son una sola cosa, y sueltos en el
   flex de `.amigo-alto` el `space-between` los habría separado, dejando la
   flecha flotando en medio de la fila. */
.amigo-titulo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
}

.amigo-nombre {
  font-weight: 600;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* El botón que recoge las cuentas externas. Llega `hidden` del servidor y lo
   enseña el JavaScript: sin él no hay nada que plegar y la lista se ve
   entera, que es lo que hace falta para reconocer a alguien.

   No es teal aunque se pulse: teal es el color de las acciones de la fila
   —aceptar, rechazar, eliminar—, y esto sólo enseña y esconde lo que ya está
   en la página. */
.plegar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  border: 1px solid transparent;
  border-radius: var(--r-control);
  background: none;
  color: var(--texto-tenue);
  cursor: pointer;
  transition: color var(--t-rapida), border-color var(--t-rapida),
              background-color var(--t-rapida);
}
.plegar[hidden] { display: none; }
.plegar:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto);
}

/* Desplegado, la flecha mira hacia arriba: el estado no viaja sólo en el
   color, y debajo está la propia lista para confirmarlo. */
.plegar .icono { transition: transform var(--t-rapida); }
.plegar[aria-expanded="true"] .icono { transform: rotate(180deg); }

.amigo-derecha {
  display: flex;
  align-items: center;
  gap: var(--e2);
  flex-wrap: wrap;
}

/* Las cuentas externas, debajo del nombre y una por línea. Seguidas en
   horizontal no se sabía qué nombre era de qué plataforma; con la plataforma
   delante de cada nombre, sí. */
.lista-externas {
  list-style: none;
  margin: var(--e2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
}
.lista-externas li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--e1);
  font-size: 0.875rem;
  color: var(--texto-suave);
  min-width: 0;
}
.externa-plataforma { flex: none; color: var(--texto-tenue); }
.externa-nombre { overflow-wrap: anywhere; min-width: 0; }

/* "Sin cuentas externas públicas" se dice, no se deja el hueco: vacío se
   leería como "no tiene", y Epic sólo devuelve las públicas. */
.sin-externas { margin: var(--e2) 0 0; }

/* --- Cuenta conectada ---------------------------------------------------- */

/* La fila de arriba del área principal: hamburguesa a la izquierda, rincón de
   la sesión a la derecha. En escritorio sólo se ve el rincón, así que queda
   exactamente como estaba. */
.alto-pagina {
  display: flex;
  align-items: center;
  gap: var(--e3);
  margin-bottom: var(--e4);
}
.alto-pagina .rincon { flex: 1 1 auto; margin-bottom: 0; }

/* La hamburguesa. `display: none` de partida y la enseña `.con-js` sólo en
   móvil: sin JavaScript no hay cajón que abrir, y un botón que no lleva a
   ninguna parte es peor que no tenerlo. */
.hamburguesa {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  padding: var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  cursor: pointer;
  transition: background-color var(--t-rapida), border-color var(--t-rapida),
              color var(--t-rapida);
}
.hamburguesa:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto);
}

/* Soltar la cuenta es una acción sobre la página, no una sección más: va en
   la esquina de arriba a la derecha, por encima de la cabecera. Quién es la
   cuenta lo dice la barra lateral; aquí sólo queda lo que hace falta para
   decidir si soltarla —el plazo— y el botón. */
.rincon {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--e3);
  flex-wrap: wrap;
  margin-bottom: var(--e4);
}

/* "Sesión activa: 9:34" no se parte por la mitad: la frase es corta y el
   reloj sin su rótulo no dice de qué es. Las cifras van tabulares (.cifras),
   así que el segundero no cambia de ancho al bajar. */
.sesion { white-space: nowrap; }

/* El punto no lleva texto: lo que dice ya está escrito al lado, así que va
   aria-hidden y no hace falta que parpadee para que se entienda. */
.conectada-punto {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ok-alto);
}

/* Sin cuenta conectada el punto se apaga, pero el color no es el único
   canal: al lado lo dice el texto. */
.conectada-punto--suelto { background: var(--texto-tenue); }

/* La tarjeta de la cuenta con la que se está trabajando: en verde, que es lo
   que la señala de un vistazo en la rejilla. Verde y no teal a propósito —el
   teal es el color de lo que se pulsa, y aquí no hay nada que pulsar: la
   cuenta ya está conectada—. El color tampoco es el único aviso: la tarjeta
   lleva su etiqueta "Conectada" en el hueco del botón. */
.cuenta--conectada {
  border-color: var(--ok);
  /* Un 8% y no más: por encima, la superficie sube del escalón de
     `--superficie-3` y `--texto-tenue` deja de pasar AA encima. El verde que
     se ve de lejos lo ponen el borde, el filo y la etiqueta. */
  background-color: color-mix(in srgb, var(--ok) 8%, var(--superficie-2));
}
/* Al pasar por encima no se aclara —ya está en lo alto de la escalera—: lo
   que responde es el borde. Sin esto, el hover de `.cuenta` la devolvería a
   gris y la tarjeta activa se perdería justo al apuntarla. */
.cuenta--conectada:hover {
  border-color: var(--ok-alto);
  background-color: color-mix(in srgb, var(--ok) 8%, var(--superficie-2));
}
.cuenta--conectada::after { opacity: 1; background: var(--ok-alto); }

.etiqueta--conectada {
  border-color: color-mix(in srgb, var(--acento-hondo) 60%, transparent);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
}

/* --- Rejilla de tarjetas ------------------------------------------------ */
.rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--e5);
}

.cuenta {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Denso: tres líneas cortas y el botón. Lo que separa los bloques es el
     margen de arriba del botón, no un hueco igual entre todo. */
  gap: var(--e2);
  padding: var(--e3) var(--e4) var(--e4);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  min-width: 0;
  transition: transform var(--t-media), border-color var(--t-media),
              background-color var(--t-media);
}
.cuenta:hover {
  transform: translateY(-2px);
  border-color: var(--borde-alto);
  background-color: var(--superficie-2);
}

/* La misma línea de luz de las tarjetas de entrada, pero sólo al posarse
   encima: en reposo la rejilla se mantiene tranquila. */
.cuenta::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  border-radius: var(--r-tarjeta) var(--r-tarjeta) 0 0;
  background: var(--grad-acento);
  opacity: 0;
  transition: opacity var(--t-media);
}
.cuenta:hover::after { opacity: 1; }

/* Línea 1: asa, nombre, estado y menú. El nombre se lleva el hueco que sobra
   y es el único que se parte; los otros tres miden lo que miden. */
.cuenta-alto {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
}

.cuenta-nombre {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.cuenta-alto .etiqueta, .cuenta-alto .menu { flex: none; }

/* --- La tarjeta dedicada ------------------------------------------------
   **Sólo el borde y la etiqueta, nunca el fondo entero.** La tarjeta ya lleva
   la cifra de V-Bucks, que en este panel también es dorada; tiñendo la
   superficie, el saldo y la reserva se leerían como la misma cosa. Lo que
   señala es el filo, que es donde no compite con nada.

   Y el color no es el único canal, como en el hover: la etiqueta dice
   «Dedicada» con todas sus letras y debajo van los clientes. */
.cuenta--dedicada { border-color: var(--dedicada); }
/* Al pasar por encima responde el borde, como en el resto de la rejilla. Sin
   esto, el hover de `.cuenta` la devolvería al gris y la reserva se perdería
   justo al apuntarla. */
.cuenta--dedicada:hover { border-color: var(--dedicada-alto); }

/* --- La tarjeta dormida -------------------------------------------------
   **Discreta a propósito y sin color propio.** El reposo no es un estado que
   haya que atender —lo pidió su dueño— así que no se le da ni pastilla ni
   tono: lo que dice que está dormida son la luna de al lado del nombre y que
   la tarjeta se apaga un punto. Así «Dedicada» conserva su sitio y su dorado
   cuando la cuenta lleva las dos cosas. */
.cuenta--dormida { border-style: dashed; }
/* Lo de dentro se apaga, menos la marca de dormida y la etiqueta de dedicada,
   que son justo lo que hay que seguir leyendo. Es `opacity` y no un color:
   así no hay que volver a medir el contraste de seis piezas. */
.cuenta--dormida .cuenta-nombre,
.cuenta--dormida .cuenta-linea,
.cuenta--dormida .cuenta-dedicada { opacity: 0.68; }

/* La luna, pegada al nombre. Es el escalón de metadato y no lleva caja: en la
   rejilla tiene que verse sin pedir el sitio de una pastilla. */
.cuenta-dormida {
  flex: none;
  display: inline-flex;
  align-items: center;
  color: var(--texto-tenue);
}
.cuenta-dormida .icono { font-size: 18px; width: 18px; height: 18px; }

/* Quiénes son, debajo de los datos. Es metadato: va en el cuerpo de texto
   pequeño y dentro de la frase que lo explica, sin tarjeta ni cifra grande. */
.cuenta-dedicada {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e1) var(--e2);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}
.cuenta-dedicada .icono { color: var(--dedicada-alto); }
.cuenta-dedicada .separa { color: var(--borde-alto); }
.cuenta-dedicada-quienes {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* --- El minipanel de clientes dedicados ---------------------------------
   Vive en el cajón de siempre y reusa `.lista`, `.nota` y `.acciones`: lo
   único propio es la fila, que pone el nombre a la izquierda y el botón de
   quitar a la derecha. */
.dedicada-fila {
  display: flex;
  align-items: center;
  gap: var(--e3);
}
.dedicada-quien {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-wrap: anywhere;
}
.dedicada-usuario { color: var(--texto); font-weight: 500; }
/* El nombre que le puso el vendedor. Acompaña al dato, así que se queda en el
   escalón de metadato (CLAUDE.md, Cifras). */
.dedicada-ref { color: var(--texto-tenue); font-size: 0.8125rem; }

/* El asa de reordenar. En reposo va apagada y SIN cursor de agarre, porque
   así es como llega del servidor: arrastrar es cosa del JavaScript y sin él no
   puede prometer nada. `.asa--viva` la pone el propio JavaScript al arrancar,
   y es lo único que enciende el agarre. */
.asa {
  flex: none;
  display: flex;
  align-items: center;
  margin-left: calc(var(--e2) * -1);
  border-radius: var(--r-control);
  color: var(--texto-tenue);
  opacity: 0.5;
  cursor: default;
  transition: color var(--t-rapida), opacity var(--t-rapida);
}

.asa--viva { opacity: 1; cursor: grab; }
.asa--viva:hover, .asa--viva:focus-visible { color: var(--texto-suave); }
.asa--viva:active { cursor: grabbing; }

/* La tarjeta que se está moviendo. Se atenúa para que se vea que el hueco de
   debajo es el sitio al que va, y se le quita el hover: mientras se arrastra
   ya está "elevada", y elevarla dos veces la despega de la rejilla. */
.cuenta--arrastrando {
  opacity: 0.4;
  border-color: var(--acento-hondo);
}
.cuenta--arrastrando:hover { transform: none; }
.cuenta--arrastrando::after { opacity: 0; }

/* Líneas 2 y 3: icono + cifra, sin más texto. Lo que el icono no puede decir
   está en el `title` y en el texto para lector de pantalla. */
.cuenta-linea {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e2);
  margin: 0;
  font-size: 0.875rem;
  color: var(--texto-suave);
}
.cuenta-linea .icono {
  color: var(--texto-tenue);
  font-size: 18px; width: 18px; height: 18px;
}
.cuenta-linea .mono, .cuenta-linea .cifras { color: var(--texto-alto); }
.cuenta-linea .separa { color: var(--borde-alto); }
/* La lista de amigos casi llena y llena, en corto («Amigos: 974/1k»). La
   línea entera —icono y cifra incluidos— va en el `-alto` de su estado, que
   es el que se lee sobre `--superficie`; la frase larga está en el `title` y
   en `sr-solo`, así que el color no es el único canal. */
.cuenta-linea--aviso,
.cuenta-linea--aviso .icono,
.cuenta-linea--aviso .cifras { color: var(--aviso-alto); }
.cuenta-linea--peligro,
.cuenta-linea--peligro .icono,
.cuenta-linea--peligro .cifras { color: var(--peligro-alto); }

/* Las tres cifras de la tarjeta, cada una con su icono delante. En fila y no
   en línea de texto: con el icono como `inline`, la caja de 18px se alinea por
   la base y la cifra queda un pelo alta. */
.cuenta-vbucks,
.cuenta-slots,
.cuenta-fichas { display: flex; align-items: center; gap: var(--e2); }

/* El desglose de fichas por pase.
   ------------------------------------------------------------------------
   El icono de fichas es el asa, y es un botón de verdad: sale con el ratón
   encima (`:hover`) y también al pulsarlo o tabular hasta él
   (`:focus-within`), que es lo que lo hace funcionar en el móvil, donde no hay
   hover que valga.

   Sin JavaScript. Y sin `display: none`: se esconde con `opacity`, así que el
   globo sigue en el árbol de accesibilidad y un lector de pantalla lee el
   desglose entero al llegar al botón —que para eso lo referencia con
   `aria-describedby`—. */
.cuenta-fichas { position: relative; }

.fichas-boton {
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--texto-tenue);
  cursor: pointer;
  transition: color var(--t-rapida);
}
.fichas-boton:hover { color: var(--acento-alto); }

.fichas-globo {
  position: absolute;
  left: 0;
  bottom: calc(100% + var(--e1));
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  min-width: 168px;
  padding: var(--e2) var(--e3);
  background-color: var(--superficie-2);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  color: var(--texto-suave);
  font-size: 0.8125rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-rapida);
}
.cuenta-fichas:hover .fichas-globo,
.cuenta-fichas:focus-within .fichas-globo { opacity: 1; }

/* Cada línea: el nombre a la izquierda y la cifra pegada a la derecha, para
   que las cuatro cifras caigan en la misma columna y se comparen de un
   vistazo. */
.fichas-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e4);
  white-space: nowrap;
}
.fichas-fila .cifras { color: var(--texto-alto); }

/* Cuándo se leyeron, debajo de las cuatro filas y separado por un filo: es un
   metadato del desglose, no una ficha más. Es la fecha propia de las fichas
   (`pass_tokens_at`) y no la de la foto. */
.fichas-fecha {
  margin-top: var(--e1);
  padding-top: var(--e2);
  border-top: 1px solid var(--borde);
  color: var(--texto-suave);
  white-space: nowrap;
}

/* El icono de V-Bucks es una imagen de marca, no un Material Symbol: no se
   tiñe ni se sustituye por un glifo. Autoalojada, como todo lo demás. */
.vbuck { display: block; width: 18px; height: 18px; flex: none; }

/* La acción de la tarjeta, abajo y de lado a lado: es la única que hay, así
   que no tiene con qué competir por el sitio. */
.boton--ancho {
  width: 100%;
  margin-top: var(--e2);
  padding: 8px var(--e3);
  font-size: 0.9375rem;
}
.cuenta form { margin-top: var(--e2); }
.cuenta form .boton--ancho { margin-top: 0; }

/* La conectada no se pulsa: ya lo está. Se queda en el hueco del botón para
   que la rejilla no baile, pero en superficie y en el mismo verde que la
   tarjeta entera. */
.boton--conectada {
  /* Verde hondo, mezclado contra `--fondo` y no contra lo que tenga debajo:
     es el equivalente de `--acento-tenue`, y es lo que deja a `--ok-alto`
     pasar AA encima. Un velo translúcido sobre la tarjeta ya teñida se
     quedaba en 3.9:1. */
  background-color: color-mix(in srgb, var(--ok) 20%, var(--fondo));
  background-image: none;
  border-color: color-mix(in srgb, var(--ok) 60%, transparent);
  color: var(--ok-alto);
  cursor: default;
}
.boton--conectada:hover { transform: none; }

/* --- Menú de acciones de la tarjeta ------------------------------------- */
.menu { position: relative; }

/* **La tarjeta con el menú abierto va por encima de las demás.** Sin esto la
   lista salía a veces detrás de la tarjeta siguiente: `.cuenta:hover` lleva un
   `transform`, que hace de la tarjeta un contexto de apilamiento, y el
   `z-index` de `.menu-lista` sólo vale dentro de él. Las tarjetas de después
   van detrás en el HTML y se pintaban encima. Con la tarjeta entera subida da
   igual que haya `transform` o no, y en el móvil, donde el hover se queda
   pegado tras el toque, también. Lo vigila `tests/test_menu_tarjeta.py`. */
.cuenta:has(.menu[open]) { z-index: 2; }

/* Cerca del borde de abajo de la ventana se abre hacia arriba. Lo decide el
   guion de `base.html` al abrir; sin él se abre hacia abajo, como siempre. */
.menu--arriba > .menu-lista { top: auto; bottom: calc(100% + var(--e1)); }

.menu > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  cursor: pointer;
  transition: background-color var(--t-rapida), color var(--t-rapida),
              border-color var(--t-rapida);
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto);
}

.menu-lista {
  position: absolute;
  right: 0;
  top: calc(100% + var(--e1));
  z-index: 10;
  min-width: 180px;
  padding: var(--e1);
  background-color: var(--superficie-2);
  background-image: var(--grad-superficie-alta);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
}

.menu-lista a {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  border-radius: var(--r-control);
  color: var(--texto);
  text-decoration: none;
  font-size: 0.9375rem;
  transition: background-color var(--t-rapida), color var(--t-rapida);
}
.menu-lista a:hover { background-color: var(--acento-tenue); color: var(--acento-alto); }
.menu-lista .peligroso { color: var(--peligro-alto); }
.menu-lista .peligroso:hover {
  background-color: color-mix(in srgb, var(--peligro) 20%, transparent);
  color: var(--peligro-alto);
}

/* --- Panel lateral deslizante ------------------------------------------- */
.velo {
  position: fixed;
  inset: 0;
  z-index: 20;
  background-color: color-mix(in srgb, var(--fondo) 78%, transparent);
  backdrop-filter: blur(2px);
  animation: aparecer var(--t-media) both;
}

.cajon {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  z-index: 21;
  width: min(440px, 100%);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  padding: var(--e6);
  background-color: var(--superficie);
  background-image: linear-gradient(200deg,
    color-mix(in srgb, var(--acento-tenue) 60%, transparent) 0%,
    transparent 34%);
  border-left: 1px solid var(--borde-alto);
  animation: entrar-derecha var(--t-media) both;
}

/* Mejorar plan **no es un panel lateral**: comparar tres planes es mirarlos a
   la vez, y contra un borde de la pantalla no caben tres columnas.

   Es el mismo arreglo que necesitó el diálogo de confirmar pedido, y por el
   mismo motivo: `.cajon` clava `top/right/bottom` y un ancho de panel, y hay
   que deshacerlo entero.

   **El alto tiene que ser `fit-content` y no `auto`**: con `auto` entre un
   `top` y un `bottom` la caja se estira de arriba abajo en vez de centrarse.
   Es exactamente lo que se veía la otra vez. */
.cajon--modal {
  /* El ancho por variable para que pueda haber uno estrecho sin copiar el
     resto de la regla: lo que cambia entre «Mejorar plan» y un formulario de
     dos campos es el ancho y nada más. */
  --ancho-modal: 960px;
  inset: 0;
  margin: auto;
  width: min(var(--ancho-modal), calc(100% - 2 * var(--e4)));
  height: fit-content;
  max-height: calc(100% - 2 * var(--e4));
  /* Ya no se apoya en ningún borde: lleva su caja entera y el radio de las
     superficies grandes. `.cajon` sólo le pone el filo izquierdo. */
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  /* Entra fundiendo, con el velo: `entrar-derecha` es el gesto de un panel que
     se asoma por un lado, y aquí no hay lado del que asomarse. */
  animation: aparecer var(--t-media) both;
}

/* El modal de un formulario corto. Los mismos 480px que los `<dialog>` del
   panel, para que un diálogo mida igual se abra con JavaScript o sin él.
   Va por especificidad y no por orden: así no depende de dónde se escriba. */
.cajon--modal.cajon--estrecho { --ancho-modal: 480px; }

@keyframes aparecer {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes entrar-derecha {
  from { transform: translateX(16px); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

.cajon-alto {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--e3);
}

.cerrar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  text-decoration: none;
  transition: background-color var(--t-rapida), color var(--t-rapida),
              border-color var(--t-rapida);
}
/* La X del diálogo de `/comprar` nace escondida: sin JavaScript no hay
   diálogo que cerrar. */
.cerrar[hidden] { display: none; }
.cerrar:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto);
}

/* Pasos numerados para conseguir el código de autorización. */
.pasos {
  margin: 0; padding: 0; list-style: none;
  counter-reset: paso;
  display: flex; flex-direction: column; gap: var(--e3);
}
.pasos li {
  counter-increment: paso;
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--e3);
  font-size: 0.9375rem;
  color: var(--texto-suave);
}
.pasos li > div { min-width: 0; }

.pasos li::before {
  content: counter(paso);
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border: 1px solid color-mix(in srgb, var(--acento-hondo) 70%, transparent);
  border-radius: 50%;
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

/* Los dos botones del paso 1: abrir el enlace y copiarlo. En fila, y en
   columna en cuanto no quepan. La dirección de Epic ya no se enseña en
   pantalla: son tres URLs anidadas y codificadas, ilegibles y fáciles de
   romper al copiarlas a trozos. */
.acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
  margin-top: var(--e3);
}

/* En una fila de lista los botones ya van al final: no hay que separarlos de
   nada por arriba. */
.acciones--fila { margin-top: 0; flex: none; }

/* Un campo dentro de una fila de acciones se lleva el sitio que sobra, pero
   no baja de algo legible: por debajo, la fila se parte en dos líneas. */
.acciones .campo { flex: 1 1 14rem; min-width: 0; }

/* Los dos botones de lote van a la derecha, separados de la lista: son
   acciones sobre todo lo de abajo, no una fila más. */
.acciones--lote { justify-content: flex-end; margin-top: 0; }
/* La línea que explica un lote, a la izquierda de su botón: ocupa el hueco
   que deja el `flex-end` y, si no cabe, el botón baja debajo. */
.acciones-nota { flex: 1 1 16rem; margin: 0; align-self: center; }

/* El selector no: mide lo que mide su texto. Lo que crece es el campo del
   nombre, que es donde se escribe. */
.acciones .selector, .acciones .campo--selector { flex: none; }

/* Botón de fila: el mismo botón, con la densidad de una lista. */
.boton--menudo {
  padding: 6px var(--e3);
  font-size: 0.875rem;
}
.boton--menudo .icono { font-size: 18px; width: 18px; height: 18px; }

/* Cabecera de sección: título a la izquierda, su recuento a la derecha. */
.seccion-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
}

/* Título y flecha de plegar van juntos, por lo mismo que en una fila de amigo:
   sueltos en el flex de arriba, el `space-between` los habría separado y la
   flecha se habría quedado flotando en medio de la cabecera, lejos del título
   que abre. */
.seccion-titulo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
}

/* --- Los atajos del umbral de inactivos -----------------------------------
   Una fila de botones que rellenan el campo de días. **Envuelve**: son cuatro
   y en 380px no caben en una línea, y partirse es lo correcto — no encogerlos
   hasta que el rótulo no se lea. */
.atajos { display: flex; flex-wrap: wrap; gap: var(--e2); }

/* En reposo es un control neutro: el borde de control es `--borde-alto`, que
   es el que llega a 3:1 (CLAUDE.md, Reglas de UI). El acento se reserva para
   el que está puesto. */
.atajo {
  flex: none;
  padding: 6px var(--e3);
  font: inherit;
  font-size: 0.875rem;
  color: var(--texto);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  cursor: pointer;
  transition: background-color var(--t-rapida), border-color var(--t-rapida),
              color var(--t-rapida);
}

.atajo:hover {
  background-color: var(--superficie-3);
  border-color: var(--acento-alto);
  color: var(--texto-alto);
}

/* Puesto. El relleno hondo del acento con el tono claro encima, que es
   justamente el par que CLAUDE.md reserva para esto (`--acento-alto` sobre
   `--acento-tenue`). **El color no es el único canal**: lo que de verdad lo
   dice es el `aria-pressed` del marcado. */
.atajo[aria-pressed="true"] {
  background-color: var(--acento-tenue);
  border-color: var(--acento);
  color: var(--acento-alto);
}

/* --- La barra de la selección ---------------------------------------------
   Pegada arriba mientras se recorre la lista: con doscientas filas, el botón
   al final obligaba a bajar del todo para ver qué iba a pasar.

   `sticky` y no `fixed` por lo mismo que el panel de pedido: ocupa su sitio en
   el flujo, así que al cargar no tapa la primera fila — sólo pasa por encima
   al hacer scroll, que es para lo que está. */
.barra-seleccion {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e3);
  padding: var(--e3);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}

/* Un botón apagado tiene que parecerlo, y sin perder el 4,5:1: se baja la
   opacidad del conjunto y se le quita el cursor, no se le cambia el color del
   texto —que es lo que rompería el contraste medido del botón de acento—. */
.barra-seleccion .boton[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* En móvil, la hamburguesa va FIJA arriba a la izquierda —40px a 12px del
   borde, y con z-index 20—, así que una barra pegada a 0 se le metería debajo
   y se comería su propio botón. Se baja hasta pasarla.

   Sólo con JavaScript: sin él no hay hamburguesa y la barra lateral apilada se
   va con el scroll, así que arriba del todo está bien. Es la misma condición
   con la que se pinta la hamburguesa. */
@media (max-width: 860px) {
  .con-js .barra-seleccion { top: calc(52px + var(--e1)); }
}

.barra-seleccion [data-seleccion-vacia] { margin: 0; }
.barra-seleccion [data-seleccion-vacia][hidden] { display: none; }

/* Que una fila no se quede justo debajo de la barra al saltar a ella con el
   teclado: el foco tiene que verse entero, y la barra mide su alto. */
.amigo--elegible { scroll-margin-top: 72px; }

/* Una persona de la vista previa de inactivos: casilla y nombre a la
   izquierda, la fecha a la derecha. No es una `.amigo` normal —no lleva
   acciones ni cuentas externas—, así que recupera la fila de una línea que
   `.lista-amigos .amigo` había puesto en columna. Va con los dos selectores
   por eso mismo: con `.amigo--elegible` a secas pierde, que es 0,1,0 contra
   0,2,0. */
.lista-amigos .amigo--elegible {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  flex-wrap: wrap;
}

/* La casilla y lo que etiqueta van juntas y se pulsan juntas: el `<label>`
   envuelve a las dos, así que el área de clic es la fila y no un cuadrito de
   13px. */
.elegible-marca {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
  cursor: pointer;
}

/* La fecha no se parte: es lo que se compara de un vistazo al recorrer la
   lista, y partida en dos líneas deja de poder compararse. */
.elegible-fecha { flex: none; white-space: nowrap; }

/* Lo que recoge un botón `.plegar` de bloque. No pinta nada —la caja de arriba
   ya es la tarjeta—: sólo existe para tener algo que esconder de una pieza. */
.plegado[hidden] { display: none; }

/* Sólo para lectores de pantalla. Un `display: none` los dejaría fuera; esto
   lo saca de la vista sin sacarlo del árbol de accesibilidad. */
/* **`[hidden]` gana siempre, y esto está aquí porque siete veces no ganó.**

   El navegador trae `[hidden] { display: none }` en su hoja de usuario, y lo
   del autor le gana siempre — no es especificidad, es que la cascada pone la
   del autor por delante de la del navegador—. Así que cualquier regla nuestra
   con un `display` distinto de `none` dejaba al elemento **visible aunque
   llevara el atributo**, y eso no se nota hasta verlo en la pantalla: el filtro
   de amigos que no filtraba, el aviso de conexión caducada saliendo junto a un
   contador que decía "9:54", las cajas del diálogo del pedido…

   Con esto, la regla del autor existe y el caso se cierra de raíz. El `!important`
   es lo único que gana a un `display` de una regla más específica, y es justo
   para lo que sirve: **una excepción de todo el documento, no un parche local**.

   Las líneas `.clase[hidden] { display: none }` que hay repartidas por la hoja
   se quedan, y el test que las exige también: son la red por si alguien mueve
   esta regla, y documentan en su sitio que ese elemento nace escondido. Si
   alguna vez hiciera falta un elemento con `hidden` **y** visible —que no ha
   hecho falta nunca—, lo que se cambia es el atributo, no esta regla. */
[hidden] { display: none !important; }

.sr-solo {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Dos formas de decir lo mismo según haya guion o no. Para cuando la salida de
   un estado depende de él: el aviso de que el código se está aplicando termina
   solo si algo puede recargar la página, y a mano si no.

   Lo de sin JavaScript es lo que se pinta de partida y `.con-js` lo cambia, no
   al revés: si el script del <head> no llega a correr, lo que queda puesto es
   la versión que funciona sin él. */
.solo-con-js { display: none; }
.con-js .solo-con-js { display: inline; }
.con-js .solo-sin-js { display: none; }

/* Confirmación de copiado: color de acierto y un pulso muy corto. El texto
   del botón también cambia, que es lo que se lee sin ver el color. */
.boton[data-copiado] {
  border-color: var(--ok);
  color: var(--ok-alto);
  animation: pulso 320ms var(--curva);
}

@keyframes pulso {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/* Un <code> corto dentro de una frase: inline, sin caja ni salto. */
code.suelto {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.8125rem;
  color: var(--texto);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  /* La mitad del menudo: es una caja de una línea dentro de un párrafo, y con
     los 8px de --r-menudo se abombaría. Es el único radio de la hoja que no
     sale de una variable, y por eso lleva su porqué escrito. */
  border-radius: calc(var(--r-menudo) / 2);
  padding: 1px 5px;
  white-space: nowrap;
}

/* ==========================================================================
   Indicador de espera
   ==========================================================================

   Conectar una cuenta, abrir Amigos o procesar un lote son navegaciones
   normales que por dentro hablan con Epic: pueden tardar segundos. El
   navegador ya pinta su barra de carga, pero es la suya y no dice QUÉ se está
   esperando. Esto sí, y con la cara del panel.

   No es un círculo giratorio: es la misma luz teal que recorre los botones al
   pasar por encima, aquí en bucle, y debajo una pastilla con el nombre de la
   espera en crema. Teal para el movimiento, crema para lo que se lee.

   Aparece SÓLO si la espera pasa de 300ms (lo mide base.html): por debajo, la
   página ya ha cambiado y encenderlo y apagarlo es un parpadeo, que se lee
   como que algo ha fallado.

   Sin JavaScript no existe: llega `hidden` del servidor y nadie lo enciende.
   Ahí queda la barra de carga del navegador, que hace este mismo trabajo. */
.cargando {
  position: fixed;
  top: 0; left: 0; right: 0;
  /* Por encima del cajón (40) y de su velo (30): es lo último que pasa. */
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e4);
  /* Informa, no interrumpe: no atrapa ni un clic. */
  pointer-events: none;
}
.cargando[hidden] { display: none; }

/* La barra. El degradado mide el doble que la caja y lo que se mueve es su
   POSICIÓN, que es de lo barato que hay: ni maquetación ni repintado de más.
   Va y vuelve (`alternate`) en lugar de saltar al empezar de nuevo: aquí nada
   rebota ni da tirones. */
.cargando-barra {
  width: 100%;
  height: 3px;
  background-color: var(--acento-tenue);
  background-image: var(--grad-acento);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  /* 1260ms, que es el medidor (420ms) por tres. Una espera no es una
     transición entre dos estados —no termina—, así que no puede durar 120,
     180 ni 420: es el tiempo que tarda la luz en cruzar sin agobiar. */
  animation: cargando-luz 1260ms var(--curva) infinite alternate;
}

@keyframes cargando-luz {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}

/* La pastilla dice qué se espera, y lo dice con nombre: "Conectando WolfxFB2",
   no "Cargando". Es un `role="status"`, así que un lector de pantalla la canta
   sola en cuanto aparece. */
.cargando-aviso {
  margin: 0;
  max-width: min(90vw, 26rem);
  padding: var(--e2) var(--e4);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  background-image: var(--grad-superficie);
  color: var(--texto);
  font-size: 0.9375rem;
  text-align: center;
  animation: cargando-entra 180ms var(--curva);
}

@keyframes cargando-entra {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* Sin movimiento no hay recorrido: la barra se queda entera y quieta, con el
   teal claro a la izquierda. Es un cambio de color que persiste, que es el
   canal que informa cuando se quita la animación, y lo que hay que leer sigue
   estando en la pastilla, que es la que nombra la espera. */
@media (prefers-reduced-motion: reduce) {
  .cargando-barra {
    animation: none;
    background-position: 0% 50%;
  }
  .cargando-aviso { animation: none; }
}

/* --- Responsive, hasta 380px --------------------------------------------

   SIN JavaScript la barra deja de ser una columna a un lado y pasa a ser una
   cabecera de tres filas. Es el respaldo: ocupa sitio, pero es la única forma
   de que se llegue a la navegación sin poder abrir ni cerrar nada.
   Con JavaScript se convierte en un cajón (más abajo).

   En una sola fila no cabía: la marca, la cuenta conectada con sus dos
   indicadores, cinco secciones y el botón de salir no entran ni de lejos en
   380px, y la barra acababa desbordándose de lado.

   Se hace con `grid-template-areas` y no reordenando el HTML: el orden del
   marcado es el bueno para leer y para tabular, y sólo cambia dónde se pinta
   cada cosa.

       +---------------------------+-------+
       | LIMGO  v1                 | salir |   marca | pie
       +---------------------------+-------+
       | (o) CuentaDeA  13.500  5/5        |   cuenta
       +-----------------------------------+
       | [ ] [ ] [ ] [ ] [ ]   (iconos)    |   nav
       +-----------------------------------+
*/
@media (max-width: 860px) {
  .disposicion { grid-template-columns: 1fr; }

  html:not(.con-js) .lateral {
    position: static;
    height: auto;
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "marca  pie"
      "cuenta cuenta"
      "nav    nav";
    align-items: center;
    gap: var(--e2) var(--e3);
    /* Nada de `overflow-x` en la barra entera: lo que no cabe se apila, y si
       algo tiene que desplazarse es sólo la navegación. */
    overflow: visible;
    border-right: 0;
    border-bottom: 2px solid var(--borde-alto);
    padding: var(--e3) var(--e4);
  }

  html:not(.con-js) .lateral-marca { grid-area: marca; }

  /* La cuenta conectada, de lado a lado y en una línea: nombre y las dos
     cifras. Las líneas de separación pasan de los lados a arriba y abajo,
     que es por donde ahora la limita algo. */
  html:not(.con-js) .lateral-cuenta {
    grid-area: cuenta;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--e1) var(--e3);
    padding: var(--e2) 0;
    border-left: 0;
    border-right: 0;
    border-top: 1px solid var(--borde);
    border-bottom: 1px solid var(--borde);
  }
  html:not(.con-js) .lateral-quien { flex: 0 1 auto; }
  /* Las pastillas ya van en fila de serie: aquí sólo se les quita el salto de
     línea, para que la barra plegada no crezca de alto. */
  html:not(.con-js) .indicadores { flex-wrap: nowrap; }

  /* Sólo la navegación puede desplazarse, y con los rótulos escondidos entran
     las cinco secciones sin llegar a hacerlo salvo en pantallas muy estrechas.
     `-webkit-overflow-scrolling` no hace falta: es una fila corta. */
  html:not(.con-js) .nav {
    grid-area: nav;
    flex-direction: row;
    gap: var(--e1);
    overflow-x: auto;
    scrollbar-width: thin;
  }
  html:not(.con-js) .nav a { padding: var(--e2); flex: none; }
  html:not(.con-js) .nav a::before { display: none; }
  html:not(.con-js) .nav-titulo { display: none; }
  /* `contents` y no `display: flex`: los enlaces tienen que ser hijos
     directos de `.nav` para que la fila desplazable los coloque uno tras
     otro. Con los grupos como cajas saldrían tres bloques apilados. */
  html:not(.con-js) .nav-grupo { display: contents; }
  html:not(.con-js) .nav .nav-pronto { display: none; }

  html:not(.con-js) .lateral-pie { grid-area: pie; margin-top: 0; }

  /* Los rótulos se esconden, NO se quitan: con `display: none` también salen
     del árbol de accesibilidad y los botones se quedarían sin nombre. Estas
     son las mismas reglas de `.sr-solo`. */
  html:not(.con-js) .nav a span:not(.icono),
  html:not(.con-js) .lateral-pie .boton span:not(.icono) {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  html:not(.con-js) .lateral-pie .boton { padding: var(--e2); }
}



/* --- El cajón: la barra lateral en móvil, con JavaScript -----------------

   Apilada arriba ocupaba tres filas de cabecera en todas las páginas. Como
   cajón no ocupa nada hasta que se abre, y dentro cabe la barra ENTERA tal
   como se ve en escritorio: marca, cuenta conectada con sus cifras, la
   navegación con sus rótulos y "Cerrar sesión" al final.

   `.con-js` lo pone un script de una línea en el <head>, antes de pintar: si
   se pusiera al final del body se vería un instante la barra apilada y luego
   saltaría. Sin JavaScript esa clase no existe y no se aplica nada de aquí. */
@media (max-width: 860px) {
  /* Fija arriba a la izquierda. En el flujo normal se iba con la fila al bajar
     por la página, y para abrir el menú había que volver al principio; en una
     tienda de trescientas tarjetas eso es un viaje.

     Va por debajo del velo (30) y del cajón (40): con el menú abierto queda
     tapada por el propio cajón, que es lo suyo. **No hay un botón de cerrar
     dentro** —esto decía que sí y no era verdad—: se cierra pulsando el velo,
     con Escape o eligiendo una sección, que son las tres formas que documenta
     PROGRESS.

     Medida a mano, 40x40, y **por eso mismo se le puede reservar el alto**:
     fija deja de ocupar sitio en la fila, y sin reserva el resto de la página
     subiría esos 40px y se metería justo debajo del botón. El ancho no hace
     falta reservarlo: el rincón de la sesión va pegado a la derecha. */
  .con-js .hamburguesa {
    display: inline-flex;
    position: fixed;
    top: var(--e3);
    left: var(--e3);
    z-index: 20;
    width: 40px;
    height: 40px;
  }
  .con-js .alto-pagina { min-height: 40px; }

  /* **Y lo que se trae a la vista se queda por debajo de ella.** Un ancla
     (`#crear_codigo`), un `scrollIntoView` o el foco que salta con el teclado
     dejan su destino arriba del todo, y arriba del todo está el botón: 40px a
     12px del borde, más otros 12 de aire. Al cargar el título ya deja ese
     hueco (`min-height` de arriba); esto lo mantiene cuando la página se
     mueve sola. Con `:has()` porque `.con-js` es el `<html>` y `/fn/` también
     lo lleva: allí no hay hamburguesa ni franja que reservar. */
  .con-js:has(.hamburguesa) { scroll-padding-top: 64px; }

  /* Con un diálogo abierto, la hamburguesa se va.

     `.velo` sólo está en el marcado mientras hay un diálogo abierto —lo pinta
     el servidor con su `{% if %}`—, así que su presencia ES la pregunta "¿hay
     algo modal delante?".

     Antes se quedaba visible y no hacía nada: el velo va por encima y se comía
     el clic, así que era un botón que se ve y no responde, que es peor que uno
     que no está. Se esconde en vez de subirla por encima del velo a propósito:
     el diálogo es modal, y sacar de él por una puerta lateral es justo lo que
     un modal no debe ofrecer.

     `:has()` es la única forma de mirar hacia abajo sin tocar todas las
     plantillas para que pongan una clase en el `<html>`. Donde no exista
     —navegador de antes de 2023—, no se aplica y queda como estaba: visible e
     inerte, que es de donde venimos. No es una regresión, es no arreglarlo.

     `.velo-menu`, el del propio cajón, NO cuenta: es otra clase, y mientras el
     cajón está abierto el botón es precisamente el que lo cierra. */
  .con-js:has(.velo) .hamburguesa { display: none; }

  /* Y con el cajón abierto, por el mismo motivo: queda debajo de él, así que
     enseñarla es enseñar un botón que no se puede pulsar. Cerrar es del botón
     de dentro, que sí está en el foco atrapado. */
  .con-js:has(.lateral--abierta) .hamburguesa { display: none; }

  /* El botón de cerrar del cajón. Sólo aquí: en escritorio la barra no se
     cierra. Va centrado y empujado a la derecha porque `.lateral-marca` alinea
     por la base —lo bueno para el nombre y su versión, no para un icono—. */
  .con-js .cerrar-menu {
    display: inline-flex;
    margin-left: auto;
    align-self: center;
  }

  .con-js .lateral {
    position: fixed;
    top: 0; bottom: 0; left: 0;
    z-index: 40;
    width: min(300px, 86vw);
    height: auto;
    /* **Esto no sobra, y quitarlo rompe el cajón entero.**

       Arriba, para la columna de escritorio, `.lateral` lleva
       `align-self: start`: sin él, la barra `sticky` se estiraría al alto del
       grid en vez de al de la ventana. El problema es que `.disposicion` es un
       grid y la alineación **también se le aplica a un hijo `position: fixed`**:
       `start` le dice "no te estires", así que anula el `top: 0` + `bottom: 0`
       de dos líneas más arriba y el cajón se dimensiona por su contenido.

       Con eso, el cajón medía 1000px —1091 con cuenta conectada— dentro de una
       ventana de 780, y como el elemento medía exactamente lo que mide su
       contenido, el `overflow-y: auto` de abajo no tenía nada que desbordar:
       `scrollHeight == clientHeight`, o sea que tampoco se podía desplazar. El
       final de la barra quedaba fuera de la pantalla y sin forma de llegar:
       "Cerrar sesión" no se alcanzaba en ningún tamaño, y con cuenta conectada
       tampoco Amigos ni Código de creador.

       Medido en Chromium a 360x800, 390x844 y 360x640. Es lo que pasa cuando
       una regla pensada para el escritorio llega intacta al móvil, y se nota
       tarde porque el síntoma —falta el final del menú— aparece sólo cuando la
       navegación crece lo bastante. */
    align-self: stretch;
    padding: var(--e6) var(--e4);
    overflow-y: auto;
    border-right: 2px solid var(--borde-alto);
    border-bottom: 0;
    /* Entra deslizándose. `visibility` además del `transform` para que, ya
       cerrado, no se pueda tabular a nada de dentro. */
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--t-media), visibility var(--t-media);
  }
  /* Al ABRIR, `visibility` no se transiciona; al cerrar, sí.

     La lista de propiedades que se animan la pone el estado al que se va, así
     que aquí se declara la del abrir y arriba queda la del cerrar. No es un
     capricho: `visibility` interpola de una forma especial —con `hidden` en el
     extremo de partida, el valor sigue siendo `hidden` mientras el progreso
     es 0— y el progreso no avanza hasta el segundo fotograma. Medido en
     Chromium: síncrono `hidden`, tras forzar reflow `hidden`, primer
     `requestAnimationFrame` `hidden`, y a partir del segundo `visible`.

     Eso dejaba el `focus()` del guion sin efecto al abrir: el navegador no
     enfoca lo que está en un subárbol invisible, así que el foco se quedaba
     fuera y sólo entraba al primer Tab, que es cuando la trampa lo recoge.
     Con la transición del abrir sin `visibility`, el cajón es enfocable en el
     mismo instante en que se le pone la clase y el foco entra solo.

     Se arregla aquí y no con dos `requestAnimationFrame` en el guion a
     propósito: lo que estaba mal era el CSS, y una espera de dos fotogramas es
     de esas cosas que nadie sabe por qué están tres meses después.

     **El cerrar sigue necesitando su `visibility` en la transición**, y por eso
     no se toca arriba: sin ella el cajón desaparecería de golpe en vez de
     salir deslizándose. */
  .con-js .lateral--abierta {
    transform: none;
    visibility: visible;
    transition: transform var(--t-media);
  }

  /* Y los cuatro bloques de dentro no animan nada.

     Parece de más y no lo es. La regla global de `prefers-reduced-motion` le
     pone `transition-duration: 0.01ms !important` a **todo**, y estos cuatro no
     declaran `transition`, así que se quedan con `transition-property: all` y
     una duración: con eso pasan a transicionar la `visibility` que heredan del
     cajón. En el primer instante siguen en `hidden` —el padre ya es
     `visible`— y el botón de cerrar hereda ese `hidden`, que es justo lo que
     impide enfocarlo.

     O sea: con "reducir movimiento" activado, el foco no entraba **aunque** la
     transición del cajón ya no toque `visibility`. Medido en Chromium: con
     movimiento normal, ninguna animación en los hijos; con movimiento
     reducido, cuatro transiciones de `visibility`.

     `transition-property` y no `transition`: la duración la clava la regla
     global con `!important` y no se le puede ganar, pero sin propiedades que
     animar da igual lo que dure. */
  .con-js .lateral > * { transition-property: none; }

  /* El velo. Funde, como el del cajón de vincular. */
  .con-js .velo-menu {
    position: fixed;
    inset: 0;
    z-index: 30;
    background-color: color-mix(in srgb, var(--fondo) 72%, transparent);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--t-media), visibility var(--t-media);
  }
  .con-js .velo-menu--visible {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
}

/* Fuera del cajón no existen ni el velo ni el botón de cerrar: ni en
   escritorio ni sin JavaScript. En los dos casos la barra está siempre a la
   vista y no hay nada que cerrar. */
.velo-menu, .cerrar-menu { display: none; }
@media (max-width: 860px) { .con-js .velo-menu { display: block; } }

@media (max-width: 480px) {
  .principal { padding: var(--e4); }
  .rejilla { grid-template-columns: 1fr; }
  .cabecera { align-items: flex-start; }
  .cajon { padding: var(--e4); }
}

@media (max-width: 380px) {
  .principal { padding: var(--e3); }
  .cuenta { padding: var(--e3); }
  .cabecera .boton { width: 100%; justify-content: center; }
}

/* --- Sin movimiento ------------------------------------------------------
   Se respeta siempre: aquí no queda ni una transición, ni un desplazamiento,
   ni un pulso. El cambio de color sigue estando, que es lo que informa. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .boton:hover, .boton:active, .cuenta:hover { transform: none; }
  /* La tarjeta que se arrastra se distingue igual: cambia el borde y la
     opacidad, que es lo que informa. */
  .boton:hover .icono { transform: none; }
  .boton[data-copiado] { animation: none; }
  /* El robot no parpadea: el botón sigue cambiando de luz y de color, que es
     lo que informa de que está bajo el puntero. */
  .boton:hover .icono--bot .bot-ojos,
  .boton:focus-visible .icono--bot .bot-ojos { animation: none; }
}


/* ==========================================================================
   Acciones largas y su aviso de progreso
   ========================================================================== */

/* La fila de la pestaña Acciones: a la izquierda qué hace, a la derecha el
   botón. Se apila en móvil porque el texto explica algo irreversible y
   comprimirlo a dos palabras por caber al lado del botón sería peor. */
.accion-larga {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--e4);
  flex-wrap: wrap;
}
.accion-larga > div { flex: 1 1 260px; min-width: 0; }
.accion-larga-titulo { font-size: 1rem; margin: 0 0 var(--e1); }

/* "Este proceso puede tardar unos 5 minutos", con el reloj delante. El icono
   se alinea con la primera línea y no con el centro del párrafo: con dos
   líneas de texto, centrado se queda flotando en mitad de la nada. */
.tiempo {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.875rem;
}
.tiempo .icono { flex: none; color: var(--texto-tenue); }

/* En su propia línea: es lo que impide leer la cifra de arriba como una
   promesa, y pegado detrás se lee como parte de ella. */
.tiempo-matiz { display: block; color: var(--texto-tenue); }

/* El aviso de progreso. Flota abajo a la derecha y sobrevive a la navegación
   porque lo pinta el servidor en cada página, no el JavaScript.

   Sin sombra, como todo: se despega del fondo con el escalón de superficie, el
   borde alto y un filo de luz arriba, que es el mismo recurso con el que se
   eleva la tarjeta de cuenta. */
.trabajo {
  position: fixed;
  right: var(--e4);
  bottom: var(--e4);
  /* Por encima del contenido, y por DEBAJO del velo del cajón móvil (30) y del
     cajón (40): el cajón es modal, así que mientras está abierto no puede
     quedar nada flotando por encima. El indicador de espera sigue siendo el
     último de todos (50). */
  z-index: 25;
  width: min(320px, calc(100vw - var(--e4) * 2));
  padding: var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
/* El filo de luz: 2px de teal arriba, que es lo que lo levanta del fondo. */
.trabajo::before {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 2px;
  border-radius: var(--r-tarjeta) var(--r-tarjeta) 0 0;
  background-image: var(--grad-acento);
}
.trabajo[hidden] { display: none; }

.trabajo-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e2);
}
.trabajo-titulo {
  font-size: 0.9375rem;
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.trabajo-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.trabajo-cuerpo[hidden] { display: none; }

/* La cifra no cambia de familia ni de tamaño: se lee dentro de la frase que la
   explica (CLAUDE.md, Cifras). Sólo `tabular-nums`, para que no baile al subir. */
.trabajo-progreso { margin: 0; font-size: 0.875rem; }
.trabajo .medidor { max-width: none; }
.trabajo-acciones { display: flex; }

/* En móvil se estira de lado a lado, que es donde hay sitio. */
@media (max-width: 480px) {
  .trabajo {
    left: var(--e3);
    right: var(--e3);
    bottom: var(--e3);
    width: auto;
  }
}

/* Los avisos flotantes del envío de la Tienda Gift: el resultado de «Enviar
   ahora» cuando el regalo sale por detrás y la página no se vuelve a pintar.

   **Una pila abajo a la derecha**, y cada envío es un aviso propio: el nuevo
   entra abajo y los de antes suben (`flex-direction: column` anclado por
   abajo). Abajo vive también el aviso de progreso de los trabajos; si está a
   la vista, el guion sube la pila por encima de él (`--pila-sobre`).

   **No tapa el botón de enviar**, y no por suerte: el diálogo del pedido se
   abre con `showModal()` y vive en la capa superior, por encima de cualquier
   `position: fixed`.

   La barra del sistema en móvil: `safe-area-inset-bottom`. Hoy vale 0 —la
   meta `viewport` no lleva `viewport-fit=cover`, así que es el navegador quien
   deja la página fuera de esa barra—, y está escrito para el día que lo lleve.

   La misma pieza que el aviso de progreso —superficie, borde alto y el filo
   de luz, sin sombra—. El estado no lo da el fondo, que es el de siempre: lo
   dan el icono, en el `-alto` de su color porque se lee, y la palabra. Mismo
   z-index que el de progreso, por debajo del cajón. */
.avisos-flotantes {
  position: fixed;
  right: var(--e4);
  bottom: calc(var(--e4) + env(safe-area-inset-bottom, 0px) + var(--pila-sobre, 0px));
  z-index: 25;
  width: min(360px, calc(100vw - var(--e4) * 2));
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  /* La pila vacía no puede quedarse con los clics de lo que hay debajo. */
  pointer-events: none;
}

.aviso-flotante {
  position: relative;
  pointer-events: auto;
  padding: var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
}
.aviso-flotante::before {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 2px;
  border-radius: var(--r-tarjeta) var(--r-tarjeta) 0 0;
  background-image: var(--grad-acento);
}
.aviso-flotante[hidden] { display: none; }

.aviso-flotante-linea {
  flex: 1;
  min-width: 0;
  margin: 0;
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  color: var(--texto);
  font-size: 0.9375rem;
  overflow-wrap: anywhere;
}
.aviso-flotante-linea[hidden] { display: none; }
.aviso-flotante-linea .icono { flex: none; }
.aviso-flotante-linea--ok .icono { color: var(--ok-alto); }
.aviso-flotante-linea--error .icono { color: var(--peligro-alto); }

@media (max-width: 480px) {
  .avisos-flotantes {
    left: var(--e3);
    right: var(--e3);
    bottom: calc(var(--e3) + env(safe-area-inset-bottom, 0px) + var(--pila-sobre, 0px));
    width: auto;
  }
}

/* --------------------------------------------------------------------------
   Tienda Gift
   --------------------------------------------------------------------------
   Rejilla de artículos. Las piezas son pequeñas y son muchas —la tienda real
   ronda las 300 ofertas—, así que la columna es más estrecha que la de las
   cuentas: caben seis por fila en un portátil y siguen leyéndose.

   Sin colores por rareza. `--acento` es el único color de acción y el resto de
   la paleta no tiene un tono por cada rareza de Fortnite; inventarlos sería
   meter siete colores nuevos que además compiten con el teal. La rareza va
   escrita en su pastilla, que es lo que se lee igual de bien. */
.rejilla--tienda { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.rejilla--tienda > [hidden] { display: none; }

/* El degradado tiñe la tarjeta entera y llega hasta el borde, así que el
   relleno vive en los hijos y no aquí. `overflow: hidden` recorta el color a
   las esquinas redondeadas. */
.articulo {
  position: relative;
  display: flex;
  flex-direction: column;
  /* El fondo NO se declara aquí: es el primer escalón de la cadena de
     respaldos y vive con los otros dos, más abajo. Ver "Respaldo por rareza". */
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  overflow: hidden;
  min-width: 0;
  /* Para que el `max-height` de la foto se pueda medir en `cqw`, o sea contra
     el ancho de esta tarjeta y no contra la ventana. */
  container-type: inline-size;
  transition: transform var(--t-media), border-color var(--t-media);
}
.articulo:hover {
  transform: translateY(-2px);
  border-color: var(--borde-alto);
}

/* Aquí NO va el filo teal que llevan las tarjetas de cuenta. El teal es el
   color de lo accionable y esta tarjeta no se pulsa —es un `<li>`, no hay nada
   que hacer con ella todavía—; sobre un fondo de color propio, además, se
   leería como un botón. El hover se marca con el borde, que es neutro. */

/* Cuadrada de partida, y **se queda el hueco que sobre**.

   Las tarjetas de una fila igualan altura, así que la más alta —un nombre de
   dos líneas, una rareza larga— estira a las demás. Ese sobrante caía entre la
   foto y el texto y se veía como una banda de color debajo de la imagen: no
   parecía aire, parecía que la foto estaba cortada. Con `flex-grow` la zona de
   la foto se lo queda y la imagen se centra dentro, así que el color queda
   repartido arriba y abajo en vez de amontonado en un canto.

   `flex-basis: auto` y no `0`: la base tiene que seguir siendo el cuadrado del
   `aspect-ratio`. Con `flex: 1` a secas la base sería cero y la foto de la
   tarjeta más corta de la fila se encogería hasta desaparecer. Y sin encoger,
   que el cuadrado es el suelo.

   El crecimiento tiene tope: `max-height` en `cqw`, o sea en tanto por ciento
   del ancho de la propia tarjeta. Sin él, un nombre de tres líneas en una sola
   tarjeta estira la zona de imagen de toda la fila y las fotos quedan
   desproporcionadas respecto a lo que miden de ancho. Con 120 puede crecer un
   quinto de su ancho y ahí se planta; lo que sobre a partir de ahí vuelve al
   `margin-top: auto` del cuerpo, que es un canto acotado en vez de uno
   abierto. Va en unidades de contenedor y no en píxeles porque la tarjeta mide
   cosas muy distintas según el ancho de pantalla: un tope fijo sería enorme en
   móvil y ridículo en escritorio.

   El cuadrado se reserva desde el principio, como antes: la altura no depende
   de que la imagen llegue, así que la rejilla no da un salto al cargarse las
   fotos. Sin fondo propio: el color lo pone la tarjeta y esto lo deja ver. */
.articulo-foto {
  flex: 1 0 auto;
  aspect-ratio: 1;
  max-height: 120cqw;
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* La imagen llena la caja y se recorta por los lados: es el reparto de
   siempre, el que no deja huecos con ninguna forma, y **el que se ve sin
   JavaScript**. La colocación por forma es un añadido encima de esto, no un
   sustituto: si el guion no corre, esto es lo que queda y no se ve roto.

   La otra colocación, `.foto--apoyada`, está con la del historial: las dos
   rejillas la comparten en el mismo selector. */
.articulo-foto img,
.operacion-foto img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Apoyada en la base, que es lo que hace el guion con todo lo que no es una
   imagen ancha. `contain` la mete entera dentro de la caja y `bottom` la pega
   abajo, así que cuando la fila estira la tarjeta el hueco sale ARRIBA y la
   pieza se queda de pie sobre la línea del nombre, como si tuviera suelo. En
   una caja cuadrada —lo normal— `contain` y `cover` dan lo mismo: esto sólo se
   nota justo en el caso que lo motiva. */
.articulo-foto img.foto--apoyada,
.operacion-foto img.foto--apoyada {
  object-fit: contain;
  object-position: bottom;
}

/* Lo que Epic vende y ninguna API supo identificar. Se enseña igual: se puede
   regalar, y esconderlo sería esconder algo que está a la venta. */
.articulo-sinfoto { color: var(--texto-tenue); }
.articulo-sinfoto .icono { font-size: 32px; width: 32px; height: 32px; }

.articulo-nombre {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* El bloque de texto, sobre el color. Lleva el velo que garantiza que se lea
   con cualquier degradado que mande Epic: ver `--velo-tarjeta`. */
.articulo-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding: var(--e3) var(--e4) var(--e4);
  background-image: var(--velo-tarjeta);
  /* El sobrante de la fila se lo queda ahora la foto, así que esto ya no tiene
     hueco que absorber. Se deja porque sigue siendo la garantía de que el velo
     llega abajo del todo: si algún día la tarjeta se queda sin foto, vuelve a
     hacer falta. */
  margin-top: auto;
}

/* Todo en crema, y la jerarquía por tamaño. Medido contra las 327 ofertas
   reales del 28/08/2026 sobre el velo: el crema pasa AA en las 327 (peor caso
   8,04:1). Con los tonos de la v2, `--texto-suave` fallaba en 76 y
   `--texto-tenue` en 171; los de la v3 son más claros y fallan menos, pero
   sobre la oferta más clara de ese día siguen dejando 4,21:1 y 3,34:1. */
.articulo-cuerpo .articulo-nombre,
.articulo-cuerpo .articulo-precio,
.articulo-cuerpo .articulo-rareza,
.articulo-cuerpo .articulo-caduca { color: var(--texto-alto); }

/* Precio, rareza y cuenta atrás: tres líneas cortas y apretadas. */
.articulo-precio {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--e2);
  font-size: 0.9375rem;
  color: var(--texto-alto);
}

.articulo-rareza {
  margin: 0;
  font-size: 0.75rem;
}

.articulo-caduca {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--e1);
  font-size: 0.75rem;
}
/* Baja de 20px, así que baja también la caja o el glifo se descentra. */
.articulo-caduca .icono { font-size: 14px; width: 14px; height: 14px; }

/* Filtros por categoría. Son enlaces con `?categoria=`, como las pestañas de
   Amigos: enlazables, sin JavaScript y sin estado que se pierda al recargar. */
.filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e1);
}

.filtro {
  padding: var(--e1) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  text-decoration: none;
  font-size: 0.8125rem;
  transition: background-color var(--t-rapida), color var(--t-rapida),
              border-color var(--t-rapida);
}
.filtro:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto);
}

/* El activo no se marca sólo con el fondo: también cambia el color del texto,
   que es el canal que queda cuando el fondo no se distingue. */
.filtro--activo {
  background-color: var(--acento-tenue);
  border-color: var(--acento-hondo);
  color: var(--acento-alto);
  font-weight: 600;
}

/* En móvil la fila no se parte: se desplaza.

   Con siete categorías y el área táctil de 44px, a 360px se partía en tres
   renglones y **140px de filtros** empujaban la rejilla —lo que se ha venido a
   mirar— fuera de la primera pantalla. Medido; a 480 eran dos renglones y 92px.
   En una sola fila que se arrastra son 44 y siempre los mismos, sea cual sea el
   número de categorías, que es lo que hace que esto no se vuelva a torcer al
   añadir la octava.

   Es el patrón que pide CLAUDE.md para el contenido ancho: desplaza **dentro
   de su caja**, y la página nunca se mueve a lo ancho.

   `flex: none` en las pastillas no es adorno: sin él, `nowrap` las aprieta
   hasta que "Accesorios (123)" cabe a la fuerza y el texto se recorta. */
@media (max-width: 860px) {
  .filtros {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* Sitio para el filo del foco, que si no lo recorta el desplazamiento. */
    padding-bottom: var(--e1);
  }
  .filtro { flex: none; }
}

/* La regla de móvil de esta rejilla no está aquí: la comparte con la del
   historial y vive con ella, que es la que se declara después. Puesta aquí, la
   `.rejilla--historial` de más abajo le ganaría por orden —las dos son una sola
   clase, así que decide quién va última— y el historial se quedaría con una
   columna. Ver "Dos por fila en móvil". */

/* --------------------------------------------------------------------------
   El color de la tarjeta de tienda: la única excepción a la paleta
   --------------------------------------------------------------------------
   CLAUDE.md dice que el morado es el único acento y que nada más significa
   algo accionable. **Aquí hay una excepción, y es a propósito**: la tarjeta se
   pinta con el degradado que Epic le da a esa oferta en su propio escaparate,
   para que se parezca a lo que el vendedor ve en el juego.

   Son DOS fuentes, y la primera manda:

     1. `--c1/--c2/--c3`, que vienen en el `style` de cada tarjeta desde el
        `colors` de /v2/shop. **Es por artículo, no por rareza**: Rubius es
        Serie de ídolos y sale violeta-turquesa (`57239b` -> `08bcba`) en vez
        de dorado, porque Epic se lo define así.
     2. `--rareza-*`, el color de la rareza, para cuando la oferta no trae
        colores. Es un respaldo, no la norma.

   `.articulo--color` va DESPUÉS de las clases de rareza a propósito: las dos
   son de una sola clase, así que a igual especificidad gana la última, y la de
   Epic tiene que ganar.

   La excepción está acotada:

     - **No tiñe nada accionable**: ni bordes de control, ni botones, ni focos.
       En estas tarjetas no hay nada que pulsar, y por eso tampoco llevan el
       filo teal de hover que sí tienen las de cuenta.
     - **El texto que va encima se lee siempre**, porque el bloque de texto
       lleva `--velo-tarjeta`. Comprobado numéricamente contra las 327 ofertas
       reales, no contra un ejemplo.
     - **La palabra sigue estando**: el color nunca es el único canal. Quien no
       distinga los tonos lee "Épico" debajo.

   Los tonos de rareza son hex de Fortnite, no de la paleta, y por eso son hex
   y no variables de superficie. Se declaran igual como variables —CLAUDE.md
   prohíbe el hex suelto dentro de un componente— y cada una es un degradado en
   el mismo eje de 163° que las superficies. */
:root {
  /* El velo del bloque de texto. Es lo que hace legible cualquier color que
     mande Epic, incluido un `ffee00`.

     El suelo es 0,72 arriba del bloque y sube a 0,94 abajo. Ese 0,72 no es un
     número bonito: sobre BLANCO puro —el peor caso posible— deja un #4f4e53
     que da 7,71:1 con el crema, muy por encima del 4,5:1 de AA. Contra la
     oferta más clara del 28/08/2026 da 8,14:1.

     El velo se pinta con el hex del fondo escrito a mano, porque `rgba()` no
     acepta una variable: al mover `--fondo` hay que moverlo aquí también, y de
     ahí que estas cuentas suban un pelo respecto a las de la v3.

     Si alguien lo baja, que rehaga la cuenta: por debajo de 0,58 el peor caso
     deja de pasar AA. (La v3 decía 0,62; el número estaba mal y la cuenta
     rehecha da 0,58. El 0,72 no se mueve: el margen es para no ir justo.) */
  --velo-tarjeta: linear-gradient(
    to bottom, rgba(13, 11, 18, 0.72), rgba(13, 11, 18, 0.94)
  );

  /* Las cinco rarezas de siempre. */
  --rareza-common:        linear-gradient(163deg, #6d7278, #4a4e54);
  --rareza-uncommon:      linear-gradient(163deg, #4f9e3a, #2f6a22);
  --rareza-rare:          linear-gradient(163deg, #2f7fd4, #1c4f8c);
  --rareza-epic:          linear-gradient(163deg, #8f43cf, #5b2688);
  --rareza-legendary:     linear-gradient(163deg, #d1732c, #8c4515);

  /* Las de serie, con su tono propio. */
  --rareza-mythic:        linear-gradient(163deg, #d8b13a, #8f6f13);
  --rareza-icon:          linear-gradient(163deg, #33c9c9, #12666b);
  --rareza-gaminglegends: linear-gradient(163deg, #6b4bd6, #341f7a);
  --rareza-shadow:        linear-gradient(163deg, #4a4a52, #1e1e24);
  --rareza-slurp:         linear-gradient(163deg, #23c2b8, #0d6d70);
  --rareza-dark:          linear-gradient(163deg, #b0339a, #4d0f52);
  --rareza-frozen:        linear-gradient(163deg, #6fc4e8, #2a6d99);
  --rareza-lava:          linear-gradient(163deg, #d4762c, #7a2c14);
  --rareza-marvel:        linear-gradient(163deg, #c33b36, #6d1714);
  --rareza-dc:            linear-gradient(163deg, #2f5fb8, #142c66);
  --rareza-starwars:      linear-gradient(163deg, #3b4a63, #131a29);
  --rareza-platform:      linear-gradient(163deg, #6b4bd6, #341f7a);
}

/* El fondo del que parten las dos rejillas que pintan ofertas —la tienda y el
   Historial— y **el último respaldo**: lo que se ve cuando no hay ni colores de
   Epic ni rareza conocida.

   Se declara aquí y no dentro de `.articulo`/`.operacion` porque quien lo pisa
   son las dos reglas de debajo, y las tres son de una sola clase: a igual
   especificidad manda el orden en la hoja. Con el fondo dentro de cada
   tarjeta, `.operacion` —que se define al final del fichero— le ganaba a su
   propia rareza y el Historial se quedaba plano. Los tres escalones seguidos y
   en orden son la única forma de que eso no vuelva a pasar sin avisar. */
.articulo,
.operacion {
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
}
/* La lámina de la tienda pública sale de la misma lista, pero **escribe su
   degradado en vez de usar el token de la casa**. El resultado es idéntico
   —`--grad-superficie` se arma con `--superficie-2`, que el `.fn` redefine por
   tema—, y lo que se gana es poder vigilar la regla entera: dentro de `.fn` no
   queda ni un `--grad-*`, así que no hay que mirar uno por uno cuáles traen
   color de la casa y cuáles no. */
.fn-lamina {
  background-color: var(--superficie);
  background-image: linear-gradient(163deg,
    color-mix(in srgb, var(--superficie-2) 82%, transparent) 0%,
    transparent 58%);
}

/* **En la tienda la lámina sube un escalón, y no es estética.** Es el respaldo
   de cuando la oferta no trae colores, y en el tema claro `--superficie` es
   **blanco puro**: un render blanco —los emotes y los picos lo son a menudo—
   sobre blanco no se ve. Con `--superficie-2` el fondo sigue siendo neutro del
   tema, que es lo que tiene que ser, y nunca es blanco.

   El degradado sube con él para conservar el mismo escalón de luz: es la
   escalera de superficies de CLAUDE.md, un peldaño más arriba.

   **Con `:where(.fn)` y no con `.fn` a secas, y es lo que hace que el
   respaldo sea respaldo.** Con `.fn .fn-lamina` pesaba (0,2,0) y le ganaba a
   `.brillo--<rareza>`, que pesa (0,1,0): dentro de `/fn/` el color de la
   rareza no salía **nunca**, y todo lo que dependía de él —las tarjetas de
   Órdenes, que sólo tienen la rareza congelada, y la rejilla cuando una
   oferta no trae colores— se quedaba en el neutro oscuro. Se vio en los items
   sin foto de Órdenes. Con `:where()` pesa lo mismo que las rarezas y va
   escrito antes, así que la cadena es la de todas partes: **el color de la
   oferta** (`.fn-lamina--color` y `.fn-tarjeta--color .fn-lamina`, que pesan
   más), **la rareza**, y **el neutro** cuando no hay ninguna de las dos. */
:where(.fn) .fn-lamina {
  background-color: var(--superficie-2);
  background-image: linear-gradient(163deg,
    color-mix(in srgb, var(--superficie-3) 82%, transparent) 0%,
    transparent 58%);
}

/* Respaldo por rareza: sólo se ve cuando la oferta no trajo colores.

   Lo comparten **tres** sitios —la tienda con la rareza de hoy, el Historial
   con la que guardó la operación y el resplandor de la portada—, y lo comparten
   de verdad, en el mismo selector: con tres listas de diecisiete, la que
   alguien tocara de menos dejaría la misma pieza de dos colores según la
   pantalla.

   En la portada el degradado no pinta una tarjeta sino un borrón desenfocado
   detrás de la figura; que sea un degradado y no un color plano le va bien,
   porque el desenfoque lo convierte en un halo con dos tonos. */
.articulo--common,
.operacion--common,
.brillo--common { background-image: var(--rareza-common); }
.articulo--uncommon,
.operacion--uncommon,
.brillo--uncommon { background-image: var(--rareza-uncommon); }
.articulo--rare,
.operacion--rare,
.brillo--rare { background-image: var(--rareza-rare); }
.articulo--epic,
.operacion--epic,
.brillo--epic { background-image: var(--rareza-epic); }
.articulo--legendary,
.operacion--legendary,
.brillo--legendary { background-image: var(--rareza-legendary); }
.articulo--mythic,
.operacion--mythic,
.brillo--mythic { background-image: var(--rareza-mythic); }
.articulo--icon,
.operacion--icon,
.brillo--icon { background-image: var(--rareza-icon); }
.articulo--gaminglegends,
.operacion--gaminglegends,
.brillo--gaminglegends { background-image: var(--rareza-gaminglegends); }
.articulo--shadow,
.operacion--shadow,
.brillo--shadow { background-image: var(--rareza-shadow); }
.articulo--slurp,
.operacion--slurp,
.brillo--slurp { background-image: var(--rareza-slurp); }
.articulo--dark,
.operacion--dark,
.brillo--dark { background-image: var(--rareza-dark); }
.articulo--frozen,
.operacion--frozen,
.brillo--frozen { background-image: var(--rareza-frozen); }
.articulo--lava,
.operacion--lava,
.brillo--lava { background-image: var(--rareza-lava); }
.articulo--marvel,
.operacion--marvel,
.brillo--marvel { background-image: var(--rareza-marvel); }
.articulo--dc,
.operacion--dc,
.brillo--dc { background-image: var(--rareza-dc); }
.articulo--starwars,
.operacion--starwars,
.brillo--starwars { background-image: var(--rareza-starwars); }
.articulo--platform,
.operacion--platform,
.brillo--platform { background-image: var(--rareza-platform); }

/* El de Epic, y va el último para ganar por orden. `--c2` falta en la mayoría
   de las ofertas, así que se cae a `--c1` y el degradado va de un color a
   otro sin escala intermedia. */
.articulo--color {
  background-image: linear-gradient(
    163deg, var(--c1), var(--c2, var(--c1)), var(--c3)
  );
}

/* --- Panel de regalo ------------------------------------------------------
   El único sitio del panel donde un botón cuesta dinero. Las cifras van
   grandes de leer, no grandes de tamaño: es la regla de CLAUDE.md sobre
   cifras, cada una dentro de la frase que la explica. */
.regalo-articulo {
  display: flex;
  align-items: center;
  gap: var(--e4);
}

.regalo-foto {
  width: 96px;
  height: 96px;
  flex: none;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
}
.regalo-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.regalo-foto--vacia { color: var(--texto-tenue); }

.regalo-nombre { margin: 0; font-weight: 600; overflow-wrap: anywhere; }

.regalo-precio {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--e2);
}

/* Las tres cifras de la decisión: lo que hay, cómo queda y cuántos caben. */
.regalo-cifras li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  font-size: 0.9375rem;
}
.regalo-cifras .mono, .regalo-cifras .cifras { color: var(--texto-alto); }



/* --- Crear pedido ---------------------------------------------------------
   Fijo arriba y sobre la rejilla: el pedido se arma mientras se navega la
   tienda. `sticky` y no `fixed`, para que no tape nada al hacer scroll
   horizontal ni se despegue del ancho de la columna. */
.pedido {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  padding: var(--e3) var(--e4) var(--e4);
  margin-bottom: var(--e4);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}

/* Dos columnas y dos filas: arriba el destinatario y el artículo, y el botón
   de comprobar **debajo del destinatario**, no colgando a la derecha del
   artículo. Es donde acaba de escribir quien arma el pedido, y en la tercera
   columna quedaba lejos del último campo que se toca. La colocación es la que
   sale sola del orden del marcado —Para, Artículo, acciones—, y `grid-column`
   sólo la fija para que un aviso largo no la mueva. */
.pedido-campos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: end;
  gap: var(--e3);
}
/* `relative` porque es la caja contra la que flota la lista de sugerencias
   del destinatario (ver «Autocompletado del destinatario»). */
.pedido-campo {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
}
.pedido-campo .campo { width: 100%; max-width: none; }
.pedido-acciones { grid-column: 1; display: flex; align-items: center; }

/* Tienda Propia: un solo campo, el artículo. Sin destinatario, la segunda
   columna se quedaría vacía y el botón colgando en mitad de la fila. */
.pedido-campos--solo { grid-template-columns: 1fr; }

/* Sin cuenta conectada el panel se ve entero, con los campos apagados. El
   `<output>` del artículo no admite `disabled`, así que se le baja el texto a
   mano para que haga juego con el campo de al lado. */
.pedido--apagado .campo-salida { color: var(--texto-tenue); }
.pedido-acciones .aviso { margin: 0; }
.pedido-cifras { margin: 0; }

@media (max-width: 720px) {
  /* En una columna: los dos campos en fila no caben sin estrujar el nombre. */
  .pedido-campos { grid-template-columns: 1fr; }
  .pedido { position: static; }
}

/* Elegir un artículo: el enlace no tiene texto y se estira sobre toda la
   tarjeta, así que se pulsa entera. Su nombre accesible va en un `sr-solo`
   dentro, porque un enlace sin texto no tiene nombre y un lector de pantalla
   leería la URL. */
.articulo-elegir::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-tarjeta);
}

/* La elegida se marca con el borde Y con el check: dos canales, no uno. El
   borde va a 2px porque a 1px, sobre un fondo de color propio, se perdía. */
.articulo--elegido {
  border-color: var(--acento-hondo);
  border-width: 2px;
  /* Con 2px de borde la caja crecería 1px por lado y la rejilla bailaría al
     elegir: se compensa quitándoselo al relleno del cuerpo. */
  margin: -1px;
}

/* El check, en la esquina de la foto. Lleva su propio fondo oscuro porque cae
   sobre el arte del artículo, que puede ser de cualquier color: sin él, un
   check verde sobre un fondo claro no se ve. */
.articulo-check {
  position: absolute;
  top: var(--e2);
  right: var(--e2);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background-color: var(--fondo);
  color: var(--ok-alto);
}
.articulo-check .icono { font-size: 20px; }
/* Está en las trescientas tarjetas y oculto en las que no toca: al elegir sin
   recargar, el navegador lo enseña y lo esconde. `display: grid` gana a lo que
   el navegador trae de serie para `[hidden]`, así que hay que decirlo. */
.articulo-check[hidden] { display: none; }

/* --- Autocompletado del destinatario --------------------------------------
   Lista bajo el campo, filtrada por el buscador de `base.html` (el mismo que
   usa Amigos) y a la vista sólo mientras se escribe (ver debajo). Con tope de
   altura: son decenas de personas y no puede empujar la rejilla hacia abajo.

   `overscroll-behavior: contain`: al llegar al final de la lista deslizándola,
   la página no se lleva el resto del gesto. */
.sugerencias {
  list-style: none;
  margin: var(--e1) 0 0;
  padding: 0;
  max-height: 190px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
}

/* **Con JavaScript, flota encima de lo que hay debajo** y no ocupa sitio en
   el bloque. Dentro del flujo, abrirla estiraba «Crear pedido» y empujaba el
   campo Artículo hacia abajo mientras se escribía: todo el bloque saltaba con
   cada tecla que vaciaba o llenaba la lista. Lo mismo el «ningún amigo
   coincide», que sale en el mismo sitio y con la misma caja.

   Sin JavaScript se queda en el flujo, desplegada: ahí no se abre ni se
   cierra, y flotando taparía el campo de al lado para siempre.

   El `z-index` le gana a lo que pueda haber debajo dentro de la página —el
   check de las tarjetas va a 2— también en el móvil, donde `.pedido` deja de
   ser `sticky` y ya no hace de capa propia. */
.con-js .pedido-campo > .sugerencias,
.con-js .pedido-campo > .sugerencias-vacio {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 5;
}
.sugerencias-vacio {
  margin: var(--e1) 0 0;
  padding: var(--e2) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
}
.sugerencias-vacio[hidden] { display: none; }

/* Con JavaScript sólo se ve mientras se escribe: desplegada todo el rato
   tapaba la rejilla, que es lo que se está mirando. Se esconde desde aquí y no
   desde el guion para que no llegue a pintarse y dar un parpadeo al cargar.

   Sin JavaScript (`.con-js` no está) se queda desplegada, que es la única
   forma de elegir a alguien cuando nada reacciona al teclado. */
.con-js .sugerencias:not(.sugerencias--abierta) { display: none; }

.sugerencia + .sugerencia { border-top: 1px solid var(--borde); }
.sugerencia[hidden] { display: none; }

/* La caja de una sugerencia. **Todas son un enlace**: desde que al amigo sin
   nombre de Epic se le elige por su `accountId`, no queda ninguna fila que no
   lleve a ninguna parte. Con ella se fue `.sugerencia-muerta`, que era la
   forma apagada de las que no se podían pulsar. */
.sugerencia a {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--e2) var(--e3);
  color: var(--texto);
  text-decoration: none;
  transition: background-color var(--t-rapida);
}
.sugerencia a:hover { background-color: var(--superficie-3); }

.sugerencia-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e2);
}
.sugerencia-nombre { font-size: 0.9375rem; overflow-wrap: anywhere; }

/* Las externas, debajo y más apagadas: son el dato de apoyo para reconocer a
   la persona, no el nombre por el que se la elige. */
.sugerencia-externas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e1);
  font-size: 0.75rem;
  color: var(--texto-tenue);
}

/* De qué plataforma es el nombre del título, cuando el título no es el nombre
   de Epic porque esa cuenta no tiene ninguno. Detrás y más apagado: el nombre
   es por lo que se le reconoce, la plataforma es de dónde sale. */
.sugerencia-fuente { font-size: 0.75rem; color: var(--texto-tenue); }


/* --- El resumen del pedido ------------------------------------------------
   Toma la caja de `.cajon` —superficie, degradado, `gap` y padding— pero **no
   es un panel lateral**: es un diálogo modal centrado, del ancho de su
   contenido, con lo de detrás oscurecido. Lo que se está a punto de pagar se
   mira de frente, no arrinconado contra un borde de la pantalla.

   Es un `<dialog>` abierto con `showModal()`, así que el navegador ya lo
   centra él solo. Lo que hay que deshacer es lo que `.cajon` le clava encima:
   `top/right/bottom`, el ancho de panel, el borde de una sola cara y la
   entrada desde la derecha. */
dialog.cajon {
  /* `inset: 0` + `margin: auto` es exactamente lo que hace el navegador con un
     diálogo modal. **El alto tiene que ser `fit-content` y no `auto`**: con
     `auto` entre un `top` y un `bottom` la caja se estira de arriba abajo en
     vez de centrarse, que es justo lo que se veía. */
  inset: 0;
  margin: auto;
  width: min(480px, calc(100% - 2 * var(--e4)));
  height: fit-content;
  max-width: none;
  /* Lo que no quepa hace scroll dentro: `.cajon` ya trae `overflow-y: auto`. */
  max-height: calc(100% - 2 * var(--e4));
  /* Ya no se apoya en ningún borde de la pantalla, así que lleva su caja
     entera y el radio de las superficies grandes. */
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  color: var(--texto);
  /* Entra fundiendo, con el velo. `entrar-derecha` es el gesto de un panel que
     se asoma por un lado, y aquí no hay lado del que asomarse. */
  animation: aparecer var(--t-media) both;
}
/* `.cajon` lo pone en `flex`, y una regla de hoja de estilo gana siempre a la
   del navegador: sin esto, el diálogo cerrado se vería. */
dialog.cajon:not([open]) { display: none; }

/* El velo, igual que el del cajón de vincular: funde y desenfoca lo de atrás. */
dialog.cajon::backdrop {
  background-color: color-mix(in srgb, var(--fondo) 78%, transparent);
  backdrop-filter: blur(2px);
  animation: aparecer var(--t-media) both;
}

/* El cierre del resumen es un `<button>` y no un enlace: el diálogo sólo
   existe con JavaScript, así que no hay ninguna URL a la que volver. */
button.cerrar {
  padding: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}

/* Los párrafos de dentro se apoyan en el `gap` del cajón: con el margen que
   trae de serie un <p>, el hueco se contaría dos veces. */
dialog.cajon p { margin: 0; }

/* La ficha del artículo: la foto a la izquierda y los tres datos a la derecha.
   Sustituye a la lista de rótulos que había ("Artículo", "Precio",
   "Destinatario"): al lado de su imagen, el nombre de un cosmético no necesita
   que le digan lo que es. */
.resumen-articulo {
  display: flex;
  align-items: center;
  gap: var(--e4);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}

/* Cuadrada y de tamaño fijo, con el degradado de la oferta debajo: es el mismo
   color que lleva su tarjeta en la rejilla, y es lo que hace que se reconozca
   de un vistazo que es la que se acaba de pulsar.

   El color se lo ponen las clases `.articulo--*`, que copia el guion de la
   tarjeta. No hace falta declarar ninguna aquí. */
.resumen-foto {
  flex: none;
  width: 96px;
  height: 96px;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* `--r-menudo`, no `--r-tarjeta`: va DENTRO de una caja y redondearla igual
     que ella la desbordaría (CLAUDE.md, Radios). */
  border-radius: var(--r-menudo);
  background-color: var(--superficie-3);
}
.resumen-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.resumen-foto img[hidden] { display: none; }
/* La foto que no llegó: el marcador se centra y no se estira. */
.resumen-foto .articulo-sinfoto .icono { font-size: 32px; width: 32px; height: 32px; }

.resumen-datos {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  min-width: 0;
}
/* El nombre manda por tamaño, no por color: aquí no hay velo que garantice
   contraste sobre el degradado, así que este bloque vive sobre la superficie
   normal y usa la paleta de siempre. */
.resumen-nombre { font-size: 1rem; font-weight: 600; }
.resumen-rareza { font-size: 0.8125rem; color: var(--texto-suave); }
.resumen-rareza[hidden] { display: none; }

/* Los dos botones del final, en una fila. "Enviar ahora" va dentro de su
   formulario y sale sólo tras el verde; "Cancelar" está siempre, que es lo que
   da una salida mientras se comprueba. */
.resumen-pie {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
}

/* Las cajas del resultado, la nota y el formulario de envío se turnan, y todas
   tienen `display` propio: `[hidden]` por sí solo no las apagaría. La de
   `.boton` la usan además los tres botones de la ficha de la tienda pública,
   que se turnan según haya destinos guardados o no. */
.exito[hidden], .resumen-envio[hidden], .boton[hidden] { display: none; }
/* La línea roja lleva la suya **por su clase** y no sólo por vivir dentro del
   diálogo: `dialog.cajon p[hidden]` la tapaba de rebote, y una regla que
   depende de dónde esté el elemento se rompe el día que se mueva. */
.aviso--con-icono[hidden] { display: none; }
dialog.cajon p[hidden] { display: none; }

/* ==========================================================================
   Código de creador: qué tiene puesto cada cuenta
   ========================================================================== */

/* El lado derecho de cada fila de la lista: el código, desde cuándo y, si
   toca, la pastilla de "pendiente". No reutiliza `.acciones--fila` porque ahí
   no hay acciones: son tres datos de distinto alto que tienen que quedar
   alineados por el centro, y el `stretch` de aquella estiraba la pastilla.

   Se envuelve por su cuenta en pantallas estrechas y se alinea a la derecha,
   que es de donde viene leyéndolo el ojo desde el nombre de la cuenta. */
.codigo-cuenta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--e2);
  text-align: right;
}

/* ==========================================================================
   Historial: fila de filtros, rejilla de operaciones y paginación
   ========================================================================== */

/* Los filtros en fila, y se apilan solos en cuanto no caben. No es
   `.acciones`: ahí el campo se lleva el sitio que sobra, y aquí los tres
   desplegables miden lo que miden. */
.filtros-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--e2);
}

/* --- La rejilla de operaciones --------------------------------------------

   Las mismas tarjetas que la tienda, y por el mismo motivo: aquí se busca
   RECONOCER —"aquel traje que le mandé a Fulano"— y eso se hace por la foto y
   el color, que es como se vio al elegirlo.

   Algo más anchas que las de la tienda (200 frente a 180): estas llevan dos
   líneas más —de dónde salió y a quién fue—, y con nombres de cuenta de Epic
   dentro, que son largos.

   El color de la tarjeta es la excepción a la paleta que documenta CLAUDE.md,
   con las mismas condiciones que en la tienda: no tiñe nada accionable, el
   texto de encima va sobre `--velo-tarjeta` y siempre en crema, y el estado
   lleva la palabra además del color. Lo que cambia es de dónde salen los
   colores: allí de la oferta de hoy, aquí de lo que la operación guardó el día
   que ocurrió. */
.rejilla--historial {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.rejilla--historial > [hidden] { display: none; }

/* --- Dos por fila en móvil ------------------------------------------------
   Con una sola columna, ver la tienda o el historial es un scroll
   interminable, y son dos rejillas de piezas pequeñas donde comparar de un
   vistazo es justo lo que se hace.

   **Dos de verdad, no un `auto-fill` con un mínimo.** Con `minmax()` el número
   de columnas sale de una cuenta con cuatro sumandos —el ancho de la ventana,
   el relleno de `.principal`, el de la `.tarjeta` que envuelve la rejilla y el
   hueco— y a 360px esa cuenta daba 304 de sitio contra 300 de necesidad: cuatro
   píxeles. Cualquier cosa que se coma cinco la tira a una columna, y la barra
   de desplazamiento se come quince. Por eso la tienda se veía a una columna y
   el historial a dos siendo la misma regla: la tienda es larga y siempre tiene
   barra, el historial de cuatro operaciones no.

   Pedir dos columnas es lo que se quiere, así que se piden dos columnas y se
   acabó la aritmética. A 360px con barra salen 138px de tarjeta; a 320, 122.
   Estrechas, pero dos, que es lo que se estaba pidiendo.

   El hueco baja a `--e3`: 20px entre dos tarjetas de 138 es un cuarto del
   ancho de una.

   Va **después** de las dos declaraciones y en un solo selector: las dos
   rejillas tienen una sola clase, así que entre `.rejilla` y
   `.rejilla--historial` no decide la especificidad sino el orden. */
@media (max-width: 480px) {
  .rejilla--tienda,
  .rejilla--historial {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--e3);
  }
}

/* Como `.articulo`: el degradado llega hasta el borde, así que el relleno vive
   en los hijos. Sin `:hover` que la levante y sin filo teal — no hay nada que
   pulsar en una operación que ya ocurrió, y un realce invita a intentarlo. */
.operacion {
  position: relative;
  display: flex;
  flex-direction: column;
  /* El fondo se declara con el de `.articulo`, arriba: las dos comparten la
     cadena de respaldos y el orden entre sus tres escalones importa. */
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  overflow: hidden;
  min-width: 0;
  /* Como en `.articulo`: es el contenedor de los `cqw` de su foto. */
  container-type: inline-size;
}

/* El degradado que guardó la operación. Va después de todo lo demás para
   ganar por orden, igual que `.articulo--color`. `--c2` falta en la mayoría de
   las ofertas y se cae a `--c1`. */
.operacion--color {
  background-image: linear-gradient(
    163deg, var(--c1), var(--c2, var(--c1)), var(--c3)
  );
}

/* De dónde salió la operación, **superpuesta en la esquina de la foto**.

   Encima de la imagen y no en una banda propia: es un dato de contexto, y una
   banda le quitaba una línea entera a la tarjeta para decir una palabra. En la
   esquina no le quita sitio a nada, que es lo que le toca a un dato que casi
   siempre dice lo mismo.

   **Fondo opaco**, por la misma razón que la pastilla del estado y con más
   motivo: debajo no hay un degradado de la paleta sino una foto cualquiera del
   CDN de Epic, que puede ser blanca, y no hay velo que se pueda calcular
   contra algo que no se conoce. Con `--fondo` opaco el contraste del crema no
   depende de la imagen y es siempre el mismo: **18,6:1**, medido.

   Al lado del texto va el rótulo "Origen:" en `sr-solo`: en la pantalla la
   esquina de la foto ya dice de qué se está hablando, y leído en voz alta
   "Panel" a secas no diría nada. */
.operacion-origen {
  position: absolute;
  top: var(--e2);
  left: var(--e2);
  /* Por encima de la foto, que es un hermano posterior. */
  z-index: 1;
  margin: 0;
  /* Ni el origen más largo puede llegar al otro borde de la tarjeta. */
  max-width: calc(100% - var(--e4));
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 2px var(--e2);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--fondo);
  color: var(--texto);
  font-size: 0.75rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* El icono baja a 14px con su caja, como el del estado: a 20px dentro de una
   pastilla de 0,75rem manda él y no la palabra. */
.operacion-origen .icono { font-size: 14px; width: 14px; height: 14px; }

/* **Los dos orígenes, y se distinguen sin inventar un color.**

   Un envío que pidió un cliente y uno que mandó el vendedor a mano son la misma
   fila, la misma acción y la misma tarjeta; lo único que los separa es esta
   pastilla, así que tiene que separarlos de verdad.

   Lo que cambia son tres cosas y ninguna es un tono nuevo: el icono
   (`storefront` frente a `dashboard`), el crema en lugar del gris del cuerpo, y
   el borde, que sube de `--borde-alto` a ese mismo crema rebajado. Un color
   propio habría sido lo fácil y está prohibido por dos sitios: el morado es lo
   accionable y esto no se pulsa, y el ámbar de energía no es identidad de nada.

   Los números, contra el `--fondo` opaco de la pastilla —que es opaco
   precisamente para que no dependan de la foto de debajo—: el crema da
   **18,64:1** y el gris del panel **13,86:1**; el borde de tienda, **6,00:1**
   frente a los 3,63 de `--borde-alto`. Los dos se leen de sobra; lo que hace la
   diferencia es que uno pesa y el otro no.

   Y la palabra sigue estando, que es la regla de siempre: quien no distinga el
   icono ni el peso lee "Tienda". */
.operacion-origen--tienda {
  border-color: color-mix(in srgb, var(--texto-alto) 55%, transparent);
  color: var(--texto-alto);
}

/* Igual que `.articulo-foto`, y por lo mismo: la rejilla del historial tiene el
   mismo patrón —tarjeta en columna, foto arriba y bloque de texto abajo— y le
   salía la misma banda de color. Ver allí el porqué de cada línea. */
.operacion-foto {
  flex: 1 0 auto;
  aspect-ratio: 1;
  max-height: 120cqw;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* Lo anterior a que se guardara la foto. No hay de dónde sacarla, y un
   marcador es más honesto que una imagen que no es la suya. */
.operacion-sinfoto { color: var(--texto-suave); }
.operacion-sinfoto .icono { font-size: 32px; width: 32px; height: 32px; }

/* El bloque de texto, sobre el color y con el velo que lo hace legible con
   cualquier degradado que mande Epic. `margin-top: auto` para que el velo
   llegue abajo del todo aunque el texto sea corto. */
.operacion-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding: var(--e3) var(--e3) var(--e3);
  background-image: var(--velo-tarjeta);
  /* Como en la tienda: el sobrante lo absorbe la foto y esto es el respaldo. */
  margin-top: auto;
}

/* Todo en crema, y la jerarquía por tamaño. Es la misma regla que en la
   tienda, y sale de lo mismo: medido contra las 327 ofertas reales sobre el
   velo, el crema pasa AA en las 327 y `--texto-suave` no. */
.operacion-cuerpo .operacion-nombre,
.operacion-cuerpo .operacion-precio,
.operacion-cuerpo .operacion-quien { color: var(--texto-alto); }
/* El usuario de un cliente son hasta 40 caracteres desde el 3c, y muchos un
   correo: sin un espacio donde partir, desborda la tarjeta. */
.operacion-quien span { overflow-wrap: anywhere; }

.operacion-nombre {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Precio a la izquierda y fecha a la derecha, en la misma línea: son los dos
   datos que se leen de un vistazo y juntos ocupan una sola.

   **Y cuando no caben, la fecha baja entera.** A 380px la tarjeta mide 138px y
   ahí los dos no entran: sin `wrap`, la fecha se partía por donde tocara y el
   año se salía de la caja —se leía "15/09/" y debajo "14:35"—. Con `nowrap`
   ella y `wrap` la fila, o van juntas o la fecha se va abajo del todo, alineada
   a la derecha por su propio `margin-left: auto`. Una fecha a medias no es una
   fecha. */
.operacion-precio {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  font-size: 0.9375rem;
}
.operacion-fecha {
  margin-left: auto;
  font-size: 0.75rem;
  white-space: nowrap;
}

/* Las personas de la operación, separadas de la pieza por una línea.

   Arriba va qué es, qué costó y cuándo; aquí, de qué cuenta salió, a quién fue
   y —sólo en los envíos de la tienda— quién lo pidió. Sin la línea eran cinco
   renglones seguidos del mismo tamaño, y encontrar el que se busca obligaba a
   leerlos todos.

   La línea va en crema rebajado y no en `--borde`: esto se apoya sobre el velo
   de una tarjeta que puede ser de cualquier color, y un gris de la paleta
   desaparece encima de un degradado oscuro. Es **decoración y no texto** —lo
   que informa son los rótulos—, así que no se le pide AA; lo que se le pide es
   verse en los dos extremos, y al 28% sale a 1,9:1 contra el velo más claro
   posible y a 2,2:1 contra el más oscuro. */
.operacion-quienes {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  margin-top: var(--e2);
  padding-top: var(--e2);
  border-top: 1px solid color-mix(in srgb, var(--texto-alto) 28%, transparent);
}

/* De qué cuenta salió, a quién fue y quién lo pidió, en líneas seguidas con el
   mismo formato: puestas una debajo de otra se leen como un grupo, que es lo
   que son. En una compra las dos primeras serían la misma cuenta y sólo se
   pinta "De:". */
.operacion-quien {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--e1);
  font-size: 0.75rem;
  min-width: 0;
}
/* El rótulo no se encoge: lo que cede sitio es el nombre, que es lo largo.
   Va en crema como todo lo que hay sobre el color de la tarjeta —apagarlo es
   justo lo que falla AA contra un degradado claro—, y lo que lo separa del
   valor es que es corto y siempre el mismo, no el tono. */
.operacion-rotulo { flex: none; }
.operacion-quien span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* El estado. **Fondo opaco a propósito**, y no el `color-mix` translúcido de
   `.etiqueta--ok`: esta pastilla va sobre el velo de una tarjeta que puede ser
   de cualquier color, y con un fondo translúcido el verde se queda en **2,94:1**
   contra el peor caso —una oferta blanca bajo el velo al 0,72— y no llega ni
   de lejos al 4,5:1 de AA. Con `--fondo` opaco el contraste ya no depende del
   color de la tarjeta y es siempre el mismo: **8,16:1**, medido.

   El borde sí puede seguir siendo translúcido: es decoración, no texto, y lo
   que informa es la palabra que hay dentro.

   Y lleva la palabra "Enviado" además del color, que es la regla de siempre:
   el color nunca es el único canal. */
.operacion-estado {
  margin: var(--e2) 0 0;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: 2px var(--e2);
  border: 1px solid color-mix(in srgb, var(--ok) 55%, transparent);
  border-radius: var(--r-pastilla);
  background-color: var(--fondo);
  color: var(--ok-alto);
  font-size: 0.75rem;
}
.operacion-estado .icono { font-size: 14px; width: 14px; height: 14px; }

/* Paginación: los dos botones a los lados y en medio dónde estás. Los extremos
   apagados son `<span>` y no `<button disabled>`: no hay ninguna acción que
   deshabilitar, simplemente no hay página a la que ir. El aspecto de apagado
   ya lo pone `.boton[aria-disabled="true"]`. */
.paginacion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  margin-top: var(--e4);
}
.paginacion p { margin: 0; }

/* ==========================================================================
   Global Friends Link
   ========================================================================== */

/* --- La cabecera de un enlace público -------------------------------------
   Título, estado y contador en una fila. **El estado es un dato de contexto y
   su sitio es la cabecera** (CLAUDE.md, Cifras): metido dentro del bloque del
   enlace le robaba una línea a lo único que se viene a buscar, que es la URL.
   Se parte solo cuando no cabe, y entonces el contador cae debajo. */
.enlace-alto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2) var(--e3);
  min-width: 0;
}
.enlace-alto h1 { margin: 0; }
.enlace-alto .contador { margin: 0; }
.enlace-alto .etiqueta { flex: none; }

/* --- El enlace y sus acciones, en una fila -------------------------------
   La URL se lleva el hueco que sobra y los botones miden lo que miden. Las dos
   acciones que hacen daño van en icono: ocupan menos y, sobre todo, cuestan
   más de pulsar sin querer que un botón con su texto al lado del de copiar.

   `flex: 1 1 18rem` y no `1 1 0`: por debajo de 18rem la URL deja de parecer
   una URL, así que a partir de ahí se prefiere que la fila se parta. */
.enlace-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
}
.enlace-fila .enlace-url { flex: 1 1 18rem; min-width: 0; margin: 0; }
.enlace-fila form { display: flex; }

/* Botón de sólo icono. Cuadrado y del alto de los demás para que la fila no
   se escalone. Lo que dice va en `aria-label` y en `title`: el icono nunca es
   el único canal (CLAUDE.md, Iconos). */
.boton--icono {
  padding: 0;
  width: 40px;
  min-width: 40px;
  height: 40px;
  justify-content: center;
}

/* --- El plegable de contexto ---------------------------------------------
   Lo que antes eran tres párrafos encima del enlace. **Nace cerrado siempre**,
   también la primera vez: quien entra viene a copiar su URL o a cambiar su
   color, no a leerse los límites.

   La flecha de serie del `<summary>` se quita; la marca es el chevron de la
   izquierda, que además gira. **La palabra es el canal que informa** y el giro
   es el adorno, así que se va con `prefers-reduced-motion` y el texto se
   queda. */
.detalle > summary {
  list-style: none;
  cursor: pointer;
  width: fit-content;
}
.detalle > summary::-webkit-details-marker { display: none; }
.detalle-marca {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  font-size: 0.875rem;
  color: var(--texto-suave);
  transition: color var(--t-rapida);
}
.detalle > summary:hover .detalle-marca { color: var(--acento-alto); }
.detalle > summary:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
  border-radius: var(--r-control);
}
.detalle-marca .icono { transition: transform var(--t-media); }
.detalle[open] .detalle-marca .icono { transform: rotate(180deg); }
.detalle-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  margin-top: var(--e3);
}
.detalle-cuerpo p { margin: 0; }

/* La URL: se lee y se copia, así que va en monoespaciada y **parte por donde
   haga falta**. Sin `overflow-wrap` un token de 32 caracteres sin espacios
   desborda la tarjeta y saca una barra horizontal a toda la página. */
.enlace-url {
  overflow-wrap: anywhere;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto-alto);
  font-size: 0.875rem;
}

/* --- La página pública de "Agrégame" ------------------------------------- */

/* Los tres botones de plataforma. Son etiquetas de tres radios de verdad: el
   navegador se encarga de que sólo haya uno marcado y de que las flechas
   muevan la selección, así que esto funciona **sin una línea de JavaScript**.

   `1fr` cada uno para que los tres midan lo mismo: con anchos según el texto,
   "PlayStation" haría al de Epic la mitad de grande y el grupo dejaría de
   leerse como tres opciones del mismo peso. */
.plataformas {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--e2);
  margin: 0;
  padding: 0;
  border: 0;
}

.plataforma {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e1);
  padding: var(--e3) var(--e2);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-size: 0.8125rem;
  text-align: center;
  cursor: pointer;
  /* `--t-rapida` ya trae la curva dentro (160ms + cubic-bezier), así que aquí
     no se vuelve a poner: con las dos, la declaración entera es inválida y el
     navegador la tira sin decir nada. */
  transition: background-color var(--t-rapida),
              border-color var(--t-rapida),
              color var(--t-rapida);
}
.plataforma img {
  width: 28px;
  height: 28px;
  border-radius: var(--r-menudo);
}
/* **El hover sólo donde hay puntero.** En una pantalla táctil el navegador
   deja el `:hover` pegado en lo último que se tocó, así que al elegir Epic
   después de haber tocado Xbox quedaban DOS con borde de acento y parecía que
   había dos plataformas seleccionadas. Con `(hover: hover)` el dedo no lo
   dispara nunca y el único borde de acento es el del elegido, que además va a
   2px y con el fondo teñido. */
@media (hover: hover) {
  .plataforma:hover {
    background-color: var(--superficie-3);
    border-color: var(--acento-alto);
  }
}

/* El elegido. **El color no es el único canal**, como en el resto del panel: el
   borde es más grueso y el texto sube a crema, así que se distingue también sin
   ver el tono. El relleno es `--acento-tenue`, sobre el que el texto de acento
   se lee (ver la paleta). */
.plataforma-radio:checked + .plataforma {
  border: 2px solid var(--acento);
  padding: calc(var(--e3) - 1px) calc(var(--e2) - 1px);
  background-color: var(--acento-tenue);
  color: var(--texto-alto);
}

/* El foco se pinta sobre la etiqueta porque el radio está escondido: sin esto,
   quien navega con el teclado no vería dónde está. Mismos 3px y 3 de
   separación que el resto del panel. */
.plataforma-radio:focus-visible + .plataforma {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* El botón de enviar de esta pantalla es el único de todo el producto que se
   pinta con el degradado de acento a lo ancho: es la única acción de la página
   y quien llega aquí viene de un enlace que le han pasado, así que tiene que
   verse antes que nada. El robot parpadea al pasar por encima, como en la
   portada. */
.boton--enviar {
  width: 100%;
  justify-content: center;
  font-size: 1rem;
}

/* El recordatorio de la tarjeta de "¿Ya somos amigos?". **Una línea, no una
   caja**: antes era un `.nota--aviso` amarillo que salía siempre, y un aviso
   que no falta nunca deja de avisar de nada — el color se guarda para cuando
   de verdad no se puede hacer nada.

   El icono es el de información y no el de advertencia, que es lo que era: no
   hay nada que temer, hay algo que saber. */
.agregar-pista {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  font-size: 0.875rem;
  color: var(--texto-suave);
}
/* `flex: none` para que el icono no se encoja cuando el texto parte en dos
   líneas, que en 380px es siempre. */
.agregar-pista .icono { flex: none; }

/* El límite, pegado al botón al que se refiere y centrado debajo: es
   información sobre esa acción y se lee antes de pulsar, no al final de la
   tarjeta. Sin caja, por lo mismo que el de arriba. */
.agregar-limite {
  margin: 0;
  text-align: center;
  font-size: 0.875rem;
  color: var(--texto-suave);
}

.agregar-pie {
  margin: 0;
  text-align: center;
  color: var(--texto-tenue);
}


/* A 380px los tres botones apretados parten "PSN" en dos líneas. Se apilan
   antes de llegar a eso. */
@media (max-width: 400px) {
  .plataformas { grid-template-columns: 1fr; }
  .plataforma { flex-direction: row; justify-content: center; }
}

/* --- El tema claro de la página pública ----------------------------------
   **Es la única superficie clara de todo el producto**, y vive aquí acotada a
   `.acceso[data-tema="claro"]`: no toca el `:root` ni el panel, sólo la página
   que el vendedor haya puesto en claro.

   No es el espejo exacto de la escalera oscura, y no puede serlo: en oscuro la
   tarjeta sube desde el fondo, y en claro lo que se espera es un papel gris
   suave con la tarjeta **blanca** encima y los controles un escalón por debajo.
   Lo que sí se conserva es la separación medida: 8,5 puntos de L* entre el
   papel (91,5) y la tarjeta (100), que es el mismo escalón que tiene la
   escalera oscura.

   Los textos están medidos sobre la tarjeta blanca, que es el peor caso de los
   dos: 17,70 / 13,21 / 8,81 / 6,86:1 de alto a tenue. El crema no sobrevive
   aquí —sobre blanco no se ve—, así que `--texto-alto` pasa a un casi negro
   morado, que es lo que hace de "alto" en este tema.

   Y el acento **no es el del logo**: sobre blanco se queda en 2,85:1. Se baja
   de claridad el mismo tono hasta 6,39:1 sobre la tarjeta y 5,15:1 sobre el
   papel. Está también en `core/friend_link.TEMAS`, porque el servidor tiene
   que medir contra él el color que elija el vendedor, y hay un test que cruza
   las dos copias.

   Lo mismo le pasa al verde de estado, y por eso baja aquí abajo: un color
   pensado para caer sobre el negro del panel no se ve sobre una tarjeta
   blanca. */
.acceso-tarjeta[data-tema="claro"],
.fn[data-tema="claro"] {
  --superficie:   #FFFFFF;
  --superficie-2: #DCD7E8;
  --superficie-3: #CFC9DE;
  --borde:        #C9C4D6;
  --borde-alto:   #8C8499;

  --texto-alto:   #1A1626;
  --texto:        #332C46;
  --texto-suave:  #4E4664;
  --texto-tenue:  #5E5674;

  /* El verde de estado **también se baja de claridad**, por lo mismo que el
     acento y con la misma cuenta. Los de `:root` están hechos para caer sobre
     el negro del panel: sobre esta tarjeta blanca el check de `--ok-alto` se
     queda en 2,09:1 —y en 1,59:1 contra el peor caso compuesto, que es una foto
     oscura detrás—, o sea invisible justo en la línea que dice que todo ha ido
     bien. Mismo tono (156°) y misma saturación (34%), sólo más oscuros:

     Los números son los del **fondo real del mensaje**, que no es la tarjeta:
     la caja de `.exito` se pinta con un 14% del propio verde encima, así que
     debajo del check hay foto, tarjeta al 88% y ese tinte. Medido en los dos
     extremos de foto:

       --ok       borde, contra la tarjeta   4,56:1 (foto clara) / 3,45:1 (oscura)
       --ok-alto  el check, contra la caja   6,11:1 (foto clara) / 4,74:1 (oscura)

     El `-alto` pasa AA **como texto** en los dos extremos, que es más de lo que
     se le pide a un icono; el plano llega al 3:1 de un borde por los dos lados.
     En este tema "alto" es más oscuro y no más claro, igual que pasa con el
     acento: lo que significa es "el que se lee". */
  --ok:           #408267;
  --ok-alto:      #2F604C;

  /* Y el ámbar de los avisos, exactamente por lo mismo. `--aviso-alto` es el
     texto de `.nota--aviso`, y el de `:root` sobre esta tarjeta se queda en
     1,75:1 con una foto clara detrás y 1,36:1 con una oscura: el aviso
     amarillo de la revisión no se leería. Mismo tono (45,6°) y misma
     saturación (68%), sólo más oscuros:

       --aviso       relleno y borde de la nota
       --aviso-alto  el que se lee    6,13:1 / 5,46:1 sobre la caja compuesta

     El borde de `.nota` se queda en 1,85:1 y no se persigue: con la mezcla al
     55% que trae la clase no hay tono de este color que llegue a 3:1 sobre una
     tarjeta clara, y no hace falta —es el filo de una caja, no un control, y
     lo que dice lo que es son el icono y el texto—. */
  --aviso:        #91751C;
  --aviso-alto:   #554511;

  /* Y el rojo, por tercera vez lo mismo. `--peligro-alto` es el texto de
     `.nota--peligro`, y el de `:root` se queda en 2,23:1 sobre esta tarjeta con
     una foto clara detrás y 1,74:1 con una oscura. Mismo tono (5,5°) y misma
     saturación (49%), más oscuros: el texto queda en 8,49:1 y 6,58:1 sobre la
     caja compuesta.

     **Los tres colores de estado han necesitado el mismo ajuste**, y no es
     casualidad: los de `:root` están calculados para caer sobre el negro del
     panel. Cualquier estado nuevo que se use aquí dentro hay que medirlo antes,
     no después. */
  --peligro:      #9B4034;
  --peligro-alto: #6B2B24;
}

/* Los dos derivados del acento se calculan a partir de él, y tienen que
   hacerlo: el acento lo elige el vendedor y viene por variable, así que dejar
   fijos el de hover y el de relleno los dejaría morados sobre un acento verde.
   En oscuro "más alto" es más claro y en claro es más oscuro, que es lo que
   significa ese token en cada tema: el que gana contraste. */
.acceso-tarjeta[data-tema],
.fn[data-tema] {
  --acento-alto:  color-mix(in srgb, var(--acento) 78%, #FFFFFF 22%);
  --acento-tenue: color-mix(in srgb, var(--acento) 16%, transparent);
  /* El otro extremo del degradado de la marca: el acento aclarado casi a la
     mitad. Lo usó también el glifo de los V-Bucks; ya no, que la moneda dejó
     de tener color propio y hereda el del texto. */
  --marca-brillo: color-mix(in srgb, var(--acento) 55%, #FFFFFF 45%);
  /* El halo del logo. **No sale del acento y es a propósito**: el logo lo pone
     el vendedor y puede ser de cualquier color, así que un halo de su mismo
     tono desaparece justo con los logos que más lo necesitan —uno morado sobre
     un halo morado no se despega del fondo—. Lo que tiene que hacer el halo es
     separar la figura del papel, y para eso el color es el contrario del
     fondo: blanco en oscuro y casi negro en claro. */
  --halo-logo: #FFFFFF;
}
.acceso-tarjeta[data-tema="claro"],
.fn[data-tema="claro"] {
  --acento-alto:  color-mix(in srgb, var(--acento) 78%, #000000 22%);
}

/* --- El velo de una tarjeta de oferta -------------------------------------

   El bloque de texto de una tarjeta teñida con el degradado de Epic. **No es
   decoración: es lo que crea el fondo contra el que el servidor calcula el
   color del texto** (`core/store_tarjeta.py`).

   Por qué hace falta, medido: de las 346 ofertas del 20/09/2026, **143 —el
   41%— traen un degradado que va de muy oscuro a muy claro** (`1a1d22` ->
   `bcd9f5` es el caso típico). Contra un degradado así **no hay un solo color
   de texto posible**: el mejor de blanco y negro se queda en 1,46:1. Con el
   velo delante, el peor caso de las 1608 ofertas guardadas deja **6,88:1** en
   oscuro y **10,59:1** en claro.

   **Y cambia con el tema, al revés que el de la tienda del panel.** Allí puede
   ser un negro fijo porque el panel siempre es oscuro; aquí el tema lo elige
   el vendedor, y un velo negro sobre una tienda clara mete una banda que no
   pega con nada. El color es **la superficie de su tema** —o sea, el fondo que
   la tarjeta tendría si la oferta no trajera colores—, así que las teñidas y
   las neutras salen de la misma familia y la rejilla no se parte en dos.

   Los dos números son los mismos que `--velo-tarjeta`: 0,72 arriba y 0,94
   abajo. **El de arriba es el que hay que mirar** —es donde el color de Epic
   pesa más—, y si alguien lo baja, que rehaga la cuenta.

   Va con el hex escrito a mano porque `rgba()` no acepta una variable: al
   mover una superficie de `TEMAS` hay que moverla también aquí, y hay un test
   que lo vigila. */
.fn[data-tema] {
  --fn-velo-oferta: linear-gradient(
    to bottom, rgba(30, 29, 39, 0.72), rgba(30, 29, 39, 0.94)
  );
}
.fn[data-tema="claro"] {
  --fn-velo-oferta: linear-gradient(
    to bottom, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.94)
  );
}
/* En claro el degradado de la marca va **hacia abajo, nunca desde blanco**:
   sobre papel claro un extremo blanco no se ve. Mismo gesto, otro sentido. */
.fn[data-tema="claro"] {
  /* **El degradado estaba, pero apenas se separaba.** De `--acento-alto` (el
     acento con un 22% de negro) a un 45% de negro hay 11,6 puntos de L*, y
     sobre papel claro y en un nombre corto eso se lee como un color plano; en
     el tema oscuro los dos extremos son claros sobre fondo hondo y el mismo
     salto sí se nota. Bajando el final al 40% del acento —o sea, un 60% de
     negro— el recorrido pasa a ~20-29 puntos de L* según el acento, que es el
     doble, y **de paso sube el contraste**: el extremo oscuro va de 8,25:1 a
     10,83:1 sobre el papel con el morado de la casa.

     El arranque se queda en `--acento-alto` y no en `--acento` a propósito:
     el acento crudo sobre papel claro se queda en 2,3-3,7:1 según el que
     elija el vendedor, y el nombre de la tienda es texto. `--acento-alto` es
     justo el token que existe para eso. */
  --marca-brillo: color-mix(in srgb, var(--acento) 40%, #000000 60%);
  --halo-logo: #0D0B12;
}

/* **El velo no cambia con el tema, y es a propósito.** El tema es de la
   tarjeta; el fondo sigue siendo la foto del vendedor o la de la casa, así que
   el velo sigue siendo el mismo de siempre: el que aplana la imagen para que
   la tarjeta se despegue. Una tarjeta clara sobre una foto velada en oscuro es
   exactamente lo que se quiere ver. */

/* El borrón de detrás de la tarjeta sigue al acento **sólo en la página
   pública**: si el vendedor elige verde, un halo morado se lee como un fallo.
   Se distingue por `[data-tema]`, que sólo lleva esa pantalla; las dos de
   acceso se quedan con `--grad-acento`, que es el de la casa. */
.acceso--publica .acceso-brillo {
  background-image: radial-gradient(closest-side, var(--acento), transparent);
}

/* --- El halo del logo -----------------------------------------------------
   Un logo lo sube el vendedor y puede ser de cualquier color, así que puede
   tocarle un blanco sobre fondo claro o un negro sobre fondo oscuro: sin nada
   detrás, desaparece. El halo lo despega **al revés que el tema**, oscuro en
   claro y claro en oscuro, que es la única forma de que sirva para los dos
   casos a la vez.

   Es `drop-shadow` y no `box-shadow` —que sigue prohibido—, y la diferencia no
   es de nombre: `drop-shadow` sigue el recorte alfa de la imagen, así que
   rodea el dibujo del logo y no su caja. Dos capas cortas en vez de una larga:
   una sola a 8px se lee como una mancha, y dos apiladas dan un borde definido
   que se difumina.

   El color sale de `--texto-alto` y no de una regla por tema, y eso **es** la
   solución: ese token ya es, por construcción, lo contrario del fondo —crema
   en el tema oscuro, casi negro en el claro—. Una regla, los dos casos, y el
   día que se añada un tercer tema el halo va detrás solo. */
.marca-logo--halo {
  filter:
    drop-shadow(0 0 1px color-mix(in srgb, var(--texto-alto) 60%, transparent))
    drop-shadow(0 0 6px color-mix(in srgb, var(--texto-alto) 40%, transparent));
}

/* --- Personalizar la página: formulario y vista previa --------------------
   Dos columnas otra vez, y con el mismo criterio que el bloque de arriba: a la
   izquierda lo que se rellena, a la derecha lo que sale. La previa no baja de
   300px porque por debajo la tarjeta de dentro deja de parecerse a la real, y
   dejar de parecerse es lo único que esta pieza no puede hacer. */
.estilo-bloque {
  display: grid;
  /* **El formulario manda en el reparto**, y antes no lo hacía: con `3fr/2fr`
     sobre un contenedor estrecho, la columna de la izquierda se quedaba por
     debajo de lo que mide una dirección y los campos salían cortados.
     `minmax(22rem, …)` le da un suelo de verdad, y cuando no cabe la previa se
     va debajo en vez de estrujarlo. */
  grid-template-columns: minmax(22rem, 1.6fr) minmax(17rem, 1fr);
  align-items: start;
  gap: var(--e5);
}
.estilo-form label { display: block; margin-bottom: var(--e1); }
/* `min-width: 0` en los dos, y es lo que arreglaba los campos cortados: un
   `<input>` trae un ancho intrínseco de unos 20 caracteres, y dentro de un
   flex —`.pila-corta`— un hijo no baja de su contenido salvo que se le diga.
   Con `width: 100%` a secas, el campo desbordaba su columna y se veía
   "https:/". */
.estilo-form > div,
.estilo-form > .campo-fila > div { min-width: 0; }
.estilo-form .campo { width: 100%; min-width: 0; }

/* Dos campos cortos a la par. `auto-fit` y no dos columnas fijas: en cuanto no
   caben los dos, se apilan solos y no hace falta un punto de ruptura más. */
.campo-fila {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--e3);
}

/* Un apunte al lado de lo que lo provoca. **En línea y no en caja**: una nota
   de nueve palabras dentro de un recuadro de media columna se lee en vertical.
   El icono va pequeño y es lo único teñido; el texto se queda en el escalón de
   metadato, porque no es un estado ni hay nada que atender. */
.pista {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  color: var(--texto-tenue);
  font-size: 0.8125rem;
}
.pista .icono {
  flex: none;
  font-size: 16px;
  width: 16px;
  height: 16px;
  color: var(--aviso-alto);
}

/* Las dos variantes de `.nota` que faltaban. Mismo patrón que las etiquetas:
   el tono base bordea y tiñe, y el `-alto` es el que se lee. */
.nota--peligro {
  border-color: color-mix(in srgb, var(--peligro) 55%, transparent);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  background-image: none;
  color: var(--peligro-alto);
}
.nota--aviso {
  border-color: color-mix(in srgb, var(--aviso) 55%, transparent);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  background-image: none;
  color: var(--aviso-alto);
}
.nota .icono { vertical-align: -4px; }

/* Los dos temas: mismos radios pintados como botones que el selector de
   plataforma de la página pública, en corto. */
.temas {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  border: 0;
}
/* La nota va en su línea, debajo de las opciones: al lado, con tres líneas de
   texto, estiraba los botones hasta su alto. */
.temas > p { flex-basis: 100%; margin: 0; }
.temas legend { padding: 0; margin-bottom: var(--e1); }
.tema {
  padding: var(--e2) var(--e4);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-size: 0.875rem;
  cursor: pointer;
}
.tema-radio:checked + .tema {
  border: 2px solid var(--acento);
  padding: calc(var(--e2) - 1px) calc(var(--e4) - 1px);
  background-color: var(--acento-tenue);
  color: var(--texto-alto);
}
.tema-radio:focus-visible + .tema {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

.casilla {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  font-size: 0.875rem;
}
/* Apagada se ve y se lee, sólo que no se puede marcar: esconderla dejaría al
   vendedor sin saber que la ventaja existe, que es justo lo contrario de lo
   que quiere una ficha de plan. */
.casilla--apagada { color: var(--texto-tenue); }

/* --- La vista previa ------------------------------------------------------
   Es la tarjeta de verdad con el marcado de verdad, encogida: por eso reusa
   `.acceso`, `.acceso-fondo`, `.acceso-velo` y `.acceso-tarjeta` en vez de
   imitarlas con otras clases. Lo único que cambia es que aquí no ocupa la
   pantalla, no llueve —una previa no necesita animarse— y no se pulsa nada. */
.previa-marco {
  min-height: 0;
  padding: var(--e5);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}
.previa-tarjeta { max-width: 100%; }
.previa-tarjeta h3 { margin: 0; font-size: 1.125rem; }
.previa-tarjeta p { margin: 0; }
/* El botón de la previa **no es un botón**: es un `<span>` con su aspecto. No
   hay nada que enviar desde aquí, y un `<button>` de adorno dentro de un
   formulario es un envío esperando a pasar. */
.previa-boton {
  justify-content: center;
  pointer-events: none;
}
.estilo-previa {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.estilo-previa > p { margin: 0; }

/* La caja de una pieza que la previa sustituye en su sitio (ver
   `js/previa_en_sitio.js`). **No pinta nada y no ocupa nada**: está para que
   el guion encuentre qué cambiar aunque dentro no haya nada —el aviso de un
   bloque que no se ha previsualizado, la previa de una ayuda vacía—. Con
   `display: contents` sus hijos cuentan como hijos del formulario o del
   bloque, así que el hueco entre piezas es el de siempre. */
.previa-sitio { display: contents; }

/* La previa se va debajo antes de que el formulario llegue a su suelo: entre
   estrujar los campos y bajar la previa una fila, se baja la previa. Ahí gana
   además el ancho entero, que es lo que hace que se siga pareciendo a la
   página de verdad. */
@media (max-width: 1080px) {
  .estilo-bloque { grid-template-columns: 1fr; gap: var(--e5); }
}

/* --- El emblema de un pase, en su caja -----------------------------------
   **La comparten las dos pantallas que enseñan pases**, y por eso está aquí y
   no dos veces: la lista de Global Store Link (`.pases-lista`) y la rejilla de
   «Regalar un pase» de Tienda (`.pases-rejilla`) enseñan lo mismo —el emblema
   cuadrado de un pase, o un marcador si todavía no lo tenemos— y lo enseñan
   igual. Lo que cambia entre las dos es **dónde va la caja**, no la caja.

   Antes eran dos reglas con el mismo nombre en sitios distintos de la hoja, y
   la segunda le ganaba a la primera sin que nada lo dijera: tocar la rejilla de
   Tienda cambiaba la lista de Store Link, y al revés no. Un nombre compartido
   **a propósito** se puede tocar; uno compartido por accidente, no.

   `--r-menudo` y no `--r-control`: va DENTRO de una fila o de una tarjeta, y
   redondearla como ellas la desbordaría (CLAUDE.md, Radios). Tamaño fijo y
   `flex: none` para que un nombre largo no la estruje. `position: relative`
   porque es el ancla de la marca de elegido de la rejilla (`.pase-check`); en
   la lista no hay marca y no estorba.

   **Lo único que las dos no comparten es cómo encaja la imagen**, y es una
   diferencia de verdad: la rejilla la recorta (`cover`) y la lista la mete
   entera (`contain`). Hoy da igual —las cuatro son nuestras y miden 800x800,
   así que en una caja cuadrada las dos dan lo mismo— y las dos reglas están
   escritas para la que algún día no lo sea. De ahí el modificador en vez de
   elegir una: unificarlas sería decidir por las dos pantallas algo que cada
   una ya había decidido. */
.pase-arte {
  flex: none;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  border-radius: var(--r-menudo);
  overflow: hidden;
  background-color: var(--superficie-3);
  color: var(--texto-tenue);
}
.pase-arte img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* La lista de Global Store Link mete la imagen entera en vez de recortarla. */
.pase-arte--entera img { object-fit: contain; }

/* --- Los pases en la tienda del vendedor ---------------------------------
   Una fila por pase: el emblema, el nombre, el precio y el interruptor. Es una
   lista densa de cuatro filas, no una rejilla de tarjetas: lo que se compara de
   un vistazo es el precio de cada uno, y en columna eso se lee solo.

   **Los nombres llevan el prefijo de su fila** (`.pase-fila-…`) y no el
   genérico: la rejilla de Tienda tiene sus propias piezas y las dos se
   llamaban igual. Lo único que comparten de verdad es `.pase-arte`, que está
   arriba. */

.pases-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}

.pase-fila {
  display: grid;
  /* `minmax(0, 1fr)` en el nombre y no `1fr` a secas: las dos columnas de la
     derecha no pueden encoger —un campo y un interruptor miden lo que miden—,
     así que con `1fr` el nombre era el único que cedía y «Pase de Batalla» se
     partía en tres líneas antes de que nada más se moviera. */
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--e2) var(--e3);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}

/* El filo del pase que no se ha podido guardar. **Nunca es el único canal**:
   debajo va el motivo escrito, que es lo que de verdad lo explica. */
.pase-fila--mal { border-color: var(--peligro); }

/* El nombre, que aquí es un `<span>` de una fila y no el rótulo de una
   tarjeta. `min-width: 0` para que pueda ceder: es la única columna de la fila
   que puede. */
.pase-fila-nombre {
  color: var(--texto-alto);
  font-weight: 600;
  min-width: 0;
}

.pase-precio {
  display: flex;
  align-items: center;
  gap: var(--e2);
  /* El icono y el campo son una pieza y **no se parten**: partidos, el precio
     caía debajo del V-Buck y se veían encima el uno del otro. */
  flex-wrap: nowrap;
}
.pase-precio .vbuck { flex: none; }
/* Ancho fijo para que los cuatro campos se lean como una columna: con el ancho
   automático, cuatro precios de distinta longitud dejan los bordes en zigzag.
   `flex: none` para que el ancho sea de verdad y no un máximo del que el campo
   se baje cuando la fila aprieta.

   **12ch y no 9, y el número está medido y no elegido**: a 9 no cabía ni el
   precio más alto que el servidor admite —«100,000», que pide 11— ni el
   marcador de vacío, «Sin precio», que pide 12. Lo que se veía era «Sin pre»,
   y un rótulo cortado parece un fallo de la pantalla y no un campo sin
   rellenar. Se midió el texto de verdad con la fuente de verdad, contando el
   relleno y el borde del campo; si cambia `PRECIO_MAX` o el rótulo, se vuelve
   a medir — y hay un guardián que lo pide. */
.pase-precio .campo { flex: none; width: 12ch; text-align: right; }

.pase-visible { justify-self: end; }


/* El motivo ocupa la fila entera por debajo de las cuatro columnas: al lado del
   interruptor no cabe, y partido en dos líneas estrechas no se lee. */
.pase-error {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--peligro-alto);
  font-size: 0.8125rem;
}
.pase-error[hidden] { display: none; }

/* En móvil la fila se parte en dos: arriba la foto y el nombre, abajo el precio
   y el interruptor. Con cuatro columnas a 380px, el campo del precio se
   quedaba en tres caracteres. */
@media (max-width: 560px) {
  .pase-fila { grid-template-columns: auto minmax(0, 1fr); }
  .pase-precio { grid-column: 1 / 2; }
  .pase-visible { grid-column: 2 / 3; }
}

/* --- La tarjeta traslúcida de la página pública --------------------------
   Se ve la foto por detrás, desenfocada. La opacidad **está medida** y vive
   también en `core/friend_link.OPACIDAD_TARJETA`, porque el servidor la
   necesita para decirle al vendedor si su color de acento se va a distinguir:

   - detrás puede haber cualquier imagen, así que el peor caso es el extremo
     contrario a la tarjeta: una foto blanca detrás de la oscura, una negra
     detrás de la clara;
   - a 0,88 el texto más flojo de los dos temas (`--texto-tenue`) se queda en
     4,57:1 en oscuro y 5,20:1 en claro. A 0,85 el oscuro cae a 4,11 y deja de
     pasar AA. **Si se toca esta opacidad, se rehace la cuenta**; hay un test
     que la rehace.

   El desenfoque no entra en la cuenta y conviene saberlo antes de subirlo para
   "ganar contraste": emborronar no aclara ni oscurece una zona uniforme, así
   que el peor caso sale igual con 14px que con 40. El desenfoque es para que se
   intuya la foto; quien sostiene la legibilidad es la opacidad.

   `-webkit-` va delante porque Safari todavía lo pide. Donde no haya
   `backdrop-filter` la tarjeta se queda traslúcida y sin desenfocar: se ve algo
   peor y se lee igual, que es el orden correcto de las dos cosas. */
.acceso-tarjeta--velada {
  background-color: color-mix(in srgb, var(--superficie) 88%, transparent);
  background-image: none;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

/* El botón de enviar de la página pública: relleno de acento, borde de acento
   y **el texto que la cuenta diga**. `--acento-texto` lo calcula el servidor
   (`Estilo.acento_texto`): negro o blanco, el que más contraste dé, que nunca
   baja de 4,58:1. Es la única vez que ese color no es `--fondo`, y el motivo
   es que el relleno no es nuestro. */
.acceso-tarjeta--velada .boton--enviar {
  border-color: var(--acento);
  background-color: var(--acento);
  background-image: none;
  color: var(--acento-texto);
}
.acceso-tarjeta--velada .boton--enviar .icono { color: var(--acento-texto); }

/* El botón mientras no se puede pulsar: la cuenta atrás del enfriamiento
   (`data-estado="espera"`) y el aviso de que se ha gastado el cupo del día
   (`"agotado"`). Los pinta el guion; sin él, el botón no llega nunca aquí.

   **No se queda con el gris de `[disabled]`, y es la decisión de este bloque.**
   Ese estado atenúa el botón entero —opacidad 0,45 y saturación 0,4— porque
   normalmente su texto ya no importa; aquí el texto **es** el mensaje, y
   apagarlo sería esconder lo único que hay que leer. Así que se apaga el
   relleno y no la letra: superficie de control debajo y `--texto` encima, que
   son 9,51:1 en el tema oscuro y 9,39:1 en el claro.

   El aro **no se toca**: lo sigue poniendo la regla de arriba en el acento del
   vendedor, así que el botón se ve exactamente igual de recortado sobre la
   tarjeta que cuando sí se podía pulsar. Cambia lo de dentro, no la silueta.

   Va después del bloque velado a propósito: las dos reglas pesan lo mismo y
   quien gana es la última. */
.acceso-tarjeta .boton--enviar[data-estado] {
  background-color: var(--superficie-2);
  background-image: none;
  color: var(--texto);
  opacity: 1;
  filter: none;
  /* El del cupo gastado es una frase entera, no una etiqueta: tiene que poder
     partirse en dos renglones dentro del botón. */
  white-space: normal;
}

/* El robot acompaña al verbo "enviar" y aquí no hay verbo: en la cuenta atrás
   sobra, y delante de la frase del cupo la parte por la mitad. */
.acceso-tarjeta .boton--enviar[data-estado] .icono { display: none; }

/* El filo de luz de `--llamada` es de lo que invita a pulsarse. */
.acceso-tarjeta .boton--enviar[data-estado]::after { content: none; }

/* La confirmación de sin JavaScript. Con guion no llega a verse: lo primero
   que hace es quitarla, porque lo que confirma es la cuenta atrás. */
.agregar-hecho { margin: 0; }


/* --- Revisar amistad ------------------------------------------------------
   La segunda acción de la página pública. Todo lo de aquí vive dentro de la
   misma tarjeta: el "diálogo" no es una capa nueva sino la propia tarjeta
   enseñando otra cosa, que es lo que permite que funcione **sin una línea de
   JavaScript** —un `<dialog>` de verdad necesita `showModal()`—. Mismo tamaño,
   mismo sitio y los mismos colores del vendedor, sin inventar una ventana. */

/* Más delgado que el de enviar, y es la jerarquía de la página: quien abre
   esta URL viene a que le agreguen; revisar es lo que hace después. Mismo
   ancho para que la columna no se rompa, menos peso visual. */
.boton--revisar {
  width: 100%;
  justify-content: center;
  padding: 8px var(--e4);
  font-size: 0.9375rem;
}

/* Sin ninguna cuenta con hueco, revisar es **lo único** que se puede hacer
   aquí, y deja de ser la acción de después: recupera el peso del botón de
   enviar, que es el que ya no está. */
.boton--revisar.boton--llamada {
  padding: 13px var(--e6);
  font-size: 1rem;
}

/* "Regresar" dentro del título del diálogo: es la salida, no una acción. */
.revision-volver {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--texto-suave);
  font-size: 0.875rem;
  text-decoration: none;
}
.revision-volver:hover { color: var(--texto-alto); }

/* La lista de cuentas que ya te tienen agregado. Sin viñetas: cada línea ya
   empieza con el nombre de la cuenta, que es su marca. */
.revision-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.revision-cuenta {
  display: flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--superficie-2) 60%, transparent);
  font-size: 0.9375rem;
}
/* El check es el de estado y va en su color: aquí sí significa "esto está
   bien", al revés que el de la caja verde, donde lo dice la caja entera. */
.revision-cuenta .icono { flex: none; color: var(--ok-alto); }
/* El nombre enmascarado va en monoespaciada: son tres letras, asteriscos y una
   letra, y en proporcional los asteriscos bailan. */
.revision-cuenta .mono { flex: none; color: var(--texto-alto); }

/* La espera de la revisión: **la barra de carga del panel dentro de la
   tarjeta**, no un indicador nuevo. Misma clase, misma animación y el mismo
   `prefers-reduced-motion` que ya trae `.cargando-barra`, que la deja quieta y
   entera. Lo que se lee sigue estando en el texto de debajo, que es un
   `role="status"`: quien no ve el movimiento lo oye.

   La barra vive aquí redondeada porque no está pegada al borde de la pantalla
   como la del panel, sino dentro de una caja con esquinas. */
.revision-esperando {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  align-items: center;
}
.revision-esperando .cargando-barra { border-radius: var(--r-pastilla); }
.revision-esperando p { margin: 0; }

.revision-pie {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}
.revision-pie .boton { flex: 1; justify-content: center; }

/* El título y la línea que lo acompaña van juntos: son una frase en dos
   renglones, no dos bloques. El resto de la tarjeta mantiene su `--e4`. */
.agregar-alto {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
}
.agregar-alto h1 { margin: 0; }
.agregar-alto p { margin: 0; }

/* El pie **no va en el acento**: el acento es de los bordes y del botón, y como
   color de texto tendría que llegar a 4,5:1 sobre la tarjeta, que es más de lo
   que se le exige. Va en texto suave y se distingue por el subrayado, que es
   un canal que no depende del color. */
.agregar-pie a {
  color: var(--texto-suave);
  text-decoration: underline;
}
.agregar-pie a:hover { color: var(--texto-alto); }

/* El selector de color nativo, al lado de su campo de texto. Se le quita el
   relleno que le ponen los navegadores para que el cuadrado llegue al borde, y
   se le da el mismo radio y el mismo borde que a un control cualquiera. */
.color-par { flex-wrap: nowrap; }
.color-par .campo { flex: 1; min-width: 0; }
.color-muestra {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 2px;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  cursor: pointer;
}
.color-muestra::-webkit-color-swatch-wrapper { padding: 0; }
.color-muestra::-webkit-color-swatch {
  border: 0;
  border-radius: var(--r-menudo);
}
.color-muestra::-moz-color-swatch {
  border: 0;
  border-radius: var(--r-menudo);
}
.color-muestra:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* El título de la página pública va en el acento del vendedor. Es el único
   texto que lo lleva, y por eso sube a 1,5rem: a 24px WCAG lo considera texto
   grande, cuyo listón es 3:1 —el mismo que se le mide al acento— en vez de los
   4,5:1 del cuerpo. A 22px habría que pedirle al color más de lo que se le
   pide, y el color es del vendedor.

   Con el acento de la casa son 4,05:1 sobre la tarjeta oscura compuesta en su
   peor caso; con uno flojo, el panel lo avisa al lado del campo y guarda
   igual. */
.agregar-titulo {
  color: var(--acento);
  font-size: 1.5rem;
}


/* --- Área táctil: lo que un dedo necesita y un ratón no ------------------

   Un puntero fino acierta en 20px; un dedo, no. La referencia son 44x44, que
   es lo que piden tanto Apple como el criterio AAA de WCAG (2.5.5).

   Va en `pointer: coarse` y **no en el breakpoint de ancho**, y la diferencia
   importa: lo que decide cuánto hay que agrandar un control no es el tamaño de
   la pantalla sino con qué se toca. Una tableta ancha se toca con el dedo y una
   ventana estrecha en un portátil se sigue pulsando con el ratón.

   Y va aparte de la densidad de escritorio, que CLAUDE.md pide alta y no se
   toca: en el ratón todo esto se queda exactamente como estaba. Aquí no cambia
   ni un color ni un radio; sólo crece la superficie que hay que acertar.

   Lo que NO sube: los enlaces de texto dentro de una frase ("Ir a Mis
   cuentas", "Abrir mi página de verdad"). Un enlace de 44px de alto en mitad
   de un párrafo abre un boquete en el interlineado, y el texto que lo rodea es
   lo que explica a dónde lleva. */
@media (pointer: coarse) {
  /* El asa de reordenar era 20x20 y es el peor caso de todos, porque además
     de tocarse **se arrastra**: fallar el agarre no es pulsar al lado, es
     mover la tarjeta que no era. Crece la caja, no el icono. */
  .asa {
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    margin-left: calc(var(--e3) * -1);
  }

  /* El de tres puntos de la tarjeta de cuenta: 32 -> 44. */
  .menu > summary { width: 44px; height: 44px; }

  /* La X de los diálogos (32) y el plegar del aviso de progreso (26). Las dos
     son la salida de algo que tapa la pantalla, así que fallarlas es quedarse
     dentro. `min-*` en el plegar porque su tamaño lo daba el `padding`. */
  .cerrar { width: 44px; height: 44px; }
  .plegar { min-width: 44px; min-height: 44px; }

  /* Los filtros de la tienda (30px de alto), las pestañas de Amigos (39) y
     los atajos del umbral (32).
     `min-height` y no `padding` para no descuadrar el texto dentro: lo que
     crece es la caja, y el contenido se queda centrado donde estaba. */
  .filtro, .pestana, .atajo {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}


/* ------------------------------------------------------------------ pases

   El bloque de "Regalar un pase", encima de la rejilla de la tienda. Es una
   superficie como el panel de pedido y con el mismo filo, porque son dos cajas
   hermanas: en la de arriba se dice a quién, y aquí qué pase.

   Lo que NO es: una fila de tarjetas de métrica. No hay cifras destacadas ni
   iconos grandes; cada pase es su nombre, una frase con las fichas que quedan
   dentro y un botón. La cifra va en el cuerpo del texto, como manda CLAUDE.md.

   No es `sticky` como el pedido: eso es para lo que se mira mientras se navega
   el escaparate, y esto se mira una vez. */
.pases {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  padding: var(--e3) var(--e4) var(--e4);
  margin-bottom: var(--e4);
  background-color: var(--superficie);
  background-image: var(--grad-superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}

/* El plegable. La flecha de serie del `<summary>` se quita —la marca es el
   botón de la derecha, que además dice la palabra— y el `<summary>` se
   convierte en la misma fila de cabecera que `.seccion-alto`. */
.pases-plegable > summary { list-style: none; cursor: pointer; }
.pases-plegable > summary::-webkit-details-marker { display: none; }

.pases-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  margin-bottom: var(--e3);
}
.pases-alto h2 { margin: 0; }

/* El botón de plegar. No lleva `.boton` porque no es una acción del pedido:
   es la misma marca discreta que el plegable de un trabajo en curso. */
.pases-plegar {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  font-size: 0.8125rem;
  color: var(--texto-suave);
  transition: color var(--t-rapida);
}
.pases-plegable > summary:hover .pases-plegar { color: var(--acento-alto); }
/* El foco se pinta sobre la fila entera, que es lo que se pulsa. */
.pases-plegable > summary:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
  border-radius: var(--r-control);
}

/* Cerrado: cambia la palabra y gira la flecha. **La palabra es el canal que
   informa** —el giro es el adorno, y por eso se va con
   `prefers-reduced-motion` mientras el texto se queda—. Los dos rótulos están
   en el marcado y aquí sólo se esconde el que no toca. */
.pases-plegable[open] [data-plegado="si"],
.pases-plegable:not([open]) [data-plegado="no"] { display: none; }
.pases-plegable .pases-plegar .icono {
  transition: transform var(--t-media);
}
.pases-plegable:not([open]) .pases-plegar .icono { transform: rotate(180deg); }
/* Cerrado, la cabecera es lo único que queda: sin margen debajo no deja un
   hueco muerto dentro de la caja. */
.pases-plegable:not([open]) .pases-alto { margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
  .pases-plegable .pases-plegar .icono { transition: none; }
}

/* Cuatro pases hoy, y con `auto-fit` no hace falta saber cuántos serán: si
   Epic estrena un quinto, la rejilla se reparte sola.

   El mínimo son 240px y no 210: con el emblema de 56px al lado del nombre, a
   210 el texto se quedaba en 140 y "Pase de Batalla" partía en dos. */
.pases-rejilla {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--e3);
}

/* Cada pase, sobre el escalón de encima de la caja: la tarjeta se separa del
   bloque por superficie y borde, sin sombra (CLAUDE.md, Profundidad).

   Sin hover: aquí no hay nada que pulsar en la tarjeta entera, sólo su botón.
   Un filo que se enciende al pasar por encima diría que sí lo hay, que es el
   mismo motivo por el que las tarjetas de la tienda tampoco lo llevan. */
.pase {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding: var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
}

/* Apagado es "no se puede ahora": no le queda ficha, o la cuenta ha gastado
   sus cinco regalos.

   La opacidad va en **los hijos que se apagan**, no en la tarjeta: `opacity`
   crea un contexto de composición y un `opacity: 1` dentro no lo deshace, así
   que apagando el `<li>` entero se habría apagado también el motivo, que es
   justo lo único que hay que leer cuando no se puede. */
/* Un pase sin ficha no se puede elegir, y se ve: cae al escalón del fondo en
   vez de quedarse en el de las tarjetas. **La superficie es el canal**, no sólo
   la opacidad — con la tarjeta a media tinta parecía que estaba cargando.

   La opacidad va en **los hijos que se apagan**, no en el `<li>`: `opacity`
   crea un contexto de composición y un `opacity: 1` dentro no lo deshace, así
   que apagando la tarjeta entera se habría apagado también el motivo, que es
   justo lo único que hay que leer cuando no se puede. */
.pase--apagado {
  background-color: var(--fondo);
  border-color: var(--borde);
}
.pase--apagado .pase-alto,
.pase--apagado .pase-fichas { opacity: 0.72; }

/* La foto y el nombre, en fila. El emblema al lado y no encima: son cuatro
   imágenes cuadradas y a lo ancho de la tarjeta empujarían la rejilla de la
   tienda —que es a lo que se viene— media pantalla hacia abajo. */
.pase-alto {
  display: flex;
  align-items: center;
  gap: var(--e3);
}

/* La caja de la imagen es `.pase-arte`, que está arriba y la comparten las dos
   pantallas de pases. Aquí no se redefine nada de ella.

   El nombre del pase. Es un rótulo de bloque, así que va en crema, pero NO es
   un `h3`: dentro de una rejilla de cuatro, cuatro títulos de sección
   competirían con el "Regalar un pase" que los encabeza. Peso y color hacen la
   jerarquía. */
.pase-nombre {
  margin: 0;
  font-weight: 600;
  color: var(--texto-alto);
  /* Puede ceder dentro de `.pase-alto`, que es una fila flex: sin esto, un
     nombre de una sola palabra larga no encogería y empujaría la tarjeta.
     **Lo tenía y no se veía**: se lo prestaba la regla homónima de la lista de
     Store Link, que le ganaba por estar más abajo en la hoja. Ahora es suyo. */
  min-width: 0;
}

/* La frase con la cifra dentro. En `--texto`, con la cifra en crema por el
   `.cifras` que ya la pone tabular. */
.pase-fichas {
  margin: 0;
  font-size: 0.875rem;
  color: var(--texto);
}
.pase-fichas .cifras { color: var(--texto-alto); font-weight: 600; }

/* Por qué no se puede. Va en el tono de aviso y no en el de error: no ha
   fallado nada, falta algo. A plena opacidad aunque la tarjeta esté apagada,
   que es lo que consigue apagar los hermanos en vez del `<li>`. */
.pase-bloqueo {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--aviso-alto);
}

/* Los dos botones abajo y pegados al fondo, para que cuatro tarjetas con
   frases de distinto largo los tengan a la misma altura. `margin-top: auto`
   dentro de la columna flexible es lo que los baja.

   En fila y no apilados: son dos acciones cortas y distintas —elegir y
   comprar— y una encima de otra la tarjeta crecía el doble. Se reparten el
   ancho a partes iguales, así que con uno solo ocupa todo. */
.pase-acciones {
  margin-top: auto;
  display: flex;
  gap: var(--e2);
}
.pase-acciones .boton { flex: 1 1 0; justify-content: center; }

/* El rótulo largo de "Seleccionar" es el nombre accesible del enlace y no cabe
   en el botón: dentro se lee sólo la palabra. Es lo mismo que hace la tarjeta
   de la tienda, donde el rótulo entero vive en un `sr-solo`. */
.pase-elegir [data-elegir-rotulo] {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.pase-elegir::after { content: "Seleccionar"; }
.pase--elegido .pase-elegir::after { content: "Elegido"; }

/* La marca de elegido, sobre el emblema y no en el cuerpo: es donde se mira.
   El ancla es el `position: relative` de `.pase-arte`. El fondo es opaco porque
   debajo hay una imagen cualquiera, no una superficie de la paleta — la misma
   razón que en la esquina de la tarjeta de tienda. */
.pase-check {
  position: absolute;
  inset-block-start: 2px;
  inset-inline-end: 2px;
  display: flex;
  border-radius: var(--r-pastilla);
  background-color: var(--fondo);
  /* El mismo verde que la marca de la rejilla de tienda: significan lo mismo
     —"elegido para el pedido"— y con dos colores parecerían dos cosas. */
  color: var(--ok-alto);
}
.pase-check .icono { font-size: 16px; width: 16px; height: 16px; }
/* **`display: flex` gana a lo que el navegador trae de serie para `[hidden]`**,
   así que hay que decirlo. Sin esto la marca salía en las cuatro tarjetas a la
   vez y ninguna estaba elegida. Es exactamente lo que ya documenta
   `.articulo-check[hidden]`, y se me pasó al escribir ésta. */
.pase-check[hidden] { display: none; }

/* La tarjeta elegida se marca con el filo del acento, no con relleno: al lado
   de la de tienda —que sí se ilumina entera— un segundo bloque encendido
   competiría con ella, y lo que se elige es uno de los dos. */
.pase--elegido {
  border-color: var(--acento-hondo);
  background-color: var(--superficie-3);
}


/* ------------------------------------------------- compras sin canjear

   Lo que se ha pagado y Epic no ha entregado, dentro del bloque de compras
   reales. No es una tabla: son pocas filas y cada una lleva su propia acción,
   que es justo lo que una tabla hace mal. */
.pendientes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}

/* Cada fila sobre el escalón de encima de la tarjeta: se separa por superficie
   y borde, sin sombra (CLAUDE.md, Profundidad). El nombre a la izquierda y la
   acción a la derecha, que es donde se busca. */
.pendiente {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  padding: var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
}

.pendiente-nombre { margin: 0; font-weight: 600; color: var(--texto-alto); }
.pendiente p { margin: 0; }



/* ==========================================================================
   La tienda pública del Global Store Link — `/fn/{token}`
   ==========================================================================

   La segunda página pública, y la primera que se parece a una aplicación:
   cabecera, pestañas, buscador, filtros y rejilla. De ahí las dos diferencias
   con la página del Global Friends Link:

   1. **el tema tiñe la página entera** y no una tarjeta. `data-tema` va en el
      `.fn` de fuera, así que el bloque de tokens del tema claro —que se
      comparte con `.acceso-tarjeta`, unas líneas más arriba— redefine aquí el
      papel, las superficies y los textos de todo lo de dentro;
   2. **las superficies son opacas**. Allí la tarjeta es traslúcida sobre la
      foto del vendedor y el contraste hay que medirlo contra el peor caso
      compuesto; aquí no hay foto, así que los números son los de siempre:
      15,88 / 11,81 / 9,80 / 7,73:1 en oscuro y 17,70 / 13,21 / 7,11 / 5,54:1
      en claro, de `--texto-alto` a `--texto-tenue`.

   **Ni un color de marca escrito aquí dentro.** El acento entra una sola vez,
   en el `style` de `.fn`, y de él salen la pestaña activa, el filtro elegido,
   los botones y el foco. Lo único fijo es el dorado de los V-Bucks, que es la
   moneda del juego y no la marca de nadie. */

.fn {
  /* **El margen lateral de la página, y sólo hay uno.** Lo comparten la
     cabecera, las pestañas y el cuerpo —donde van el buscador, los filtros, la
     línea del recuento y la rejilla—, así que el logo, la primera pestaña y la
     primera columna de tarjetas caen en la misma vertical, y el botón de entrar
     acaba en el borde derecho de la última columna.

     Antes había dos anchos: la cabecera y las pestañas iban a pantalla completa
     y el cuerpo estaba topado en 1600px y centrado. En un monitor ancho eso
     dejaba el logo y el botón pegados a los bordes con la rejilla flotando en
     medio, que es exactamente lo que no puede pasar cuando las tres bandas son
     la misma página. */
  --fn-margen: var(--e6);

  /* --- El naranja de «Más vendido» --------------------------------------
     **Es de la tienda, no de la casa**, y por eso vive aquí dentro y no en el
     `:root`. El ámbar de `--energia` es un token de marca del panel y la
     tienda no lleva ninguno: lo que la tiñe es el acento que elige el vendedor
     (CLAUDE.md, Paleta). Éste es la excepción razonada — la marca tiene que
     destacar sobre un escaparate donde el acento ya está usado en las
     pestañas, los filtros y el botón de enviar— y por eso es un token propio
     con su nombre, y no `--energia` colándose en `.fn`.

     Los dos son fijos en los dos temas: lo que hay detrás de la etiqueta es
     una foto, y el filo se ha medido contra las dos superficies de tarjeta.

       --fn-top         el relleno de la pastilla. Con el texto en blanco deja
                        **5,18:1**, o sea AA de sobra.
       --fn-top-borde   el filo y el brillo. Es el único naranja que pasa el
                        3:1 de un borde de control **en los dos temas**:
                        4,68:1 sobre la tarjeta oscura (#1E1D27) y 3,56:1 sobre
                        la clara (#FFFFFF). Los más claros se caen en el tema
                        claro y los más hondos en el oscuro. */
  --fn-top: #C2410C;
  --fn-top-borde: #EA580C;

  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--fondo);
  /* Vuelve a declarar el color por lo mismo que `.acceso-tarjeta`: el
     `color: var(--texto)` de `body` ya se resolvió con el token del tema
     oscuro, y lo que baja por herencia es ese color y no la variable. Sin esta
     línea, el tema claro sale con el gris del panel sobre papel claro. */
  color: var(--texto);
}

/* El papel del tema claro. **Sólo existe aquí**: la página del enlace de
   amistad no tiene papel —detrás de su tarjeta hay una foto— y por eso este
   token no está en el bloque compartido. Es el que ya describía aquel
   comentario: L* 91,6, ocho puntos y medio por debajo de la tarjeta blanca,
   el mismo escalón que separa las superficies en oscuro. */
.fn[data-tema="claro"] { --fondo: #E9E5F1; }

/* --- El fondo de la tienda -----------------------------------------------
   Detrás de `/fn/` va **la identidad del vendedor** y no el papel plano: su
   acento (`data-fondo="color"`) o la imagen de su portada de acceso,
   difuminada (`data-fondo="imagen"`). Lo elige en Global Store Link; sin
   elegir, imagen si la tiene y, si no, color (`store_link.estilo_de`). Los
   halos de acento que había antes se fueron con esto: tapados por el fondo,
   no hacían nada.

   **La capa es lo que garantiza la lectura.** Es el papel del tema —oscuro en
   oscuro, claro en claro— con su opacidad, y se calcula contra **el peor fondo
   posible**, que no es un acento concreto sino el extremo: el blanco en el
   tema oscuro y el negro en el claro. Cualquier acento y cualquier foto caen
   entre los dos, así que lo que vale ahí vale para todos.

   Lo que tiene que pasar AA encima es **el texto que va directo sobre el
   fondo**: la cabecera, las pestañas cerradas, los títulos de sección, «N
   objetos en la tienda de hoy», la cuenta atrás y los mensajes de vacío. Todo
   eso es `--texto-suave` o más, y la capa está calculada para `--texto-suave`:

     oscuro, 76%  sobre blanco puro    --texto-suave 4,68:1  (--texto-alto 8,91)
     claro,  82%  sobre negro puro     --texto-suave 4,71:1  (--texto-alto 9,46)

   **`--texto-tenue` no llega y no se usa directo sobre el fondo**: para
   llevarlo a AA la capa clara tendría que ir al 91% y del color del vendedor
   quedaría un 9%. Por eso la pestaña cerrada —que era la única— subió a
   `--texto-suave`; lo que la distingue sigue siendo el candado y que no tiene
   caja. Lo que va dentro de una tarjeta, un campo o una pastilla no cuenta
   aquí: tienen su propia superficie opaca.

   Lo fija `tests/test_paleta.py`, que lee estos dos porcentajes de la hoja y
   rehace la cuenta. Si se tocan, se corre ese fichero.

   **Por qué un pseudo-elemento fijo y no `background-attachment: fixed`**: lo
   segundo repinta el fondo en cada paso del scroll —y en iOS ni siquiera se
   respeta—. Un `position: fixed` es una capa propia que el compositor sólo
   desplaza, así que el difuminado se calcula una vez al pintar y el scroll no
   lo vuelve a tocar. **Y no se difumina en el servidor** porque el servidor no
   tiene la imagen: es una URL del vendedor que no se descarga ni se copia
   (`store_portada`), y bajarla para procesarla sería salir desde aquí a una
   dirección que escribe un tercero.

   Se cuelga de `[data-fondo]` y no de `.fn` a secas porque `.fn` también es la
   clase de las vistas previas del panel, y una capa fija ahí dentro taparía
   la pantalla entera del panel. Nada de esto se anima. */
.fn[data-fondo] {
  --fn-capa: color-mix(in srgb, var(--fondo) 76%, transparent);
  /* El contexto de apilado propio es lo que deja a las dos capas en
     `z-index: -1` por encima del fondo de `.fn` y por debajo de todo lo que
     hay dentro. */
  isolation: isolate;
}
.fn[data-fondo][data-tema="claro"] {
  --fn-capa: color-mix(in srgb, var(--fondo) 82%, transparent);
}
.fn[data-fondo]::before,
.fn[data-fondo]::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
/* Abajo, el acento. Con imagen también: es lo que se ve mientras carga, o si
   no carga nunca. */
.fn[data-fondo]::before { background-color: var(--acento); }
/* Encima, la capa del tema. */
.fn[data-fondo]::after { background-color: var(--fn-capa); }

/* **El borde de los controles que van directos sobre el fondo.** El de
   siempre, `--borde-alto`, llega a 3:1 sobre el papel plano; sobre el fondo
   teñido, con el peor color detrás, se queda en 1,73:1 (oscuro) y 1,91:1
   (claro). Un relleno translúcido del papel dentro del botón no lo arregla:
   el borde se mide contra lo que hay **fuera**, y fuera el fondo ya es un 76%
   de ese mismo papel. Así que se sube el borde, **lo justo** y con el mismo
   tono y saturación que `--borde-alto`, sólo cambiando la claridad:

     oscuro  #6C6781 -> #9692A8   3,11:1 con blanco detrás, 6,62:1 con negro
     claro   #8C8499 -> #6A6276   3,10:1 con negro detrás, 4,88:1 con blanco

   Va sólo en lo que no tiene una tarjeta debajo: los secundarios de la
   cabecera y del buscador, el campo de buscar y las pestañas que no son la
   activa. Dentro de una tarjeta o un diálogo el borde de siempre ya llega.
   Lo fija `tests/test_paleta.py`. */
.fn[data-fondo] { --fn-borde-control: #9692A8; }
.fn[data-fondo][data-tema="claro"] { --fn-borde-control: #6A6276; }
.fn[data-fondo] .fn-alto .boton--secundario:not(:hover),
.fn[data-fondo] .fn-buscar .boton--secundario:not(:hover),
.fn[data-fondo] .fn-buscar .campo:not(:focus),
.fn[data-fondo] .fn-pestana:not(.fn-pestana--activa) {
  border-color: var(--fn-borde-control);
}

/* **El acento que se lee sobre el fondo.** El nombre de la tienda y los
   iconos de los secundarios de la cabecera y del buscador van en el acento y
   directos sobre la página, y la página ahora es ese mismo acento con la capa
   encima: un amarillo en tema claro, o un morado oscuro en el oscuro, se
   pierde contra sí mismo. El servidor calcula una variante por tema
   (`store_link.acento_legible`): mismo tono y saturación, la claridad movida
   lo justo para AA contra el fondo de verdad —o contra los dos extremos si es
   una imagen—. Si el acento ya llega, es el mismo.

   **Sólo texto e iconos.** Los rellenos —la pestaña activa, los botones— y
   los bordes de foco siguen con el acento puro: es el color del vendedor, y
   de esos avisa el panel (`aviso_de_acento`). */
.fn[data-fondo] { --acento-fondo: var(--acento-legible-oscuro, var(--acento)); }
.fn[data-fondo][data-tema="claro"] {
  --acento-fondo: var(--acento-legible-claro, var(--acento));
}
/* El degradado del nombre es el de siempre —ver `.fn-nombre-marca`—, sólo que
   arranca de la variante: hacia el blanco en oscuro y hacia el negro en claro,
   así que los dos extremos se leen igual o mejor que el arranque. */
.fn[data-fondo] .fn-nombre-marca {
  color: var(--acento-fondo);
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--acento-fondo) 78%, #FFFFFF 22%) 0%,
    color-mix(in srgb, var(--acento-fondo) 55%, #FFFFFF 45%) 100%);
}
.fn[data-fondo][data-tema="claro"] .fn-nombre-marca {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--acento-fondo) 78%, #000000 22%) 0%,
    color-mix(in srgb, var(--acento-fondo) 40%, #000000 60%) 100%);
}
.fn[data-fondo] .fn-alto .boton--secundario,
.fn[data-fondo] .fn-buscar .boton--secundario {
  color: var(--acento-fondo);
}

/* La imagen, difuminada. Sale 48px por cada lado porque el difuminado aclara
   los bordes —mezcla con lo que hay fuera, que es nada— y así ese borde cae
   fuera de la pantalla. Un fijo que se sale no crea scroll. */
.fn[data-fondo="imagen"]::before {
  inset: -48px;
  background-image: var(--fn-fondo-imagen);
  background-size: cover;
  background-position: center;
  filter: blur(24px);
}

/* El acento del vendedor manda también donde el panel usa el del logo: relleno
   de botón, borde del filtro activo y filos. En el panel `--acento-hondo` es
   un color fijo de la paleta; aquí no puede serlo, o el filtro elegido de una
   tienda verde saldría con el borde morado de la casa. */
.fn[data-tema] { --acento-hondo: var(--acento); }

/* El botón toma el acento **plano**, como el de la página del enlace de
   amistad y por lo mismo: `--grad-acento` es el degradado de la casa y aquí el
   color lo pone el vendedor. El texto de encima no se elige, se calcula
   (`Estilo.acento_texto`): negro o blanco, el que se lea, y nunca baja de
   4,58:1. */
.fn .boton {
  background-color: var(--acento);
  background-image: none;
  border-color: var(--acento);
  color: var(--acento-texto);
}
.fn .boton:hover { color: var(--acento-texto); }
.fn .boton .icono { color: var(--acento-texto); }
/* El secundario ya se pinta solo con las variables del acento
   (`--acento`, `--acento-tenue`, `--acento-hondo`), así que sólo hay que
   devolverle lo que la regla de arriba le ha quitado. */
.fn .boton--secundario {
  background-color: transparent;
  border-color: var(--borde-alto);
  color: var(--acento);
}
.fn .boton--secundario .icono { color: inherit; }
.fn .boton--secundario:hover {
  background-color: var(--acento-tenue);
  border-color: var(--acento-hondo);
  color: var(--acento-alto);
}

/* --- Cabecera ------------------------------------------------------------
   El logo y el nombre a la izquierda, entrar a la derecha. La frontera la hace
   el borde, no una sombra ni un escalón de superficie: la cabecera comparte el
   papel de la página, como la barra lateral del panel comparte su negro. */
.fn-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e4);
  flex-wrap: wrap;
  padding: var(--e4) var(--fn-margen);
  border-bottom: 1px solid var(--borde);
}

/* La izquierda de la cabecera: de quién es la tienda y el tema de quien la
   mira, juntos porque los dos son "dónde estoy". */
.fn-marca-grupo {
  display: flex;
  align-items: center;
  gap: var(--e3);
}

/* El logo, a la altura de la cabecera. A 36px se quedaba pequeño al lado del
   nombre y de una barra de 68px de alto; a 52 pesa lo que tiene que pesar,
   que es lo primero que se mira al abrir la tienda de alguien. */
.fn-marca { font-size: 1.25rem; }
.fn-marca .marca-logo { width: 52px; height: 52px; }

/* El botón del tema. Los dos iconos van dentro y el CSS enseña el que toca:
   en oscuro el sol —a donde lleva— y en claro la luna. Así el guion no toca el
   marcado para decir lo mismo. */
.fn-tema {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--e2);
  background: none;
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  cursor: pointer;
  transition: color var(--t-rapida), border-color var(--t-rapida);
}
.fn-tema:hover { color: var(--texto-alto); border-color: var(--borde-alto); }
/* Lo enciende el guion: sin JavaScript no hay dónde recordar la elección. */
.fn-tema[hidden] { display: none; }

.fn[data-tema="oscuro"] .fn-tema .icono--luna,
.fn[data-tema="claro"] .fn-tema .icono--sol { display: none; }

.fn-entrar { flex: none; }

/* --- Las cuatro pestañas -------------------------------------------------
   Fila que se desplaza dentro de su caja en móvil: con cuatro pestañas y el
   área táctil, partirlas en dos renglones empuja la rejilla fuera de la
   primera pantalla.

   **Cómo se pintan está más abajo**, con el resto de la tercera vuelta: son
   botones con fondo desde que se vio que unos rótulos con una rayita debajo
   no se leen como algo que se pulsa. */
.fn-pestanas {
  display: flex;
  overflow-x: auto;
}

/* --- El cuerpo -----------------------------------------------------------
   Ancho máximo: una rejilla de trescientas tarjetas a lo ancho de un monitor
   de 27" deja diez columnas y ninguna se mira. */
.fn-cuerpo {
  width: 100%;
  /* **Sin tope de ancho.** Lo hubo —1180 primero, 1600 después— y siempre fue
     el mismo problema: la rejilla centrada dentro de una cabecera que iba a
     pantalla completa. Ahora manda el margen de arriba y la rejilla ocupa lo
     que haya, que es lo que hace que las tres bandas empiecen y acaben en la
     misma vertical. Lo que no se deja crecer es la lámina; de eso se encarga el
     `minmax` de la rejilla. */
  padding: var(--e5) var(--fn-margen);
  display: flex;
  flex-direction: column;
  gap: var(--e4);
}

.fn-buscar {
  display: flex;
  align-items: center;
  gap: var(--e2);
}
.fn-buscar .campo { flex: 1 1 auto; min-width: 0; }
.fn-buscar .boton { flex: none; }

.fn-filtros { margin: 0; }

/* El recuento y la cuenta atrás, los dos dentro de su frase y en el cuerpo de
   texto normal: aquí no hay cifras gigantes (CLAUDE.md, Cifras). */
.fn-resumen {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2) var(--e4);
  color: var(--texto-suave);
}
.fn-resumen .cifras { color: var(--texto-alto); }
.fn-rotacion {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
}

/* --- El reloj de la cuenta atrás ------------------------------------------
   El gesto del reloj animado de lucide, hecho con dos `@keyframes`: la aguja de
   las horas da una vuelta entera en 0,6 s y el minutero gira 45° en 0,5 s, las
   dos desde el centro de la esfera.

   **El disparo no puede ser el hover**, que es lo que usa lucide: en un móvil
   no hay ratón y esta página se mira sobre todo desde uno. Así que se dispara
   solo: el ciclo dura 60 s y el movimiento ocupa el principio —el 1% y el
   0,83%—, así que **late una vez por minuto** y el resto del ciclo el icono
   está quieto. De propina sale gratis lo otro que hacía falta: una animación
   así arranca al pintarse, o sea que **también gira al recargarse la tienda**.

   Las dos agujas descansan en su posición dibujada, y por eso una va de 0 a
   360° y la otra de -45° a 0: al terminar el ciclo, la primera vuelve a 0
   —360° es lo mismo que 0 y no se ve— y la segunda salta a -45° justo en el
   fotograma en que empieza a barrer, que es donde no se nota. Con las dos
   acabando en su valor de destino, el reloj se quedaría 59 segundos torcido.

   `transform-box: view-box` va escrito aunque sea el valor inicial, porque de
   él dependen esos 12: son unidades del `viewBox`, no píxeles de pantalla. */
.icono--reloj .reloj-horas,
.icono--reloj .reloj-minutos {
  transform-box: view-box;
  transform-origin: 12px 12px;
}
/* **Un latido y no un bucle de 60 s.** El bucle dejaba la animación viva el
   minuto entero aunque se moviera el 1% de él, y una animación de un `<path>`
   de SVG no la mueve el compositor: medido el 18/10/2026, era un recálculo de
   estilos por fotograma mientras se deslizaba la tienda. Aquí late una vez al
   pintarse —también al recargar, sin JavaScript— y el resto de latidos los da
   el guion de `/fn/` con la Web Animations API, **uno por minuto y sólo con
   el reloj en pantalla y la pestaña a la vista**. */
.icono--reloj .reloj-horas {
  animation: fn-reloj-horas 0.6s ease-in-out 1;
}
.icono--reloj .reloj-minutos {
  animation: fn-reloj-minutos 0.5s ease-in-out 1;
}

@keyframes fn-reloj-horas {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@keyframes fn-reloj-minutos {
  from { transform: rotate(-45deg); }
  to   { transform: rotate(0deg); }
}

/* --- El botón de recargar -------------------------------------------------
   Las dos flechas giran **mientras se recarga**, no un fotograma suelto: el
   enlace navega y la vuelta sigue hasta que llega la página nueva, que es
   exactamente el tiempo que hay que llenar. La clase la pone el guion al
   pulsar; sin JavaScript el enlace recarga igual, sólo que sin giro. */
.icono--recargar .recargar-aspas {
  transform-box: view-box;
  transform-origin: 12px 12px;
}
.fn-recargando .recargar-aspas {
  animation: fn-vuelta 0.6s linear infinite;
}

@keyframes fn-vuelta {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Los dos gestos se van enteros, y las dos cosas que informan se quedan: el
   número de la cuenta atrás sigue corriendo —lo mueve el guion, no esto— y el
   botón sigue recargando. */
@media (prefers-reduced-motion: reduce) {
  .icono--reloj .reloj-horas,
  .icono--reloj .reloj-minutos,
  .fn-recargando .recargar-aspas,
  .fn-entrando .icono--entrar .entrar-flecha { animation: none; }
}

/* --- La rejilla ----------------------------------------------------------- */
/* Las columnas se reparten **todo** el ancho disponible y la lámina se queda
   entre 180 y 260px, que es donde un render se reconoce sin llenar la pantalla.
   Sale de la propia cuenta de `auto-fill`: entran tantas pistas de 180 como
   quepan y el `1fr` reparte lo que sobra, que nunca llega a 180 —si llegara,
   cabría otra columna—. El techo real es `180 + 192/n`: 244px con tres
   columnas, 228 con cuatro y menos de 200 a partir de siete. */
.fn-rejilla {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--e3);
}
/* La del día se esconde entera en la categoría «Pases de temporada», donde lo
   único que se mira es la sección de encima. */
.fn-rejilla[hidden] { display: none; }

.fn-articulo { display: flex; }
/* Lo esconde el filtro del navegador, y sin esta línea no se escondería: el
   `display` del autor le gana al `[hidden] { display: none }` del navegador
   (CLAUDE.md, "Lo que se esconde con `hidden`"). */
.fn-articulo[hidden] { display: none; }

/* --- Los pases de temporada, encima de la rejilla ------------------------
   Mismo marcado que un artículo y **misma rejilla**, así que aquí sólo hay lo
   que los distingue: son pocos —cuatro como mucho— y su arte es apaisado, así
   que la pista es más ancha y la lámina no es cuadrada. */

.fn-pases {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
/* La sección entera se va cuando la categoría no es la suya o cuando la
   búsqueda no deja ningún pase (CLAUDE.md, "Lo que se esconde con `hidden`"). */
.fn-pases[hidden] { display: none; }

.fn-pases-titulo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  font-size: 1.125rem;
}
/* El billete no es un adorno del rótulo: es lo que lo separa de un título
   cualquiera en una página llena de secciones. Va a la altura de la letra. */
.fn-pases-titulo .icono { flex: none; }

.fn-rejilla--pases {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

/* La lámina de un pase no lleva color de rareza —un pase no tiene— y su arte es
   apaisado: cuadrada, el recorte se comía la mitad del dibujo. `contain` y no
   `cover` por lo mismo: es una ilustración con texto dentro, y recortarla le
   quita el nombre de la temporada. */
.fn-lamina--pase { aspect-ratio: 16 / 9; }
.fn-lamina--pase img { object-fit: contain; }

/* La temporada, **dentro de la imagen** y en su esquina superior izquierda.

   Va sobre un velo negro propio y no sobre una superficie del panel, y eso es
   lo que la hace legible: debajo hay una de las cuatro ilustraciones de pase
   —moradas, azules, naranjas— y ninguna superficie de la paleta tiene un
   contraste garantizado contra todas ellas.

   **El velo es fijo en los dos temas**, y por eso lleva el negro escrito y no
   un token: lo que hay detrás es una foto, no el fondo de la página, así que el
   blanco sobre negro al 62% se lee igual con el tema claro que con el oscuro.

   **El 0,62 sale de una cuenta, no del ojo**, igual que el velo de la tarjeta
   de tienda. Medido sobre el píxel **más claro** de la esquina de cada una de
   las cuatro imágenes de pase, que es el peor caso porque el velo lo oscurece
   menos:

       Pase Musical  #fff7b4  ->   6,58:1   <- el peor de los cuatro
       Pase Batalla  #f3c847  ->   8,38:1
       Pase OG       #2ebbe7  ->  10,16:1
       Pase LEGO     #4f1faf  ->  17,70:1

   **Si se toca esa opacidad hay que rehacer la cuenta**: por debajo de 0,52 el
   peor caso deja de pasar AA. Es la misma solución que `.fn-sinsaldo` y que la
   marca de «Última compra», que viven en las otras esquinas.

   Va en el mismo sitio en la tarjeta y en la ficha: con dos formas distintas
   habría que mirar la de detrás para saber de qué temporada es la que se está
   pidiendo. */
/* Las dos marcas de la esquina, apiladas y **en un solo sitio**.

   La de más vendido va arriba porque es la que cambia —hoy está y mañana no— y
   la temporada debajo porque es lo que ese pase **es**. Las dos en la misma
   esquina y no repartidas por la tarjeta: la otra la ocupa `.fn-sinsaldo`, y
   con una marca en cada lado la tarjeta se lee como un tablero.

   El posicionamiento vive aquí y no en cada etiqueta: con él repetido, las dos
   caerían una encima de otra en cuanto coincidieran, que es justo lo que pasó. */
.fn-marcas {
  position: absolute;
  top: var(--e2);
  left: var(--e2);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--e1);
}

.fn-temporada {
  padding: 2px var(--e2);
  border-radius: var(--r-pastilla);
  background-color: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  letter-spacing: 0.02em;
}
.fn-temporada[hidden] { display: none; }

/* La marca de lo que se está vendiendo. Misma forma que la temporada —misma
   esquina, misma pastilla, mismo tamaño— y lo único que cambia es el color:
   así se leen como dos etiquetas de la misma familia y no como dos adornos.

   El blanco sobre `--fn-top` deja 5,18:1, que es AA con margen. Y lleva la
   palabra además del color y del icono: el color por sí solo no es un canal
   (CLAUDE.md). */
.fn-mas-vendido {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px var(--e2);
  border-radius: var(--r-pastilla);
  background-color: var(--fn-top);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
.fn-mas-vendido[hidden] { display: none; }
/* En la ficha va dentro de la columna de datos y no sobre la lámina —ahí mide
   72px y la pastilla no cabe—, así que necesita no estirarse a todo el ancho. */
.fn-datos > .fn-mas-vendido { align-self: flex-start; }
/* La llama, a la altura de la letra y no a los 20px de un icono suelto: aquí
   acompaña a dos palabras pequeñas. La caja baja con el dibujo, o el trazo
   queda descentrado (CLAUDE.md, Iconos). */
.fn-mas-vendido .icono { width: 14px; height: 14px; flex: none; }

/* El filo cálido de la tarjeta marcada, y **el único `box-shadow` del
   proyecto**.

   La regla de CLAUDE.md sigue en pie: la profundidad se hace con superficie,
   borde y degradado, y **esto no es profundidad** — no levanta la tarjeta ni la
   pone por delante de las demás; es un halo del mismo tono del borde, pegado a
   él, que dice "mira ésta". Por eso no lleva desplazamiento ni difuminado
   grande: `0 0 0 3px` de un naranja casi transparente es el borde ensanchado,
   no una sombra.

   **Es estático**, así que no se apaga con `prefers-reduced-motion`: no hay
   nada que se mueva, y quitar un color a quien pide menos movimiento sería
   quitarle información.

   El filo pasa el 3:1 de un borde de control en los dos temas: ver
   `--fn-top-borde`. */
.fn-tarjeta--top {
  border-color: var(--fn-top-borde);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fn-top-borde) 22%, transparent);
}
/* Al pasar por encima sube como cualquier tarjeta y el borde **no vuelve al
   neutro**: el de arriba es más específico que `.fn-tarjeta:hover`, pero se
   escribe para que se lea, no para confiarlo al orden del fichero. */
.fn-tarjeta--top:hover { border-color: var(--fn-top-borde); }

/* Y el mismo filo en la ficha, alrededor del recuadro del artículo: es donde
   la ficha dice lo mismo que la tarjeta de detrás. */
.fn-ficha-objeto--top {
  border-color: var(--fn-top-borde);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fn-top-borde) 22%, transparent);
}

/* La raya entre los pases y la tienda del día. Es un filo de superficie, no un
   borde de control: `--borde` y no `--borde-alto`. */
.fn-separador {
  width: 100%;
  height: 1px;
  margin: 0;
  border: 0;
  background-color: var(--borde);
}
.fn-separador[hidden] { display: none; }
.fn-resumen[hidden] { display: none; }

/* La nota del pase dentro de la ficha, y el motivo de que no se haya podido
   agregar. Los dos son una línea debajo del selector de destino. */
.fn-ficha-nota {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.875rem;
}
.fn-ficha-nota[hidden] { display: none; }

.fn-ficha-error {
  margin: 0;
  color: var(--peligro-alto);
  font-size: 0.875rem;
}
.fn-ficha-error[hidden] { display: none; }

/* La tarjeta entera es el enlace: en una rejilla de trescientas, el objetivo
   táctil es la tarjeta y no un rótulo dentro de ella. */
.fn-tarjeta {
  display: flex;
  flex-direction: column;
  width: 100%;
  overflow: hidden;
  background-color: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto);
  text-decoration: none;
  transition: transform var(--t-rapida), border-color var(--t-rapida);
}
/* El mismo gesto que la tarjeta de cuenta del panel: sube 2px y el borde pasa
   a `--borde-alto`. Sin sombras (CLAUDE.md, Profundidad). */
.fn-tarjeta:hover {
  transform: translateY(-2px);
  border-color: var(--borde-alto);
}

/* La lámina: el render sobre el color de la rareza y **a sangre**. El fondo lo
   pone `brillo--<clave>`, la misma lista de rarezas que usan la tienda del
   panel y el Historial; el respaldo, cuando la rareza no se conoce, es el
   `.articulo, .operacion, .fn-lamina` de más arriba, que está escrito antes
   que las rarezas justo para que ellas le ganen. */
.fn-lamina {
  position: relative;
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
}
.fn-lamina img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* El render tapa el marcador de debajo mientras carga bien. */
  z-index: 1;
}
/* Si la imagen del CDN no carga, el guion marca la lámina y aparece el
   marcador que ya estaba puesto. No hay render de repuesto para un cosmético,
   así que el respaldo es decir que no hay foto y no otra foto. */
.fn-lamina--rota img { display: none; }

/* La regla de verdad de `.fn-sinfoto` está más abajo, con el resto del
   respaldo de la lámina: aquí sólo queda dicho que **no se pinta salvo que
   toque**, que es lo que se arregló. */

/* --- La tarjeta teñida con el color de la oferta --------------------------

   El degradado que Epic le da a esa oferta en su propio escaparate, y **tiñe
   la tarjeta entera, no sólo la lámina**. Es la misma excepción a la paleta
   que la tienda del panel (ver "El color de la tarjeta de tienda" en
   CLAUDE.md) y los mismos tres tokens, escritos por el servidor en el `style`
   de cada tarjeta después de validarlos contra `^[0-9a-f]{6}$`.

   `--c2` falta en la mayoría de las ofertas —80 de 346 lo traen— y se cae a
   `--c1`: un degradado de dos paradas en vez de tres.

   La excepción está acotada igual que allí: **no tiñe nada accionable**. Los
   botones, los focos, las pestañas y la franja siguen siendo del acento del
   vendedor; lo de la oferta es el fondo de su tarjeta y nada más. */
/* **Y la casilla de la ruleta entra aquí, no en una regla suya.** Es la
   tercera pieza que enseña una oferta —la tarjeta, la ficha y ahora esto— y
   con una copia de estas cinco declaraciones la que alguien tocara de menos
   sería la que se viera distinta. Lo que la casilla añade es su tamaño, que
   está más abajo. */
/* **El degradado, escrito una vez.** Lo pintan cuatro piezas —la tarjeta de
   la rejilla, el recuadro de la ficha, la casilla de la ruleta y la miniatura
   del carrito, las cuatro de `/fn/`— y con cuatro copias del mismo
   `linear-gradient` la que alguien tocara de menos sería la que se viera
   distinta. Es el mismo eje de 163° que las superficies de la casa: todo
   parece iluminado desde el mismo sitio.

   **Se llama `--oferta-grad` y no `--grad-oferta`**, y no es una manía: la
   familia `--grad-*` son los tres degradados de la casa (CLAUDE.md,
   «Degradados») y el guardián de la tienda los vigila por ese prefijo. El
   degradado de una oferta no es de la casa —es un dato de Epic— así que no le
   toma el nombre, igual que `--canje-oro` no se escribe con `--vbucks`.

   **El token se declara en las cuatro piezas y no en `.fn`**, y eso no es una
   preferencia: el `var()` de dentro de una propiedad personalizada se
   sustituye **donde se declara**, no donde se usa. Puesto en `.fn` —que no
   tiene `--c1`— el valor sale inválido y se hereda vacío, así que las cuatro
   se quedaban sin fondo. Aquí cada una trae sus `--c1/--c2/--c3` en su
   `style`, así que el token es válido en cada una. */
.fn-tarjeta--color,
.fn-ficha-objeto--color,
.fn-suerte-casilla--color,
.fn-lamina--color {
  --oferta-grad: linear-gradient(
    163deg, var(--c1), var(--c2, var(--c1)), var(--c3)
  );
}

.fn-tarjeta--color,
.fn-suerte-casilla--color { background-image: var(--oferta-grad); }
/* La tarjeta trae un `background-color` opaco que taparía el degradado. */
.fn-tarjeta--color,
.fn-suerte-casilla--color { background-color: transparent; }

/* --- La miniatura del carrito, con el color de su oferta -----------------
   **Es la misma pieza vista pequeña, y hasta ahora no lo parecía.** La
   tarjeta de la rejilla y el recuadro de la ficha llevan el degradado de la
   oferta detrás de un render transparente; la miniatura del carrito llevaba
   sólo el respaldo por rareza, así que el mismo artículo salía blanco en la
   rejilla y azul en el carrito.

   Aquí el degradado va **en la propia lámina** y no en un antepasado, y esa
   es toda la diferencia: lo que hay que teñir es la miniatura y no la fila,
   que lleva el nombre, el destinatario y el precio encima. La excepción sigue
   acotada donde siempre —no tiñe nada accionable— y el degradado es el mismo
   token de arriba. */
/* **Con `.fn` delante**, que ya no hace falta para ganarle al respaldo neutro
   (`:where(.fn) .fn-lamina`, que no suma peso) pero sí para ganarle a
   `.brillo--<rareza>`, que la miniatura puede llevar también: el color de la
   oferta manda sobre el de la rareza. */
.fn .fn-lamina--color {
  background-color: transparent;
  background-image: var(--oferta-grad);
}

/* **La ficha, con el mismo color y el mismo cálculo.** El recuadro del
   artículo es la misma tarjeta vista de cerca: abrir una tarjeta teñida y
   encontrarse un recuadro neutro es un salto que se nota.

   **Lo que cambia es dónde cae el velo, y es la única diferencia.** En la
   tarjeta el texto va debajo de una lámina grande, así que el velo cubre su
   franja y el render se queda limpio. Aquí el texto va **al lado** de una
   lámina de 72px: con el velo sólo en su mitad, el recuadro sale partido en
   dos —una mitad al color crudo de Epic y otra apagada— y con un `ffff65`
   eso es una losa amarilla dentro del diálogo.

   Así que el velo va sobre el recuadro **entero**, delante del degradado y en
   la misma declaración. El render no se apaga con él: es un `<img>`, y el velo
   es fondo del recuadro, o sea que está detrás. El resultado es un panel
   teñido y tranquilo con la miniatura por delante.

   Y **el texto se calcula contra exactamente esta composición**, que es la
   misma de la tarjeta: mismo velo, mismas opacidades, misma cuenta. Por eso no
   hay un segundo número que mantener. */
.fn-ficha-objeto--color {
  background-color: transparent;
  background-image: var(--fn-velo-oferta), var(--oferta-grad);
  color: var(--tarj-texto);
}
/* **El color del texto lo calcula el servidor y lo elige el CSS.** Vienen los
   dos en el `style` de la tarjeta —uno por tema— y aquí se coge el que toca:
   el tema se puede cambiar sin volver a pedir la página (las fotos de las
   escenas lo cambian en el DOM), así que con un solo valor las tarjetas se
   quedarían con el texto del otro tema. */
.fn[data-tema] .fn-tarjeta--color,
.fn[data-tema] .fn-ficha-objeto--color,
.fn[data-tema] .fn-suerte-casilla--color { --tarj-texto: var(--tarj-oscuro); }
.fn[data-tema="claro"] .fn-tarjeta--color,
.fn[data-tema="claro"] .fn-ficha-objeto--color,
.fn[data-tema="claro"] .fn-suerte-casilla--color { --tarj-texto: var(--tarj-claro); }
/* El filo neutro desaparecería sobre según qué color. Se hace con el propio
   texto calculado, muy diluido: así el borde existe en las dos direcciones
   —sobre un amarillo y sobre un negro— sin meter un tercer color.

   **`:not(--top)` porque el naranja de «Más vendido» manda sobre esto**: esa
   regla está escrita antes y tiene la misma especificidad, así que sin el
   `:not` el color de la oferta se llevaría por delante la marca. Se escribe y
   no se confía al orden del fichero. */
.fn-tarjeta--color:not(.fn-tarjeta--top),
.fn-ficha-objeto--color:not(.fn-ficha-objeto--top),
.fn-suerte-casilla--color {
  border-color: color-mix(in srgb, var(--tarj-texto) 22%, transparent);
}
.fn-tarjeta--color:not(.fn-tarjeta--top):hover {
  border-color: color-mix(in srgb, var(--tarj-texto) 45%, transparent);
}
/* La lámina se queda transparente: debajo está el color de la oferta, que es
   justo lo que hay que ver a sangre. Los renders de Epic son PNG con el 87% de
   píxeles transparentes, así que el degradado se ve **por debajo del dibujo**
   y no sólo alrededor — que es la diferencia entre teñir la tarjeta y teñirle
   un marco.

   Se quitan **el color y el degradado**: `.fn-lamina` trae los dos, y dejando
   el `background-color` puesto la lámina seguía siendo la superficie del tema
   con el color de la oferta escondido detrás.

   El `brillo--<rareza>` que llevaría se queda para las tarjetas sin color, que
   es donde sigue siendo el respaldo. */
.fn-tarjeta--color .fn-lamina,
.fn-ficha-objeto--color .fn-lamina,
.fn-suerte-casilla--color .fn-lamina {
  background-color: transparent;
  background-image: none;
}

/* El bloque de texto, con el velo de su tema y el color que calculó el
   servidor contra ese velo. **El crema fijo no vale aquí** y está medido: hay
   ofertas de `ffff65` a `939300`, y ahí un color claro se pierde. */
.fn-tarjeta--color .fn-datos,
.fn-suerte-casilla--color .fn-suerte-casilla-datos {
  background-image: var(--fn-velo-oferta);
  color: var(--tarj-texto);
}
/* Los tres heredan en vez de llevar su token: el color de esta tarjeta no es
   el del tema. **La jerarquía la hace el tamaño**, igual que en la tienda del
   panel, y por eso el dato de apoyo no baja de color — bajarlo sería volver a
   tener que medir un tono más contra cada degradado de Epic. */
.fn-tarjeta--color .fn-nombre,
.fn-tarjeta--color .fn-precio,
.fn-tarjeta--color .fn-resta,
.fn-ficha-objeto--color .fn-nombre,
.fn-ficha-objeto--color .fn-precio,
.fn-suerte-casilla--color .fn-suerte-casilla-nombre,
.fn-suerte-casilla--color .fn-suerte-antes,
.fn-suerte-casilla--color .fn-suerte-ahora { color: inherit; }

/* **Lo que no alcanza pierde el color, y es a propósito.** La tarjeta apagada
   se hace en gris —lámina desaturada y texto tenue—, y eso se mide contra la
   superficie del tema; sobre un degradado de Epic habría que volver a medirlo
   contra trescientos fondos distintos. Sin la clase de color, todas las reglas
   de `--sinsaldo` de más abajo valen tal cual y sin excepciones.

   Y no se pierde ningún canal: el rótulo escrito encima de la lámina sigue
   diciendo con palabras por qué esa tarjeta no es como las demás. */
.fn-tarjeta--color.fn-tarjeta--sinsaldo:not(.fn-tarjeta--top) {
  background-image: none;
  border-color: var(--borde);
}
.fn-tarjeta--color.fn-tarjeta--sinsaldo { background-image: none; }
.fn-tarjeta--color.fn-tarjeta--sinsaldo .fn-datos {
  background-image: none;
  color: var(--texto);
}

/* El texto va debajo de la lámina. Sobre la superficie de la tarjeta cuando no
   hay color de oferta, y sobre el velo del tema cuando sí lo hay. */
.fn-datos {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding: var(--e3);
}

/* **El bloque de texto llega hasta el fondo de la tarjeta, y hace falta.**

   La rejilla estira las tarjetas de una fila a la altura de la más alta, y esa
   altura la decide el nombre más largo: uno de dos renglones hace crecer a
   todas las de al lado. El bloque de texto, en cambio, medía **sólo su
   contenido**, así que en una fila mixta las de nombre corto se quedaban con
   una franja de sobra debajo — y ahí asomaba el degradado de la oferta **sin
   el velo delante**, que es donde se vio: una banda de color cruda bajo el
   bloque claro. Se nota en las filas mixtas y en ninguna otra, y en el móvil a
   dos columnas más, porque ahí los nombres parten antes.

   `flex-grow` y no `margin-top: auto`: aquello empujaría el bloque hacia abajo
   dejando el hueco **encima** en vez de debajo, que es el mismo agujero movido
   de sitio. Y `flex-shrink: 0` para que en una tarjeta apretada el texto no se
   coma su propio aire.

   **El texto se queda arriba dentro del bloque**, que es lo que `flex-start`
   —el valor de partida de `justify-content`— ya hace: centrarlo dejaría los
   nombres de una y dos líneas a alturas distintas dentro de la misma fila, y
   la rejilla se leería desalineada.

   Va en la tarjeta y no en `.fn-datos` a secas: en la ficha ese mismo bloque
   va **al lado** de la lámina y no debajo, y allí no hay nada que rellenar.

   La lámina, a su vez, no encoge: con la tarjeta estirada tiene que conservar
   su cuadrado, y el sobrante es justo lo que se lleva el texto. */
.fn-tarjeta > .fn-datos { flex: 1 0 auto; }
.fn-tarjeta > .fn-lamina { flex: none; }

.fn-nombre {
  color: var(--texto-alto);
  font-weight: 600;
  line-height: 1.3;
  /* Dos líneas y a partir de ahí se corta: los nombres de las canciones con su
     artista detrás llegan a cinco renglones y descuadran la fila entera. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* **El icono de los V-Bucks se tiñe con el color del texto que lo acompaña.**

   El fichero es un glifo **blanco** (medido: el 100% de sus píxeles opacos son
   #F8F8F8), así que en el tema claro se quedaba en 1,06:1 sobre la tarjeta —o
   sea, no se veía— y en una tarjeta agrisada seguía blanco y brillante al lado
   de un texto apagado.

   Se pinta como máscara: el PNG aporta la forma —su canal alfa— y el color lo
   pone `currentColor`, que es el del `.fn-precio` o el del `.fn-saldo` de
   turno. Con eso, el token del oro decide el texto **y** el icono a la vez, y
   en la tarjeta sin saldo el icono se apaga solo con la cifra.

   Sólo dentro de `.fn`: en el panel el icono sigue siendo la imagen de siempre,
   que allí cae sobre superficies oscuras y se ve. */
/* **La moneda no tiene color propio: hereda el del texto.** Tuvo el oro de los
   V-Bucks y después el acento del vendedor sobre una pastilla oscura, y las dos
   veces era lo mismo: un color más compitiendo en una página que ya tiene el
   del vendedor. Ahora el glifo se pinta con `currentColor`, así que sale blanco
   sobre el tema oscuro y negro sobre el claro, y no hay ninguna cuenta de
   contraste que hacer: es el mismo color que la cifra de al lado.

   Sigue siendo una máscara y no un `<img>` porque el fichero es un glifo
   **blanco** (medido: el 100% de sus píxeles opacos son #F8F8F8) y sobre papel
   claro no se vería. */
.fn .vbuck {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask: url('/static/img/vbuck-lm.png') center / contain no-repeat;
  mask: url('/static/img/vbuck-lm.png') center / contain no-repeat;
}

/* El precio con el icono autoalojado y sin la palabra (CLAUDE.md, Cifras). El
   dorado es el de los V-Bucks y es **el único color fijo de esta página**: es
   la moneda del juego, no la marca de nadie. */
/* Sin dorado: la cifra va en el texto alto de su tema, como cualquier otro
   dato de la tarjeta. El icono la sigue por `currentColor`. */
.fn-precio {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  color: var(--texto-alto);
  font-weight: 600;
}
.fn-precio .vbuck { flex: none; width: 20px; height: 20px; }

.fn-resta {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--texto-tenue);
  font-size: 0.8125rem;
}
.fn-resta .icono { font-size: 16px; width: 16px; height: 16px; }

/* --- Los diálogos --------------------------------------------------------
   No son un `<dialog>`: eso necesita `showModal()`, o sea JavaScript, y estas
   pantallas tienen que funcionar sin él. Viven en la URL, como los cajones del
   panel, y el velo es un elemento normal en posición fija. */
/* Los dos diálogos se pintan siempre y nacen escondidos, así que esta línea no
   es opcional: sin ella el `display: flex` de aquí abajo le gana al
   `[hidden] { display: none }` del navegador y los dos saldrían abiertos a la
   vez (CLAUDE.md, "Lo que se esconde con `hidden`"). */
.fn-velo[hidden] { display: none; }

.fn-velo {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--e4);
  background-color: color-mix(in srgb, #000000 62%, transparent);
  overflow-y: auto;
}

/* **El velo que va por encima de la portada de entrar**: la ayuda del
   vendedor, abierta desde su «¿Cómo funciona?». La portada es `z-index: 50`,
   así que un velo normal (40) se quedaba debajo. Y detrás se ve la portada
   **difuminada y oscurecida**, la misma transición que usa ella sobre la
   tienda: se lee como un papel encima de otro, no como otra pantalla. */
.fn-velo--encima {
  z-index: 60;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.fn-dialogo {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  /* Vuelve a declarar el color por lo mismo que `.fn`: el diálogo cuelga de él
     y hereda un color ya resuelto, no la variable. */
  color: var(--texto);
}

.fn-dialogo-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
}
.fn-dialogo-alto h2 {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  font-size: 1.125rem;
}

/* El aspa del popup del envío se apaga mientras el pedido sale, así que esta
   clase necesita su propia línea (CLAUDE.md, Reglas de UI). */
.fn-cerrar[hidden] { display: none; }
.fn-cerrar {
  flex: none;
  display: inline-flex;
  color: var(--texto-suave);
  text-decoration: none;
}
.fn-cerrar:hover { color: var(--texto-alto); }

/* El objeto del que va la ficha: la lámina pequeña y sus dos datos al lado.
   Está en los dos estados —con sesión y sin ella— porque sin él el diálogo
   sería un formulario sin contexto. */
.fn-ficha-objeto {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
}
.fn-lamina--menuda {
  flex: none;
  width: 72px;
  /* Dentro de un control, un escalón menos de radio: redondearlo como la caja
     que lo contiene lo desborda (CLAUDE.md, Radios). */
  border-radius: var(--r-menudo);
}

.fn-ficha-entrar { margin: 0; color: var(--texto); }

.fn-form {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
.fn-form label { display: block; margin-bottom: var(--e1); }
.fn-form .campo { width: 100%; }

/* --- El 404 y la tienda que no se puede enseñar --------------------------- */
.fn-centro {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--e5);
}
.fn-hueco {
  max-width: 420px;
  text-align: center;
  color: var(--texto);
}
.fn-hueco h1 { font-size: 1.375rem; }

.fn-aviso { margin: 0; }

/* En móvil: tarjetas más estrechas y menos aire alrededor. La rejilla es lo
   que se viene a mirar, así que es lo último que se aprieta. */
@media (max-width: 560px) {
  /* El margen se estrecha en un sitio y las cuatro bandas lo siguen. */
  .fn { --fn-margen: var(--e3); }
  .fn-cuerpo { padding-top: var(--e4); padding-bottom: var(--e4); }
  /* **Dos columnas fijas, no `auto-fill`.** Con `minmax(140px, 1fr)` el número
     de columnas depende del ancho que quede, así que en un móvil estrecho
     —o con el aire de la página, o con el zoom del navegador subido— caía a
     una sola y la tienda se leía como una lista. Aquí se pide lo que se quiere:
     dos, siempre. `minmax(0, 1fr)` para que un nombre largo no ensanche su
     columna y descuadre la otra. */
  .fn-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* La vista previa del panel usa **el mismo marcado** que la tienda de verdad
   —las mismas clases `fn-*`—, así que hay que devolverle lo que `.fn` da por
   hecho de una página entera: alto de pantalla, aire de escritorio y enlaces
   que llevan a algún sitio. Aquí no se pulsa nada: es una foto de cómo va a
   quedar. */
.previa-tienda {
  min-height: 0;
  padding: 0;
  overflow: hidden;
  pointer-events: none;
}
.previa-tienda .fn-alto,
.previa-tienda .fn-cuerpo { padding: var(--e3); }
.previa-tienda .fn-pestanas { padding: 0 var(--e3); }
.previa-tienda .fn-marca { font-size: 1rem; }
.previa-tienda .fn-pestana { padding: var(--e2) var(--e3); font-size: 0.8125rem; }
.previa-tienda .boton { padding: var(--e2) var(--e3); font-size: 0.8125rem; }

/* --- Con sesión: el saldo, el usuario y lo que no alcanza ------------------
   La cabecera cambia de contenido y no de forma: donde estaba el botón de
   entrar van el saldo, el usuario y la salida, con el mismo `space-between` y
   el mismo margen, así que nada se mueve de sitio al entrar. */
.fn-sesion {
  display: flex;
  align-items: center;
  gap: var(--e3);
  flex-wrap: wrap;
}

/* El dorado de los V-Bucks, que es el único color fijo de esta página: es la
   moneda del juego, no la marca de nadie. */
.fn-saldo {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-pastilla);
  color: var(--texto-alto);
  font-weight: 600;
}

/* El botón de actualizar el saldo, dentro de la pastilla. Es un enlace a esta
   misma página: no hay nada que refrescar más que volver a sumar el libro, y
   eso pasa en cada visita. */
/* Las dos cifras de la pastilla. En ancho van en línea —"1.600 de 5.000", que
   es como se lee una resta— y en estrecho se apilan (ver el bloque de móvil):
   la cabecera de móvil es una rejilla de dos columnas y lo que se lleve la de
   la derecha se lo quita al nombre de la tienda, que **ya se salió una vez por
   esto**. */
.fn-saldo-cifras {
  display: inline-flex;
  align-items: baseline;
  gap: var(--e2);
  min-width: 0;
}

/* El balance del que sale lo disponible. **Más pequeño y apagado**: no es el
   dato, es de dónde viene el dato, y con el mismo peso las dos cifras
   competirían. Nace escondido —sin carrito no hay resta que explicar— así que
   necesita su línea de `[hidden]` (CLAUDE.md, Reglas de UI). */
.fn-saldo-de[hidden] { display: none; }
.fn-saldo-de {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  color: var(--texto-suave);
  font-size: 0.75rem;
  font-weight: 400;
  white-space: nowrap;
}
.fn-saldo-de .cifras { font-size: 0.75rem; }

/* El mismo pulso de 320 ms que la barra del carrito, y por lo mismo: es lo que
   dice que la cifra se ha movido. Se para solo con `prefers-reduced-motion`. */
.fn-saldo--cambia { animation: pulso 320ms var(--curva); }

/* **El recuadro entero es el enlace que actualiza.** Antes lo era sólo el
   icono de 16px, y a eso hay que acertarle: lo que se quiere pulsar es la cifra
   que se acaba de mirar. El icono se queda dentro como señal de que se puede.
   El borde y el texto responden al pasar por encima, que es lo que dice que
   esto no es una etiqueta. */
a.fn-saldo {
  text-decoration: none;
  transition: border-color var(--t-rapida), color var(--t-rapida);
}
a.fn-saldo:hover {
  border-color: var(--borde-alto);
  color: var(--texto-alto);
}

.fn-refrescar {
  display: inline-flex;
  color: var(--texto-suave);
  text-decoration: none;
}
.fn-refrescar:hover { color: var(--texto-alto); }
.fn-refrescar .icono { font-size: 16px; width: 16px; height: 16px; }

.fn-usuario { color: var(--texto-suave); }

/* --- La tarjeta que no alcanza --------------------------------------------
   Gris y con el motivo escrito encima de la lámina. Las dos cosas: el color
   solo no es un canal, y quien no lo distinga tiene que poder leer por qué esa
   tarjeta no es como las demás. */
.fn-tarjeta--sinsaldo .fn-lamina { filter: grayscale(1); opacity: 0.55; }
/* El nombre y el precio bajan a `--texto-tenue`, que pasa AA en los dos temas
   (7,73:1 en oscuro y 6,86:1 sobre la tarjeta clara). El icono va con ellos sin
   una línea más: se tiñe con `currentColor`. */
.fn-tarjeta--sinsaldo .fn-nombre,
.fn-tarjeta--sinsaldo .fn-precio { color: var(--texto-tenue); }
/* El icono ya no se tiñe con el texto —ahora lleva su propia pastilla y el
   acento del vendedor— así que se apaga a mano, o se quedaría brillando en una
   tarjeta que dice que no alcanza. */
.fn-tarjeta--sinsaldo .vbuck { opacity: 0.5; }
/* El hover no sube: no hay nada que elegir ahí. */
.fn-tarjeta--sinsaldo:hover { transform: none; }

.fn-sinsaldo {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  padding: var(--e1) var(--e2);
  background-color: color-mix(in srgb, #000000 72%, transparent);
  color: var(--texto-alto);
  font-size: 0.75rem;
  font-weight: 600;
  text-align: center;
}

/* La cabecera del diálogo de entrar vive **en un solo sitio**, más abajo: ver
   «El diálogo de entrar». Aquí hubo un segundo bloque suyo y es lo que se
   arregló — dos reglas con el mismo nombre, lejos una de otra, y la de abajo
   ganando en silencio. */

.fn-pin-nota { margin: var(--e1) 0 0; }

/* La flecha entra por la puerta al enviar. Sólo se mueve ella —la puerta se
   queda— porque es lo que hace que se lea como "entrar" y no como que algo se
   desplaza. Se anima `transform` y `opacity`, que no tocan la maquetación. */
.icono--entrar .entrar-flecha {
  transform-box: view-box;
  transform-origin: 12px 12px;
}
.fn-entrando .icono--entrar .entrar-flecha {
  animation: fn-entrar 0.6s ease-in-out;
}

@keyframes fn-entrar {
  0%   { transform: translateX(0);    opacity: 1; }
  55%  { transform: translateX(7px);  opacity: 0; }
  56%  { transform: translateX(-7px); opacity: 0; }
  100% { transform: translateX(0);    opacity: 1; }
}

/* --- El PIN y su ojo ------------------------------------------------------ */
.fn-pin-par {
  display: flex;
  align-items: center;
  gap: var(--e2);
}
.fn-pin-par .campo {
  flex: 1 1 auto;
  min-width: 0;
  /* Ocho dígitos que se leen de un vistazo: monoespaciada y algo separados. */
  letter-spacing: 0.18em;
}

.fn-ojo {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--e2);
  background: none;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  cursor: pointer;
}
.fn-ojo:hover { color: var(--texto-alto); border-color: var(--acento); }
/* Lo enciende el guion; sin JavaScript no se pinta. */
.fn-ojo[hidden] { display: none; }

/* La raya que tacha el ojo vive dentro del icono y sólo se ve cuando el PIN
   está a la vista. Un icono y no dos: con dos habría que cambiar el marcado al
   vuelo para decir lo mismo. */
.icono--ojo .ojo-tachado { opacity: 0; }
.fn-ojo[aria-pressed="true"] .icono--ojo .ojo-tachado { opacity: 1; }

/* --- El aviso de que la sesión se acaba ----------------------------------
   **Debajo de la cabecera y sólo en los últimos minutos.** Estuvo flotando
   abajo y contando desde el primer segundo: un contador permanente mete prisa
   durante los doce minutos en los que no pasa nada. Ahora aparece cuando queda
   poco, dice cuánto y trae el botón de seguir.

   En el flujo y no fijo: al salir empuja la rejilla hacia abajo una vez, que se
   nota menos que algo que aparece flotando encima de lo que se está mirando. */
.fn-sesion-fin {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--e2) var(--e3);
  margin: 0;
  padding: var(--e3) var(--fn-margen);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--aviso) 55%, transparent);
  color: var(--aviso-alto);
}
.fn-sesion-fin[hidden] { display: none; }
.fn-sesion-fin .boton { margin-left: auto; }

/* ==========================================================================
   La tienda pública, segunda vuelta: marca, menú, respaldos y móvil
   ========================================================================== */

/* --- El nombre del vendedor, con su degradado -----------------------------
   Era el crema de la casa —el mismo para todas las tiendas, y en el tema claro
   un gris casi negro—. Ahora sale **del acento del vendedor**: en oscuro va
   hacia una versión más clara y en claro hacia una más oscura, que es lo que
   hace que se lea en los dos. Nunca desde blanco: sobre papel claro no se ve.

   `background-clip: text` con el color transparente es lo que pinta el texto
   con el degradado. Si un navegador no lo soporta, el `color` de reserva de la
   línea de antes deja el nombre en el texto alto de siempre. */
.fn-nombre-marca {
  color: var(--acento-alto);
  background-image: linear-gradient(135deg,
    var(--acento-alto) 0%, var(--marca-brillo) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* El halo del logo va con el **contrario del fondo**, no con el acento: ver
   `--halo-logo`. El nombre sí conserva el degradado del acento —ahí el color
   es la marca y no hay figura que despegar de nada—. */
.fn .marca-logo--halo {
  filter:
    drop-shadow(0 0 1px color-mix(in srgb, var(--halo-logo) 55%, transparent))
    drop-shadow(0 0 7px color-mix(in srgb, var(--halo-logo) 40%, transparent));
}

/* **El de la cabecera va más fuerte, y no es un capricho: está medido.**
   El diálogo no hace nada distinto con el halo —es esta misma regla, mismo
   color, misma opacidad y misma capa—. Lo que cambia es el sitio: allí el logo
   se pinta a 88px sobre la tarjeta, y en la cabecera a 52px sobre `--fondo`,
   que es la superficie más honda de la página. Con el halo de arriba, el anillo
   se queda en 2,14:1 sobre el fondo en el tema oscuro y en 1,74:1 en el claro
   —o sea, no hay halo: lo que salvaba al diálogo era el tamaño de la figura,
   no el resplandor—.

   Subiendo la primera capa a 3px/90% y la segunda a 8px/55%, el anillo de la
   cabecera llega a 5,09:1 en oscuro y 3,35:1 en claro, con la mediana todavía
   en 1,2 —o sea, se marca el filo y no se enciende una mancha—. Medido sobre
   el logo de la casa rasterizado a los dos tamaños; si alguien cambia estos
   números, se rehace la cuenta.

   El diálogo se queda como está: ahí la figura es 1,7 veces más grande y sobre
   una superficie más alta, y ya se lee. */
.fn .fn-marca .marca-logo--halo {
  filter:
    drop-shadow(0 0 3px color-mix(in srgb, var(--halo-logo) 90%, transparent))
    drop-shadow(0 0 8px color-mix(in srgb, var(--halo-logo) 55%, transparent));
}

/* --- El icono de los V-Bucks, sobre su propia pastilla --------------------
   El glifo se tiñe con el acento del vendedor, así que **necesita un fondo
   propio**: sobre la tarjeta clara un acento claro desaparecía, y sobre la
   oscura uno oscuro. Con la pastilla, el par glifo/fondo es siempre el mismo
   sea cual sea el tema, y el peor acento posible deja 3,91:1. */


/* --- El respaldo de la lámina --------------------------------------------
   **Sólo cuando no hay foto o cuando falla**, que fue el fallo: estaba siempre
   detrás, y los renders de Epic son PNG con el 87% de píxeles transparentes,
   así que el icono de "sin imagen" se veía a través de casi todas las
   tarjetas —sobre todo en los lotes, donde el dibujo ocupa menos—.

   Y ya no es un icono: es el nombre del objeto sobre el color de su rareza.
   El texto va en crema fija y sobre un velo oscuro, que es la misma regla que
   la tarjeta de tienda del panel: debajo puede haber cualquier degradado de
   Epic, incluido un amarillo. Medido: 18,64:1 sobre el velo, 13,58:1 con el
   amarillo `ffee00` debajo. */
/* **Lo que hay cuando no hay render, y ya no es el nombre.** Lo escribía
   dentro de la lámina, y el nombre va debajo: un item sin render lo decía dos
   veces, pegado a sí mismo, y en las tarjetas menudas del carrito y de Órdenes
   ese texto a 11px encima de la miniatura era además lo más feo de la fila.

   Ahora es un icono y nada más. Dice lo único que la lámina puede decir —que
   la foto no está—, y quién es el item lo dice el nombre de debajo, una vez.

   El velo y su opacidad **no se tocan**: lo que hay detrás sigue siendo el
   degradado de la oferta, que es de cualquier color, y el 62% es el que hace
   que lo de encima se lea sobre todos ellos. */
.fn-sinfoto {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background-color: color-mix(in srgb, #0D0B12 62%, transparent);
  color: #FFFBD5;
}
.fn-lamina--sinfoto .fn-sinfoto,
.fn-lamina--rota .fn-sinfoto { display: flex; }

/* El icono ocupa un tercio de la lámina, con un tope: en la tarjeta grande de
   la rejilla un glifo de 20px se pierde, y en la miniatura de 40 del carrito
   uno de 48 se sale. Al bajar de 20px baja también la caja, que es la regla de
   CLAUDE.md para que el glifo no quede descentrado. */
.fn-sinfoto .icono {
  width: clamp(20px, 34%, 48px);
  height: clamp(20px, 34%, 48px);
  font-size: clamp(20px, 34%, 48px);
  opacity: 0.85;
}
.fn-lamina--menuda .fn-sinfoto .icono {
  width: 20px; height: 20px; font-size: 20px;
}

/* --- El menú de filtrar y ordenar ----------------------------------------
   Donde estaba la lupa, que no hacía falta porque el buscador filtra al
   teclear. Dentro caben las dos preguntas —qué se ve y cómo se ordena— que
   antes ocupaban una fila de siete pastillas.

   Es un `<details>`, así que abre y cierra sin JavaScript. */
.fn-menu { position: relative; flex: none; }
.fn-menu > summary {
  list-style: none;
  cursor: pointer;
}
.fn-menu > summary::-webkit-details-marker { display: none; }
.fn-menu > summary::marker { content: ""; }

.fn-menu-caja {
  position: absolute;
  top: calc(100% + var(--e2));
  right: 0;
  z-index: 20;
  width: max-content;
  min-width: 220px;
  max-width: 84vw;
  padding: var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
}

.fn-menu-titulo {
  margin: 0 0 var(--e1);
  color: var(--texto-tenue);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.fn-menu-titulo:not(:first-child) { margin-top: var(--e3); }

.fn-menu-lista { display: flex; flex-direction: column; }

.fn-menu-opcion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  padding: var(--e2) var(--e2);
  border-radius: var(--r-menudo);
  color: var(--texto);
  text-decoration: none;
}
.fn-menu-opcion:hover { background-color: var(--superficie-3); }
/* La elegida, con dos canales: el fondo y el color, como los filtros de antes. */
.fn-menu-opcion--activa {
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
  font-weight: 600;
}
.fn-menu-cuenta { color: var(--texto-tenue); font-size: 0.8125rem; }
.fn-menu-opcion--activa .fn-menu-cuenta { color: inherit; }

/* --- El rótulo de la tarjeta sin saldo ------------------------------------
   Crema fija sobre velo oscuro, y no `--texto-alto`: en el tema claro ese token
   es un casi negro y el rótulo desaparecía sobre su propio velo. Es la misma
   excepción que el texto de la tarjeta de tienda del panel, y por lo mismo:
   debajo hay un color que no elegimos nosotros. */
.fn-sinsaldo { color: #FFFBD5; }

/* --- La portada de acceso («Iniciar sesión») ------------------------------
   **Una pantalla completa y no un diálogo.** El fondo lo elige el vendedor —su
   imagen o un degradado de su acento— y encima va la tarjeta con el logo, el
   título, su frase y el formulario. El marcado y el porqué, en
   `_fn-portada.html`; los colores, en `core/store_portada.py`.

   Lleva `fn-velo` porque es el mismo interruptor de siempre —`hidden`, el id
   `fn-entrar` y `mostrar('entrar')`— y aquí se le quita lo que tenía de
   diálogo: el velo gris, el centrado y el relleno.

   **Las capas, de abajo arriba**: el fondo (degradado siempre, la imagen
   encima si la hay), el velo oscuro a pantalla entera, y la tarjeta con la
   superficie del tema al 88%. Las dos últimas entran en la cuenta del color
   del texto (`ESCRIM` y `VELO`), así que **sus opacidades no se tocan aquí sin
   tocarlas allí**: el negro va con el hex a mano porque `rgba()` no acepta una
   variable. */
.fn-portada {
  --portada-c2: var(--portada-c2-oscuro);
  --portada-c3: var(--portada-c3-oscuro);
  /* **Un contenedor, para que la hoja del móvil dependa de su ancho y no del
     de la ventana.** En la tienda es lo mismo —la portada ocupa la pantalla
     entera—, pero en la previa del panel va dentro de un teléfono de 390px
     con la ventana en escritorio: con `@media`, allí se pintaba la tarjeta
     centrada de escritorio en una pantalla de móvil. */
  container: portada / inline-size;
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  padding: 0;
  /* **Transparente, y es la transición.** Mientras el fondo de la portada
     entra con su fundido, lo que se ve detrás es la tienda, y se oscurece y
     se desenfoca: se lee como pasar de la tienda a la puerta, no como una
     pantalla que tapa otra de golpe. Cuando el fondo termina de entrar, lo
     cubre todo y el desenfoque ya no se ve. */
  background-color: transparent;
  -webkit-backdrop-filter: blur(8px) brightness(0.55);
  backdrop-filter: blur(8px) brightness(0.55);
  animation: fn-portada-detras 300ms var(--curva) both;
  /* La portada entera se desplaza cuando no cabe —un móvil con el teclado
     abierto— y el fondo se queda quieto detrás. */
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* **Con el teclado abierto, la portada mide lo que se ve.** Lo pone el guion
   de `fn.html` con `visualViewport`: donde el teclado tapa la ventana en vez
   de encogerla, `inset: 0` seguía midiendo la pantalla entera, el campo se
   quedaba debajo del teclado y no había nada que desplazar. Con el alto de
   lo visible, la hoja desborda y la portada se desplaza, que es lo que ya
   sabía hacer. */
.fn-portada--teclado {
  top: var(--portada-arriba, 0);
  bottom: auto;
  height: var(--portada-alto, 100%);
}
@keyframes fn-portada-detras {
  from {
    -webkit-backdrop-filter: blur(0) brightness(1);
    backdrop-filter: blur(0) brightness(1);
  }
}
.fn[data-tema="claro"] .fn-portada {
  --portada-c2: var(--portada-c2-claro);
  --portada-c3: var(--portada-c3-claro);
}
.fn-portada-fondo {
  position: fixed;
  inset: 0;
  background-color: var(--portada-c3);
  /* La imagen encima y el degradado debajo: **si la imagen no carga, se ve
     el degradado** sin que ningún guion tenga que darse cuenta. */
  background-image:
    var(--portada-imagen, none),
    linear-gradient(135deg, var(--portada-c1), var(--portada-c2) 55%, var(--portada-c3));
  background-size: cover;
  background-position: center;
  /* **Quieto.** Entra con un fundido de 300 ms y ya: la animación es el paso
     de la tienda a la portada, no el fondo moviéndose. */
  animation: fn-portada-funde 300ms var(--curva) both;
}
/* --- Las partículas, si el vendedor las eligió ----------------------------
   **Entre el fondo y el velo**, que es lo que hace que no molesten: el velo
   oscuro les pasa por encima y la tarjeta va más arriba, así que no tapan el
   texto ni entran en la cuenta de su color (`store_portada`). En el acento de
   la tienda y muy tenues: son ambiente, no un aviso.

   **CSS puro**: ni un guion ni un nodo por partícula. Se anima lo barato
   (`background-position` y `transform`), y cada bucle avanza **exactamente un
   periodo** del dibujo, así que la vuelta no se nota.

   Son las únicas animaciones infinitas de la portada, y por eso tienen la
   excepción escrita en su guardián (`test_portada_acceso.py`). */
.fn-portada-particulas {
  --portada-mota: color-mix(in srgb, var(--acento) 75%, transparent);
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* **Polvo**: tres capas de puntos de tamaños distintos que suben despacio,
   cada una a su ritmo —eso es lo que da profundidad—. Cada capa se desplaza
   justo el alto de su baldosa en el bucle. */
.fn-portada-particulas--polvo {
  opacity: 0.55;
  background-image:
    radial-gradient(circle, var(--portada-mota) 0 1.5px, transparent 2px),
    radial-gradient(circle, var(--portada-mota) 0 1px, transparent 1.5px),
    radial-gradient(circle, var(--portada-mota) 0 2px, transparent 2.5px);
  background-size: 140px 160px, 90px 110px, 230px 210px;
  background-position: 0 0, 40px 60px, 120px 30px;
  animation: fn-portada-polvo 40s linear infinite;
}
@keyframes fn-portada-polvo {
  to { background-position: 0 -160px, 40px -50px, 120px -180px; }
}

/* **Lluvia**: líneas de un píxel, cortadas en trazos por una máscara e
   inclinadas; al caer por su propio eje, caen en diagonal. Dos capas con otro
   espaciado y otra velocidad, que es lo que evita que parezca una rejilla.

   La máscara es de canal alfa y no de color: el negro de ahí dice "se ve",
   no pinta nada, así que no es un color de la casa ni de nadie. */
.fn-portada-particulas--lluvia { opacity: 0.4; }
.fn-portada-particulas--lluvia::before,
.fn-portada-particulas--lluvia::after {
  content: "";
  position: absolute;
  inset: -50%;
  background-image: repeating-linear-gradient(
    90deg, var(--portada-mota) 0 1px, transparent 1px 47px
  );
  -webkit-mask-image: repeating-linear-gradient(
    to bottom, #000000 0 26px, transparent 26px 140px
  );
  mask-image: repeating-linear-gradient(
    to bottom, #000000 0 26px, transparent 26px 140px
  );
  animation: fn-portada-lluvia 1.6s linear infinite;
}
.fn-portada-particulas--lluvia::after {
  background-image: repeating-linear-gradient(
    90deg, var(--portada-mota) 0 1px, transparent 1px 83px
  );
  background-position: 23px 0;
  -webkit-mask-image: repeating-linear-gradient(
    to bottom, #000000 0 40px, transparent 40px 200px
  );
  mask-image: repeating-linear-gradient(
    to bottom, #000000 0 40px, transparent 40px 200px
  );
  opacity: 0.6;
  animation-name: fn-portada-lluvia-lejos;
  animation-duration: 2.6s;
}
@keyframes fn-portada-lluvia {
  from { transform: rotate(18deg) translateY(-140px); }
  to   { transform: rotate(18deg) translateY(0); }
}
@keyframes fn-portada-lluvia-lejos {
  from { transform: rotate(18deg) translateY(-200px); }
  to   { transform: rotate(18deg) translateY(0); }
}

.fn-portada-escrim {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.40);
}

@keyframes fn-portada-funde {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Las dos salidas, arriba: la ayuda a la izquierda y el aspa a la derecha. */
/* **La franja de arriba lleva su propio velo**, de oscuro a transparente: las
   dos salidas tienen que leerse sobre cualquier foto, y una foto clara arriba
   del todo —un cielo, una pared blanca— las dejaba flotando sobre nada. Baja
   hasta la mitad de la franja al 55% y se funde antes de llegar a la tarjeta.

   **Negro escrito a mano**, como el del velo de pantalla entera: es un velo
   sobre una imagen que no elegimos, no un color del tema. */
.fn-portada-barra {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  padding: var(--e4) var(--e4) var(--e8);
  background-image: linear-gradient(
    to bottom, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0)
  );
}
/* Sobre una foto que no conocemos, las dos van en píldora sobre la
   superficie del tema: el acento del vendedor pinta el texto, como todo lo que
   se pulsa en esta página, y se lee contra un fondo que sí se conoce. */
.fn-portada-barra .fn-ayuda-abre,
.fn-portada-cerrar {
  background-color: var(--superficie);
  border: 1px solid var(--borde-alto);
}
.fn-portada-cerrar { border-radius: var(--r-control); }
.fn-ayuda-abre {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e1) var(--e3);
  border-radius: var(--r-pastilla);
  color: var(--acento);
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
}
.fn-ayuda-abre:hover { color: var(--acento-alto); }
.fn-ayuda-abre .icono { font-size: 18px; width: 18px; height: 18px; }

/* Centrado **con márgenes automáticos y no con `justify-content`**: cuando
   el contenido no cabe —la ayuda larga, un móvil con el teclado—, un
   `center` o un `flex-end` lo empujan por arriba, fuera de donde se puede
   desplazar. Los márgenes automáticos se anulan solos al desbordar. */
.fn-portada-centro {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e4);
  /* Arriba, la mitad del sello que se sale de la tarjeta. */
  padding: 44px var(--e4) var(--e8);
}


/* **El sello del vendedor**: el logo en un recuadro claro, medio metido en el
   borde de arriba de la tarjeta —la mitad fuera, la mitad dentro—. Suelto, el
   logo se leía como un icono; en el sello se lee como la marca de la tienda en
   la que estás dando tu PIN.

   **Claro en los dos temas, y por eso un color propio**: un sello no cambia
   con el tema de la página, igual que no cambia el papel de un sobre. Blanco y
   no el crema de la casa: la tienda no pinta con colores de la casa, y hay un
   guardián que lo vigila.

   **Sin sombra** (CLAUDE.md, Profundidad): lo despega el contraste entre el
   blanco y la tarjeta, y un borde fino que existe también sobre el tema claro,
   donde la tarjeta es casi del mismo blanco. */
.fn-portada-sello {
  --portada-sello: #FFFFFF;
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 88px;
  height: 88px;
  padding: var(--e2);
  border: 1px solid color-mix(in srgb, #000000 14%, var(--portada-sello));
  border-radius: var(--r-tarjeta);
  background-color: var(--portada-sello);
}
/* 72 dentro de un sello de 88: el logo no toca el borde. Los 144 del `<img>`
   son para que se vea nítido al doble de densidad; lo que mide es esto. */
.fn-entrar-logo {
  width: 72px;
  height: 72px;
  border-radius: var(--r-menudo);
}

.fn-portada-tarjeta {
  --portada-texto: var(--portada-texto-oscuro);
  position: relative;
  width: 100%;
  max-width: 420px;
  /* Los dos huecos, arriba y abajo: ver el centrado de `.fn-portada-centro`. */
  margin: auto 0;
  /* Arriba, la mitad del sello que queda dentro y el aire de siempre. */
  padding: calc(44px + var(--e3)) var(--e6) var(--e6);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  background-color: color-mix(in srgb, var(--superficie) 88%, transparent);
  /* **El texto de la tarjeta es el que calcula el servidor**, y va a los
     cuatro escalones: sobre un velo compuesto con una foto que no elegimos,
     la jerarquía la hace el tamaño y no el color —la misma regla que las
     tarjetas de oferta—. Los campos son opacos y se leen igual. */
  --texto-alto: var(--portada-texto);
  --texto: var(--portada-texto);
  --texto-suave: var(--portada-texto);
  --texto-tenue: var(--portada-texto);
  color: var(--portada-texto);
  /* Sube 20px hasta su sitio mientras entra el fondo: los dos en los mismos
     300 ms, que es lo que hace que se lea como un solo gesto. */
  animation: fn-portada-sube 300ms var(--curva) both;
}
.fn[data-tema="claro"] .fn-portada-tarjeta {
  --portada-texto: var(--portada-texto-claro);
}
@keyframes fn-portada-sube {
  from { transform: translateY(20px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

.fn-portada-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
.fn-portada-cuerpo[hidden] { display: none; }

/* La página de estado de un pago con cripto, sobre el mismo marco. Cancelar es
   un enlace y no un botón más: es la salida que menos se quiere, y un tercer
   botón del mismo peso competiría con «Ir a pagar». */
.fn-cripto-cancelar {
  align-self: center;
  padding: var(--e2);
  color: var(--acento);
  font-size: 0.875rem;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.fn-cripto-cancelar:hover { color: var(--acento-alto); }
.fn-cripto-cancelar:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.fn-cripto-confirmar {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
.fn-cripto-confirmar form { display: flex; flex-direction: column; }
.fn-portada-titulo {
  margin: 0;
  font-size: 1.375rem;
  text-align: center;
}
.fn-portada-frase {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
  overflow-wrap: anywhere;
}
.fn-portada-pie {
  margin: 0 auto var(--e1);
  max-width: 34ch;
  font-size: 0.8125rem;
  text-align: center;
}
/* El aviso de error **opaco**: su rojo está medido contra la superficie del
   tema, no contra un velo con una foto detrás. */
.fn-portada-aviso { background-color: var(--superficie); }
.fn-portada-ingresar { width: 100%; justify-content: center; }

/* **En móvil, una hoja desde abajo.** La tarjeta se pega al borde de abajo,
   a todo lo ancho y con las esquinas de arriba redondas, y el logo se queda
   sobre el fondo. Con el teclado abierto la portada entera se desplaza —ver
   `overflow-y` arriba— y el guion lleva «Ingresar» a la vista. */
@container portada (max-width: 560px) {
  .fn-portada-centro { padding: 44px 0 0; }
  /* La hoja se pega abajo: el hueco lo toma el margen de encima del logo. */
  .fn-portada-tarjeta { margin-bottom: 0; }
  .fn-portada-tarjeta {
    max-width: none;
    padding: calc(44px + var(--e3)) var(--e4) var(--e6);
    border-width: 1px 0 0;
    border-radius: var(--r-tarjeta) var(--r-tarjeta) 0 0;
  }
}

/* Sin animación: **todo sale puesto**. */
@media (prefers-reduced-motion: reduce) {
  .fn-portada,
  .fn-portada-fondo,
  .fn-portada-tarjeta { animation: none; }
  /* Las partículas no se paran: **se van**. Quietas serían una trama de
     puntos o de rayas encima de la foto, que no es lo que se eligió. */
  .fn-portada-particulas { display: none; }
}

/* La vista previa del panel: la misma portada dentro de la pantalla del
   teléfono y no a pantalla completa. `fixed` pasa a `absolute` para que el
   fondo, las partículas y el velo se queden dentro. */
.fn-portada--previa {
  position: absolute;
  inset: 0;
  overflow: hidden;
  isolation: isolate;
}
.fn-portada--previa .fn-portada-fondo,
.fn-portada--previa .fn-portada-particulas,
.fn-portada--previa .fn-portada-escrim { position: absolute; }
/* --- El teléfono de la previa ---------------------------------------------
   La portada de verdad, pintada a **390x780** —un móvil— y encogida a 0,6
   para caber en la columna del panel. Tres cajas y cada una con su trabajo:

     - `.previa-telefono` es el marco: el canto del teléfono, en la superficie
       alta del panel y con su borde. Sin sombra (CLAUDE.md, Profundidad).
     - `.previa-telefono-caja` es el hueco de la pantalla **ya encogida**: mide
       lo que ocupa después de la escala, porque `transform` no cambia lo que
       ocupa una caja en la maquetación y sin ella quedaría un hueco de 780px.
     - `.previa-telefono-pantalla` es la pantalla a tamaño real, con la escala.

   Es un contenedor a 390px, así que la portada se pinta con la hoja del móvil
   aunque el panel esté en escritorio (ver `container` en `.fn-portada`). */
.previa-telefono {
  --previa-escala: 0.6;
  --previa-ancho: 390px;
  --previa-alto: 780px;
  align-self: center;
  padding: var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: 36px;
  background-color: var(--superficie-3);
}
.previa-telefono-caja {
  position: relative;
  width: calc(var(--previa-ancho) * var(--previa-escala));
  height: calc(var(--previa-alto) * var(--previa-escala));
  overflow: hidden;
  border-radius: 26px;
}
/* Con `.fn` delante porque `.fn` trae su `min-height: 100vh` de página, y a
   su mismo peso ganaría: la pantalla mediría la ventana entera. */
.fn.previa-telefono-pantalla {
  width: var(--previa-ancho);
  height: var(--previa-alto);
  min-height: 0;
  transform: scale(var(--previa-escala));
  transform-origin: 0 0;
}
/* Centrado en su columna, y también cuando la previa se va debajo. */
.previa-telefono-sitio { align-items: center; }

/* **El cuerpo de la ayuda tiene su propio scroll, y hace falta.** Son hasta
   dos mil caracteres del vendedor: sin tope, el diálogo crece hasta salirse de
   la pantalla y en el móvil se lleva por delante el aspa de cerrar.

   `60vh` deja ver el título y el aspa a la vez que el texto; en una pantalla
   corta, el diálogo entero sigue cabiendo. */
.fn-ayuda {
  display: flex;
  flex-direction: column;
  gap: var(--e4);
  max-height: 60vh;
  overflow-y: auto;
  /* Que la barra no se coma la última letra de cada línea. */
  padding-right: var(--e1);
}

/* El texto, con el ritmo de un párrafo y no el de una etiqueta: esto se lee
   entero, no se ojea. */
.fn-ayuda-texto {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--texto);
  /* Una URL pegada o una palabra larguísima no pueden ensanchar el diálogo. */
  overflow-wrap: anywhere;
}
.fn-ayuda-texto p { margin: 0; }
/* La negrita se lee en el texto alto: es lo que el vendedor quiso destacar. */
.fn-ayuda-texto b { color: var(--texto-alto); font-weight: 600; }

/* Los botones del vendedor, al final y en columna: sus etiquetas las escribe
   él y no caben dos a lo ancho en un móvil. Van con su acento, como el resto
   de lo que se pulsa. */
.fn-ayuda-botones {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.fn-ayuda-boton {
  justify-content: center;
  /* La etiqueta la escribe el vendedor: si se pasa de largo, que parta en vez
     de ensanchar el diálogo. */
  white-space: normal;
  overflow-wrap: anywhere;
}
/* El «abre en otra pestaña» va detrás del texto y un punto más pequeño: es un
   aviso de qué va a pasar, no parte de la etiqueta. */
.fn-ayuda-boton .icono {
  font-size: 18px;
  width: 18px;
  height: 18px;
  opacity: 0.8;
}

/* La vista previa del panel: **mide lo que mide su ayuda**, con un techo y su
   propia barra si se pasa, igual que en la tienda.

   `min-height: 0` no es de adorno y es **el arreglo**: este marco lleva además
   la clase `.fn`, que trae `min-height: 100vh` porque allí es la página
   entera. `.previa-marco` ya pone `min-height: 0`, pero las dos reglas pesan lo
   mismo y `.fn` se escribe después en la hoja, así que ganaba: la previa medía
   **900px con 164 de ayuda dentro** —medido—, o sea tres cuartos de caja vacía
   debajo de dos líneas de texto. Es lo mismo que le pasaba a la previa de la
   tienda, que lo resuelve en `.previa-tienda` con esta misma línea.

   Y el techo lo pone `.fn-ayuda` con su `max-height: 60vh` y su
   `overflow-y: auto`, que ya los tiene: **antes se le quitaban aquí** con un
   `max-height: none`, y por eso una ayuda de dos mil caracteres estiraba la
   pantalla del panel hasta donde hiciera falta. Se le dejan puestos, que es lo
   que hace que la previa se parezca a lo que el cliente va a ver. */
.previa-ayuda {
  min-height: 0;
  padding: var(--e4);
}

.fn-dialogo { position: relative; }

/* --- Redes y contacto -----------------------------------------------------
   La pestaña «Redes» de `/fn/`, el diálogo de encima de la portada y la vista
   previa del panel: el mismo parcial (`_fn-redes.html`) y estas reglas.

   **Una columna de tarjetas, estrecha y centrada**: son de dos a ocho canales
   y lo que se hace es elegir uno, no ojear una rejilla.

   **El color de la marca es una excepción acotada** (CLAUDE.md, «El color de
   las marcas»): sólo el fondo de `.fn-red-icono`, con el glifo encima en el
   blanco o el negro que calcula el servidor. Todo lo demás —el botón, el
   borde, el foco— va con el acento del vendedor, como el resto de la tienda. */
.fn-redes {
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--e4);
}
.fn-redes-alto { display: flex; flex-direction: column; gap: var(--e1); }
.fn-redes-titulo { margin: 0; font-size: 1.375rem; }
.fn-redes-sub { margin: 0; color: var(--texto-suave); }
.fn-redes-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.fn-red {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e3) var(--e4);
  background-color: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  /* La entrada: una tras otra, 60 ms entre cada una. Con ocho, la última
     empieza a los 420 ms y acaba antes de los 600. */
  animation: fn-red-entra var(--t-media) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes fn-red-entra {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.fn-red-icono {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-control);
  background-color: var(--red-color);
  color: var(--red-glifo);
}
.fn-red-icono .icono { width: 24px; height: 24px; font-size: 24px; }
.fn-red-texto {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.fn-red-nombre { font-weight: 600; color: var(--texto-alto); }
/* El número o el usuario: lo escribe el vendedor y puede ser largo. */
.fn-red-dato {
  color: var(--texto);
  overflow-wrap: anywhere;
}
.fn-red-nota {
  font-size: 0.8125rem;
  color: var(--texto-suave);
  overflow-wrap: anywhere;
}
.fn-red-abrir { flex: none; }
.fn-red-abrir .icono { width: 18px; height: 18px; font-size: 18px; }
/* En estrecho, el botón baja debajo del texto en vez de apretarlo. */
@media (max-width: 420px) {
  .fn-red { flex-wrap: wrap; }
  .fn-red-abrir { width: 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .fn-red { animation: none; }
}
/* En la previa del panel y en el diálogo, sin el aire de la página. */
.previa-redes { min-height: 0; padding: var(--e4); }
.fn-redes--previa .fn-red { animation: none; }

/* «¿Dudas? Contáctanos», al pie de la portada de entrar. */
.fn-portada-contacto {
  margin: var(--e3) 0 0;
  font-size: 0.875rem;
  text-align: center;
  color: var(--texto-suave);
}

/* --- La cuenta del cliente ------------------------------------------------
   **Dos tarjetas, una debajo de otra**: «Tu cuenta», con cuatro filas
   compactas, y «Tu perfil en Top compradores». Eran cuatro tarjetas de un dato
   cada una y se leían como un mosaico de rótulos.

   Una columna y no dos: en una fila caben etiqueta, valor y botón, y partida
   en dos columnas de 340 un usuario largo empujaba el botón fuera. El ancho es
   el de un formulario corto, centrado. */
.fn-cuenta-fichas {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e4);
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
}

/* La tarjeta de la Suerte sigue apilando dato, nota y pie en el centro; ésta
   es la forma común y las dos de Cuenta la cambian abajo. */
.fn-cuenta-tarjeta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e2);
  padding: var(--e6) var(--e5);
  text-align: center;
}

.fn-cuenta-rotulo {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* --- «Tu cuenta»: cuatro filas ---------------------------------------------
   Cada fila es una rejilla de tres: etiqueta, valor y botón. El valor se
   queda con el sitio que sobre y **parte por donde sea** (`anywhere`): un
   usuario puede ser un correo de 40 sin un solo espacio, y lo que no puede
   pasar es que empuje el botón fuera de la tarjeta. */
.fn-cuenta-tarjeta--filas {
  align-items: stretch;
  padding: var(--e2) var(--e5);
  text-align: left;
}
.fn-cuenta-filas { margin: 0; display: flex; flex-direction: column; }
.fn-cuenta-fila {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--e1) var(--e3);
  padding: var(--e3) 0;
  border-top: 1px solid var(--borde);
}
.fn-cuenta-fila:first-child { border-top: 0; }
.fn-cuenta-fila dd { margin: 0; }
.fn-cuenta-etiqueta {
  color: var(--texto-suave);
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.fn-cuenta-valor {
  min-width: 0;
  color: var(--texto-alto);
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* Lo que falta para poder cambiar el usuario, debajo del valor y no de la
   etiqueta: es de ese dato. */
.fn-cuenta-fila > .fn-cuenta-nota { grid-column: 2 / -1; margin: 0; }
.fn-cuenta-saldo { display: inline-flex; align-items: center; gap: var(--e2); }
.fn-cuenta-saldo .vbuck { width: 20px; height: 20px; }

/* El valor de «Vence»: los días y el icono que abre la fecha y el porqué. */
.fn-cuenta-vence { display: inline-flex; align-items: center; gap: var(--e2); }

/* **Los últimos quince días**, en ámbar: es lo que significa «al límite» en
   esta casa —el `--aviso` del medidor de cuentas—, y no el oro de la moneda
   ni el ámbar de energía. El tema claro lo rehace en `.fn[data-tema="claro"]`
   para la tarjeta blanca (8,50 / 9,35:1 la cifra, medido).

   **El color no es el único canal**: el número ya dice cuánto queda y aparece
   el botón de ir a la tienda. */
.fn-cuenta-fila--vence .fn-cuenta-vence { color: var(--aviso-alto); }

/* El botón de cada fila, a su tamaño y no a la fila entera: aquí comparte
   sitio con el dato. En móvil cabe igual, porque la rejilla pasa a dos. */
.fn-cuenta-accion { justify-self: end; }
.fn-cuenta-accion:empty { display: none; }
.fn-cuenta-accion .boton,
.fn-cuenta-accion .fn-canjear { white-space: nowrap; }

@media (max-width: 420px) {
  /* En el móvil la etiqueta sube encima y el valor y el botón comparten la
     fila de abajo: tres columnas en 358 px dejaban el correo en cuatro líneas. */
  .fn-cuenta-fila { grid-template-columns: minmax(0, 1fr) auto; }
  .fn-cuenta-etiqueta { grid-column: 1 / -1; }
  .fn-cuenta-fila > .fn-cuenta-nota { grid-column: 1 / -1; }
}

/* --- El globo de información ----------------------------------------------
   Se abre **al pasar el ratón, al enfocar el botón o al tocarlo**: las dos
   primeras son de la hoja y funcionan sin guion; la tercera la pone
   `fn_cuenta.js` (`.fn-info--abierta`), porque en iOS tocar un botón no lo
   enfoca. Se esconde con `display` y no con `hidden`: lo abren tres cosas y
   ninguna es el marcado. */
.fn-info { position: relative; display: inline-flex; }
.fn-info-boton {
  display: inline-flex;
  padding: 2px;
  border: 0;
  border-radius: var(--r-pastilla);
  background: none;
  color: var(--texto-suave);
  cursor: help;
}
.fn-info-boton:hover { color: var(--texto-alto); }
.fn-info-globo {
  display: none;
  position: absolute;
  top: calc(100% + var(--e2));
  left: 0;
  z-index: 5;
  width: max-content;
  max-width: min(18rem, calc(100vw - 8rem));
  padding: var(--e2) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  color: var(--texto);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.4;
}
.fn-info:hover .fn-info-globo,
.fn-info:focus-within .fn-info-globo,
.fn-info--abierta .fn-info-globo { display: block; }

.fn-cuenta-nota {
  margin: 0;
  flex: 1;
  max-width: 28ch;
}

/* El pie de la tarjeta de la Suerte: su botón abajo del todo. */
.fn-cuenta-pie {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: var(--e2);
}
.fn-cuenta-pie:empty { display: none; }

/* --- La cabecera en móvil ------------------------------------------------
   Dos líneas y en este orden: arriba **quién es la tienda y qué se hace**
   —logo, nombre y entrar; o logo, nombre, saldo y salir con sesión— y debajo lo
   accesorio: el tema. Con todo en una fila, el nombre se partía en tres
   renglones y el botón de entrar caía debajo del logo. */
@media (max-width: 560px) {
  .fn-alto {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--e2) var(--e3);
    align-items: center;
  }
  .fn-marca-grupo { display: contents; }
  .fn-marca { grid-column: 1; grid-row: 1; min-width: 0; }
  .fn-marca .marca-logo { width: 40px; height: 40px; }
  .fn-entrar, .fn-sesion { grid-column: 2; grid-row: 1; }
  /* El tema, a la segunda línea: es de quien mira, no de la tienda. */
  .fn-tema { grid-column: 1 / -1; grid-row: 2; justify-self: start; }
  .fn-sesion { gap: var(--e2); }
  .fn-saldo { padding: var(--e1) var(--e2); }
}

/* --- El nombre de la tienda: se enseña si cabe, y si no se va -------------
   **La decisión es por espacio, no por ancho de ventana.** Hubo una regla de
   «por debajo de 360px» y fallaba en los dos bordes: a 360 —un Galaxy S25— y a
   712 —una tablet— «LIMX STORE» partía en dos renglones y el segundo se
   montaba sobre el balance. Un corte fijo no sabe cuánto mide el nombre ni
   cuánto se llevan el balance y los botones, y las dos cosas cambian de una
   tienda a otra y de una sesión a otra.

   **El truco es que el nombre no parte nunca: salta.** La marca es una caja
   flexible que envuelve, con el alto justo de UNA línea —el del logo— y lo
   que sobra recortado. El nombre va en `nowrap`, así que si cabe junto al
   logo se queda en la línea de arriba, entero; y si no cabe, baja **entero** a
   la segunda línea, que cae fuera del recorte. Sin guion, sin medir y sin
   `resize`: el navegador lo decide en cada reparto, con la fuente que haya
   cargado y con lo que ocupe de verdad lo de al lado.

   **Recortado es `sr-solo`, no `display: none`**: `overflow: hidden` quita el
   nombre de la vista y lo deja en el árbol de accesibilidad, que es lo que
   hace falta porque el logo va con `alt=""` y sin el nombre la cabecera se
   quedaría sin nombre accesible ninguno.

   **El halo del logo necesita sitio**, y por eso la caja se ensancha
   `--fn-halo` por los cuatro lados con relleno y lo devuelve con margen
   negativo: sin eso, el mismo recorte que esconde el nombre se comería el
   resplandor. La segunda línea arranca un `row-gap` más abajo, que es mayor
   que ese relleno, así que no asoma ni un píxel.

   El alto sale de `--fn-logo`, que es el lado del logo: 52 en escritorio y 40
   en móvil, justo debajo — **después** de la regla base y no en el bloque de
   móvil de más arriba, que la base pisaría. Lo vigilan
   `tests/test_fn_estrecho.py` y `tests/test_cabecera_sesion.py`, en un
   navegador y a los anchos donde se rompía. */
.fn-marca--recorta {
  --fn-logo: 52px;
  --fn-halo: var(--e3);
  flex-wrap: wrap;
  row-gap: var(--e4);
  min-width: 0;
  height: calc(var(--fn-logo) + 2 * var(--fn-halo));
  padding: var(--fn-halo);
  margin: calc(-1 * var(--fn-halo));
  overflow: hidden;
}
.fn-marca--recorta .marca-logo {
  flex: none;
  width: var(--fn-logo);
  height: var(--fn-logo);
}
.fn-marca--recorta .fn-nombre-marca { white-space: nowrap; }
@media (max-width: 560px) {
  .fn-marca--recorta { --fn-logo: 40px; }
}

/* **El grupo de la izquierda también envuelve.** En tablet, marca y fila de la
   sesión van juntas; si no caben las dos, la fila baja debajo de la marca en
   vez de apretarla hasta partir el nombre. `min-width: 0` es lo que deja que
   la marca, sola en su línea, ceda — y lo que cede es el nombre, entero. */
.fn-marca-grupo:has(> .fn-marca--recorta) {
  flex-wrap: wrap;
  min-width: 0;
}

/* ==========================================================================
   La tienda pública, tercera vuelta: menú, secciones y pestañas de verdad
   ========================================================================== */

/* --- El menú de la esquina -----------------------------------------------
   Donde estaba el botón de salir. Se despliega **anclado debajo del botón** y
   no en un diálogo centrado: es un menú —dos cosas y fuera—, no una decisión
   que merezca tapar la página.

   Reusa `.fn-menu` del de filtrar, que ya trae el `<details>` sin marcador y la
   caja flotante; lo que cambia aquí son las opciones, que son controles y no
   enlaces. */
.fn-menu--suyo .fn-menu-caja { min-width: 200px; }

/* Una opción del menú vale para un `<button>`, para un `<a>` y para el que va
   dentro de un `<form>`: los tres se pintan igual porque los tres hacen lo
   mismo de cara a quien mira. */
.fn-menu-caja .fn-menu-opcion {
  width: 100%;
  justify-content: flex-start;
  gap: var(--e2);
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fn-menu-caja form { margin: 0; }

/* El `display: flex` de arriba le gana al `[hidden] { display: none }` del
   navegador, así que una opción que se pinte escondida —el interruptor del
   tema, que enciende el guion— saldría igual (CLAUDE.md, "Lo que se esconde
   con `hidden`"). La regla va por la clase que trae el `display`, no por la del
   elemento concreto: la siguiente opción que nazca escondida ya la tiene. */
.fn-menu-opcion[hidden] { display: none; }

.fn[data-tema="oscuro"] .fn-tema .icono--luna,
.fn[data-tema="claro"] .fn-tema .icono--sol { display: none; }

/* --- La fila del reloj y el canje ----------------------------------------
   Las dos píldoras de la sesión, **en la misma línea siempre**.

   Existe por un fallo de móvil: por debajo de 560px la cabecera es una rejilla
   y `.fn-marca-grupo` va con `display: contents`, así que sus hijos son
   casillas. El reloj tenía la suya escrita y el botón de canjear no, de modo
   que se auto-colocaba en una tercera línea y encima estirado al ancho de la
   columna —el «botón grande»—. No era falta de sitio: a 390px el reloj mide
   174px y el botón 149, y la fila da 366.

   El reparto de dentro es el de la regla: **la sesión mide lo que mide y el
   botón se queda con lo que sobre**, no al revés. Lo que hay que poder leer
   entero es el botón; el reloj ya cede por otro lado (ver `.fn-reloj-activa`).

   El gap es `--e3` como el del grupo de la marca, para que en escritorio nada
   se mueva de sitio; en móvil baja a `--e2`, igual que el de `.fn-sesion`. */
.fn-fila-sesion {
  display: flex;
  /* **Estirar y no centrar**: así las dos píldoras salen del mismo alto —el
     del botón, que es el de la píldora de sin sesión— y la fila se lee como
     una pareja. Centradas, el reloj se quedaba 4px más bajo y el oro parecía
     un escalón más grande que su vecino. */
  align-items: stretch;
  gap: var(--e3);
  min-width: 0;
}

/* --- El contador de la sesión --------------------------------------------
   Ocupa el sitio que dejó la luna, al lado de la marca, y **está siempre que
   haya sesión**. Sustituye a la franja que salía de golpe en los últimos
   minutos: decía lo mismo y empujaba la página al aparecer.

   Lo único que cambia al final es el color, que es el canal que informa sin
   moverse. */
.fn-reloj {
  display: inline-flex;
  /* No se encoge: su ancho es su contenido, y cuando no cabe lo que se recorta
     es el rótulo de dentro (ver `.fn-reloj-activa`), no la caja. */
  flex: none;
  align-items: center;
  gap: var(--e2);
  padding: var(--e1) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-pastilla);
  color: var(--texto-suave);
  font-size: 0.8125rem;
  transition: color var(--t-media), border-color var(--t-media);
}
.fn-reloj .icono { font-size: 16px; width: 16px; height: 16px; }
.fn-reloj .cifras { color: var(--texto-alto); }

/* Los últimos minutos. Ámbar y no rojo: no ha pasado nada malo, sólo queda
   poco, y el rojo de esta paleta significa que algo ha fallado. */
.fn-reloj--poco {
  border-color: color-mix(in srgb, var(--aviso) 55%, transparent);
  color: var(--aviso-alto);
}
.fn-reloj--poco .cifras { color: var(--aviso-alto); }

/* Los dos escalones del rótulo, y **los dos anchos salen de una cuenta**: la
   fila mide el ancho de pantalla menos los dos márgenes de `--e3`, y dentro
   tienen que caber el reloj, el gap de `--e2` y los 149px del botón.

     «Sesión activa 15:00» → 174 + 8 + 149 + 24 = 355px de pantalla
     «Sesión 15:00»        → 136 + 8 + 149 + 24 = 317px
     «15:00»               →  90 + 8 + 149 + 24 = 271px

   Los cortes van 20px por encima de cada número, que es el colchón para el día
   que la fuente no cargue y el navegador mida con la de sistema. 375 y 320 son
   además anchos de teléfono de verdad. Lo comprueba
   `tests/test_cabecera_sesion.py` en un navegador y a tres anchos. */
@media (max-width: 375px) {
  .fn-reloj-activa { display: none; }
}
@media (max-width: 320px) {
  /* La palabra **no desaparece**: baja a `sr-solo` —las mismas declaraciones
     de esa clase, que aquí no se puede aplicar desde el marcado—. El icono va
     `aria-hidden`, así que sin ella quien no ve la pantalla oiría un número
     suelto. */
  .fn-reloj-texto {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/* --- Las pestañas, ahora botones -----------------------------------------
   Eran rótulos con una rayita debajo y no se leían como algo que se pulsa.
   Ahora son cajas redondeadas con fondo: la que se está mirando lleva el acento
   del vendedor y las demás el neutro de un control. */
.fn-pestanas {
  gap: var(--e2);
  padding: var(--e3) var(--fn-margen);
  border-bottom: 1px solid var(--borde);
}

.fn-pestana {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e4);
  background-color: var(--superficie-2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9375rem;
  transition: background-color var(--t-rapida), color var(--t-rapida),
              border-color var(--t-rapida);
}
.fn-pestana:hover {
  background-color: var(--superficie-3);
  border-color: var(--borde-alto);
  color: var(--texto-alto);
}

/* La activa: el relleno del acento y el texto que se calcula encima
   (`Estilo.acento_texto`), que es lo mismo que hace el botón. */
.fn-pestana--activa,
.fn-pestana--activa:hover {
  background-color: var(--acento);
  border-color: var(--acento);
  color: var(--acento-texto);
}

/* La cerrada no se pulsa —o lleva al diálogo de entrar— y se pinta apagada,
   con el candado además del color. */
/* En `--texto-suave` y no en `--texto-tenue`: va sin caja, directa sobre el
   fondo de la tienda, y la capa de encima está calculada para `--texto-suave`
   (ver «El fondo de la tienda»). Lo que la apaga es el candado y la falta de
   caja; el color ya no hace falta para eso. */
.fn-pestana--cerrada {
  background-color: transparent;
  color: var(--texto-suave);
}
.fn-pestana--cerrada .icono { font-size: 16px; width: 16px; height: 16px; }
span.fn-pestana--cerrada { cursor: default; }

/* --- Las secciones del cuerpo -------------------------------------------- */
.fn-panel {
  display: flex;
  flex-direction: column;
  gap: var(--e4);
}
.fn-panel-titulo {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  font-size: 1.25rem;
}

/* --- El Top compradores (`_fn-top.html`) ---------------------------------
   Una lista corta de tarjetas como Redes: puesto y nombre, nada más. Lo que va
   sin caja —el «Vas en el puesto…» y el vacío— en `--texto-suave` (ver «El
   fondo de la tienda»). Las dos pestañas del periodo son las mismas que las
   de la tienda, en pequeño. */
.fn-top {
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
}
.fn-top-periodos { display: flex; flex-wrap: wrap; gap: var(--e2); }
.fn-top-periodo {
  display: inline-flex;
  align-items: center;
  padding: var(--e1) var(--e3);
  background-color: var(--superficie-2);
  border: 1px solid var(--fn-borde-control, var(--borde-alto));
  border-radius: var(--r-pastilla);
  color: var(--texto-suave);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.875rem;
  transition: background-color var(--t-rapida), color var(--t-rapida);
}
.fn-top-periodo:hover { background-color: var(--superficie-3); color: var(--texto-alto); }
.fn-top-periodo--activo,
.fn-top-periodo--activo:hover {
  background-color: var(--acento);
  border-color: var(--acento);
  color: var(--acento-texto);
}
.fn-top-mio { margin: 0; color: var(--texto-suave); }
.fn-top-mio .cifras { color: var(--texto-alto); }
.fn-top-mio a { color: var(--acento-fondo, var(--acento)); }
.fn-top-vacio { margin: 0; color: var(--texto-suave); }
.fn-top-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
}
.fn-top-fila {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e3) var(--e4);
  background-color: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
}
.fn-top-numero,
.fn-top-medalla {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-pastilla);
}
.fn-top-numero {
  background-color: var(--superficie-2);
  color: var(--texto-alto);
  font-weight: 600;
}
.fn-top-medalla { color: var(--medalla-tinta); }
.fn-top-medalla--1 { background-color: var(--medalla-oro); }
.fn-top-medalla--2 { background-color: var(--medalla-plata); }
.fn-top-medalla--3 { background-color: var(--medalla-bronce); }
.fn-top-nombre {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--texto-alto);
  font-weight: 600;
}
/* «Tu perfil en Top compradores»: un formulario a lo ancho, alineado a la
   izquierda como cualquier formulario de la tienda. */
.fn-cuenta-top { align-items: stretch; text-align: left; }
.fn-top-form { display: flex; flex-direction: column; gap: var(--e3); }
.fn-top-form .campo { width: 100%; }
.fn-top-form .fn-cuenta-nota { max-width: none; }
.fn-top-form > .boton { align-self: flex-start; }

/* La vista previa es **la misma fila del ranking** (`.fn-top-fila`), para que
   lo que se ve aquí sea lo que se verá allí. */
.fn-top-previa { display: flex; flex-direction: column; gap: var(--e2); }
.fn-top-fila--previa { margin: 0; }

/* La bandera del país (`core/paises.py`, SVG de flag-icons servido desde aquí).
   Con un filo del borde del tema: hay banderas con blanco en el borde y sobre
   la tarjeta blanca del tema claro no se sabría dónde acaban. */
.fn-bandera {
  flex: none;
  width: 24px;
  height: 18px;
  border-radius: 3px;
  object-fit: cover;
  outline: 1px solid var(--borde);
  outline-offset: -1px;
}
.fn-bandera[hidden] { display: none; }

/* --- El libro de movimientos: tarjetas, no filas -------------------------
   **La misma tarjeta que la tienda.** Eran filas de extracto —icono, importe,
   fecha— y una tienda no enseña un extracto: enseña lo que ha pasado con las
   mismas piezas con las que enseña lo que se puede comprar. Así que un
   movimiento es un `.fn-tarjeta` como cualquier artículo: lámina cuadrada
   arriba, nombre y pie debajo. Lo único que cambia es lo que va dentro de la
   lámina.

   La rejilla arranca un poco más ancha que la de la tienda (200 en vez de
   180): aquí debajo del nombre van el importe, a veces el motivo y siempre el
   pie con la fecha y el estado, y a 180 el motivo se partía en cuatro
   renglones. */
.fn-rejilla--ordenes {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

/* No es un enlace y no lleva a ningún sitio, así que **no sube al pasar por
   encima**: ese gesto es el de "esto se puede pulsar" y aquí mentiría. */
.fn-orden:hover { transform: none; border-color: var(--borde); }

/* La lámina de lo que no tiene render: la moneda en grande sobre el tinte de
   su tono, que es de lo que va el movimiento. El día que haya compras, la
   ocupará la imagen del objeto y esta regla no se toca —la de dentro es la
   moneda, no la lámina—. */
.fn-lamina--orden {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--superficie-2);
}
.fn-orden-moneda {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.fn-orden-moneda .vbuck {
  width: 46%;
  aspect-ratio: 1;
  opacity: 0.9;
}
/* El tono tiñe la lámina y el importe, y son los mismos dos de siempre: lo que
   entra en verde y lo que sale en rojo. El tinte es flojo —14%— porque debajo
   va texto y la lámina no es un estado, es un fondo. */
.fn-orden--entra .fn-lamina--orden {
  background-color: color-mix(in srgb, var(--ok) 14%, var(--superficie-2));
  color: var(--ok-alto);
}
.fn-orden--sale .fn-lamina--orden {
  background-color: color-mix(in srgb, var(--peligro) 14%, var(--superficie-2));
  color: var(--peligro-alto);
}

/* La cabecera de una devolución: la flecha de volver en grande, centrada y
   sobre el mismo verde de lo que entra.

   Antes era la moneda de los V-Bucks sobre `--superficie-2`, y ese gris con un
   dibujo pequeño dentro se leía como una imagen que no ha cargado. Lo que esta
   tarjeta cuenta es que algo **volvió**, así que lo que dibuja es eso, con el
   token de lo que entra —el mismo que tiñe su importe— para que la cabecera y
   el «+» digan lo mismo.

   El tinte es el de `--ok`, la tinta `--ok-alto` —la variante que se lee
   (CLAUDE.md, Paleta)— y **va al 20% y no al 14% de `.fn-orden--entra`**: aquel
   es flojo a propósito porque **debajo lleva texto**, y aquí lo único que hay
   encima es el dibujo, así que el fondo puede decir de qué va sin pelearse con
   nada. */
.fn-lamina--devolucion {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: color-mix(in srgb, var(--ok) 20%, var(--superficie-2));
  color: var(--ok-alto);
}
.fn-devolucion-marca {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
/* En grande: la lámina es cuadrada y esto es lo único que lleva dentro, así
   que un icono de 20px se perdería. La caja va con el dibujo o el trazo queda
   descentrado (CLAUDE.md, Iconos). */
.fn-devolucion-marca .icono { width: 44%; height: 44%; }

/* El item que **no salió**, desaturado. El color por sí solo no es un canal
   —la pastilla de estado lo dice con palabras y el motivo también—, así que
   esto es lo que hace que se note de un vistazo cuál de las tres tarjetas de un
   pedido se quedó fuera sin tener que leerlas.
   A 0.6 y no a 1: en blanco y negro entero la tarjeta parece rota. */
.fn-lamina--nosalio img { filter: grayscale(0.6); }

/* La etiqueta del último item que salió, **sobre la lámina**: la esquina es
   su sitio, igual que `.fn-sinsaldo` en la otra esquina de la tarjeta de la
   tienda. Hubo una segunda forma, en la cabecera del bloque de un pedido de
   varios; el bloque ya no existe —cada item es su tarjeta— y con él se fue.

   La base es una `.etiqueta` normal con el crema encima: rotula, y el crema es
   lo que marca (CLAUDE.md). **No va en el acento**: no se pulsa. */
.fn-orden-ultima {
  border-color: var(--borde-alto);
  color: var(--texto-alto);
  font-weight: 600;
}

/* La de encima de la lámina va sobre **su propio fondo oscuro y no sobre la
   superficie**: debajo puede haber el render del objeto, que es de cualquier
   color, o el degradado de su rareza. Por eso el hex escrito y no una variable
   —es un velo, como el de la tarjeta de tienda—, y por eso el crema a pelo. */
.fn-orden-ultima--lamina {
  position: absolute;
  inset: var(--e2) auto auto var(--e2);
  z-index: 2;
  border-color: transparent;
  background-color: color-mix(in srgb, #000000 72%, transparent);
  color: #FFFBD5;
  font-size: 0.75rem;
}

/* El pie de una tarjeta de item —la fecha y el estado— va abajo del todo: con
   el motivo de por medio, las tarjetas de una fila acaban de altos distintos y
   el pie tiene que quedar alineado con el borde de abajo. */
.fn-orden-item .fn-datos { flex: 1 1 auto; }
.fn-orden-item .fn-orden-pie { margin-top: auto; }

/* **Lo devuelto, en la tarjeta del item que no salió.** «No salió · se te
   devolvieron 1,360»: la pastilla del estado y la frase van juntas porque son
   la misma noticia, y el motivo va debajo. Era una tarjeta de «Devolución»
   aparte, lejos del item, y había que casarlas a mano.

   El texto en `--texto` y la cifra en crema, como cualquier cifra dentro de
   una frase (CLAUDE.md, Cifras). **No va en verde**: el verde es de lo que
   entra en el libro, y esta tarjeta es un item que no salió. */
.fn-orden-devuelto {
  color: var(--texto);
  font-size: 0.8125rem;
  line-height: 1.7;
}
/* **En línea, no en flex**: es una frase, y partida en dos cajas la pastilla
   se quedaba sola arriba y «· se te devolvieron» empezaba el renglón de abajo.
   Así, si no cabe, se corta por donde se corta una frase. */
.fn-orden-devuelto-texto { display: inline; }
.fn-orden-devuelto .fn-precio {
  gap: var(--e1);
  color: var(--texto-alto);
  vertical-align: middle;
}
.fn-orden-devuelto .fn-precio .vbuck { width: 16px; height: 16px; }

/* La pastilla de la Suerte del día en el item que llevó el descuento. Es la
   misma `.fn-suerte-pct` de la sección —el oro de la tienda rellenando, con el
   texto que calcula el servidor encima—: lo que la ruleta prometió y lo que
   se cobró se reconocen como la misma cosa. */
.fn-orden-suerte { display: flex; }

/* El signo va **escrito** delante del número y no sólo en el color: el color
   por sí solo no es un canal (CLAUDE.md). */
.fn-orden-importe { font-variant-numeric: tabular-nums; }

/* El ticket de una recarga del Verificador, debajo del importe. **Metadato**:
   va en `--texto-tenue`, que es lo que le toca acompañando a un dato, y en
   monoespaciada como el resto de los códigos de la casa (CLAUDE.md, Cifras). */
.fn-orden-ticket {
  color: var(--texto-tenue);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}
.fn-orden--entra .fn-orden-importe { color: var(--ok-alto); }
.fn-orden--sale .fn-orden-importe { color: var(--peligro-alto); }

/* A quién se le envió, cuando lo haya: la línea que sólo llevan las compras. */
.fn-orden-para {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--texto-suave);
  font-size: 0.875rem;
}
.fn-orden-para .icono { font-size: 16px; width: 16px; height: 16px; }

.fn-orden-motivo {
  color: var(--texto-suave);
  font-size: 0.875rem;
  line-height: 1.3;
  /* El motivo de una devolución nombra al destinatario, y el cliente escribe
     el nick que quiera: "ElManuDelBarrioQueJuegaTodaLaNoche" es **una palabra**
     y sin esto se sale de la tarjeta por el borde. Se vio en una foto. */
  overflow-wrap: anywhere;
}

/* El pie: la fecha a la izquierda y el estado a la derecha. Se parte en dos
   renglones antes que recortar la fecha, que es el dato que se busca. */
.fn-orden-pie {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--e1) var(--e2);
  margin-top: var(--e1);
}
.fn-orden-estado { font-size: 0.75rem; }

.fn-vacio {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--e3);
}
.fn-vacio p { margin: 0; }

/* --- El tinte de las superficies de la tienda ----------------------------
   **Tercera fuga del mismo tipo, y la última.** `.tarjeta` y `.nota` son piezas
   de la casa y traen puesto `--grad-superficie`, el degradado gris de todo el
   panel; dentro de la tienda eso se ve como una mancha gris que va a negro por
   la esquina de arriba y que no viene de ninguna parte —pasó en el diálogo,
   pasó en la cabecera con el halo ámbar del fondo, y volvió a pasar en las
   tarjetas de Cuenta—.

   El tinte vive en un token y no repetido en cada regla, que es lo que hace que
   la próxima superficie que se añada lo herede sin acordarse de nada. Y hay un
   test que recorre la hoja y **falla nombrando la clase** si alguna regla de
   `.fn` vuelve a pintar con un color de la casa. */
.fn[data-tema] {
  --fn-tinte: linear-gradient(163deg,
    color-mix(in srgb, var(--acento) 14%, transparent) 0%,
    transparent 62%);
}
.fn-dialogo,
.fn .tarjeta,
.fn .nota {
  background-image: var(--fn-tinte);
}

/* --- Móvil ---------------------------------------------------------------
   Dos líneas: arriba quién es la tienda y qué se hace, debajo el contador. */
@media (max-width: 560px) {
  .fn-marca-grupo { display: contents; }
  .fn-marca { grid-column: 1; grid-row: 1; min-width: 0; }
  .fn-marca .marca-logo { width: 40px; height: 40px; }
  .fn-sesion { grid-column: 2; grid-row: 1; }
  /* **La casilla es la fila entera, no el reloj.** El reloj solo dejaba al
     botón de canjear sin sitio asignado, y un hijo de un `display: contents`
     sin casilla se coloca donde puede: una tercera línea, debajo. Aquí la
     segunda línea es la fila de las dos píldoras, estirada de lado a lado para
     que el botón tenga de dónde crecer. */
  .fn-fila-sesion { grid-column: 1 / -1; grid-row: 2; gap: var(--e2); }
  /* **La pastilla no puede crecer a lo ancho aquí.** Con las dos cifras en
     línea se llevaba unos setenta píxeles de la columna de la derecha y el
     nombre de la tienda se salía por debajo; apiladas, la pastilla mide lo que
     mida la más ancha de las dos. Ya pasó una vez y se arregló así. */
  .fn-saldo-cifras {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    line-height: 1.15;
  }
  .fn-pestanas { padding: var(--e2) var(--e3); }
  .fn-pestana { padding: var(--e2) var(--e3); font-size: 0.875rem; }
  /* Las órdenes ya no son filas, así que aquí no hay nada que envolver. Lo que
     sí hace falta es **repetir las dos columnas**: la regla de `--ordenes` se
     escribe después de la de móvil y las dos pesan lo mismo, así que sin esta
     línea el mínimo de 200 le ganaría y las órdenes bajarían a una columna
     mientras la tienda de al lado sigue con dos. Son las mismas tarjetas: se
     ven igual. */
  .fn-rejilla--ordenes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fn-orden-pie { gap: var(--e1); }
}

/* ==========================================================================
   Mis destinos: la agenda del cliente, dentro de la pestaña Cuenta
   ==========================================================================

   **Filas y no tarjetas, y es lo contrario de lo que hacen Órdenes y la
   tienda.** Allí lo que se enseña es un objeto —tiene render, tiene precio y se
   mira— y aquí lo que se enseña es una entrada de agenda: un icono de 28, un
   nombre y dos metadatos. En tarjeta, diez destinos eran diez láminas vacías
   ocupando la pantalla entera; en fila caben los diez de un vistazo, que es lo
   que se viene a hacer aquí —ver a quién le puedes regalar y cambiarlo—.

   Todo el color sale del acento del vendedor, como el resto de la tienda: el
   filo al pasar por encima, el relleno del medidor y los botones. Lo único que
   no es suyo son los tres colores de estado —los dos avisos de límite y el rojo
   de borrar—, que dicen qué pasa y no de quién es la tienda. */

.fn-destinos-alto {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--e4);
  flex-wrap: wrap;
}
.fn-destinos-cuenta { display: flex; flex-direction: column; gap: var(--e2); }
.fn-destinos-anadir { flex: none; }

/* La cifra dentro de la frase que la explica: ni tarjeta de métrica, ni número
   gigante (CLAUDE.md, Cifras). Lo que la destaca son el color y el peso. */
.fn-destinos-contador { margin: 0; color: var(--texto); }
.fn-destinos-contador .cifras,
.fn-destinos-limite .cifras {
  color: var(--texto-alto);
  font-weight: 600;
}

/* El medidor es una pieza de la casa y viene con `--grad-acento` puesto, que es
   el morado del panel: aquí el relleno lo pone el vendedor. Mismo caso que el
   botón, y el único sitio donde esta hoja tiene que acordarse de pisarlo. */
.fn .medidor-relleno {
  background-image: none;
  background-color: var(--acento);
}
/* El de "al límite" sí se queda como está: `--aviso` es estado y no marca. */
.fn .medidor-relleno--lleno {
  background-color: var(--aviso);
}

/* Las dos notas van sin margen: el `gap` del `.fn-panel` ya las separa, y el
   `margin-bottom` que `.exito` trae de la casa las dejaba al doble. El icono
   ya lo alinea `.nota .icono`. */
.fn-destinos-hecho,
.fn-destinos-limite { margin: 0; }

/* --- La lista ----------------------------------------------------------- */
.fn-destinos-lista { display: flex; flex-direction: column; gap: var(--e2); }

.fn-destino {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie);
  /* El mismo tinte del acento que las tarjetas y los diálogos: la sección
     entera se tiñe con las mismas reglas que el resto de la tienda. */
  background-image: var(--fn-tinte);
  transition: border-color var(--t-rapida);
}
/* No sube al pasar por encima —la fila no es un enlace, lo que se pulsa son sus
   dos botones— pero el borde sí responde: es lo que dice que la fila es una
   pieza y no una línea de una tabla. */
.fn-destino:hover { border-color: var(--borde-alto); }

.fn-destino-icono {
  flex: none;
  border-radius: var(--r-menudo);
}

.fn-destino-datos {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fn-destino-nombre {
  margin: 0;
  color: var(--texto-alto);
  font-weight: 600;
  /* Un nombre de Epic de 16 caracteres sin espacios no cabe en un móvil de
     380: que parta antes que desbordar la fila. */
  overflow-wrap: anywhere;
}

/* El segundo nombre: el canónico que devolvió Epic, cuando no es el que el
   cliente escribió. **Un escalón por debajo del de arriba y otro por encima de
   los metadatos**, que es lo que dice que son dos nombres de lo mismo y no un
   dato suelto. El rótulo va en tono de apoyo y el nombre en el del cuerpo: lo
   que hay que leer es el nombre, no la palabra "Epic". */
.fn-destino-epic {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}
.fn-destino-epic strong { color: var(--texto); font-weight: 600; }

/* Las dos chinchetas del título del diálogo del formulario, la de añadir y la
   de cambiar. **La regla de `[hidden]` no sobra y es de las que muerden**: el
   icono de dentro lleva `.icono`, que trae `display: inline-block`, y un
   `display` del autor le gana siempre al del navegador. Sin
   envoltorio y sin esta línea, las dos chinchetas saldrían a la vez. */
.fn-destino-marca { display: inline-flex; }
.fn-destino-marca[hidden] { display: none; }
/* Los metadatos van en `--texto-suave` y no en `--texto-tenue`: la fila lleva
   el tinte del acento encima de `--superficie`, y el tenue sólo está medido
   hasta `--superficie-2` limpia. El suave llega a AA sobre las cuatro. */
.fn-destino-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--e1) var(--e3);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}

.fn-destino-acciones { display: flex; flex: none; gap: var(--e1); }

/* Los dos botones de la fila. Cuadrados y de borde neutro en reposo: son dos
   por fila y diez filas, y con relleno de acento la lista entera se volvería
   una pared de botones. El acento entra al pasar por encima, que es cuando hay
   que saber cuál se va a pulsar. */
.fn-destino-accion {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  color: var(--texto-suave);
  transition: color var(--t-rapida), border-color var(--t-rapida),
              background-color var(--t-rapida);
}
/* El glifo, **al tamaño de la casa y con su caja**. Sin la caja explícita el
   lápiz y la papelera se sentaban distinto que los demás iconos de la página:
   la fuente los dibuja con otra altura óptica y sólo el `font-size` no los
   iguala (CLAUDE.md, Iconos). */
.fn-destino-accion .icono { font-size: 20px; width: 20px; height: 20px; }

.fn-destino-accion:hover {
  border-color: var(--acento-hondo);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
}
/* Borrar es lo único de la sección que no se deshace solo, así que al pasar por
   encima cambia de familia de color. **En reposo va como el otro**, y esto se
   ha probado en las dos direcciones.

   Estuvo un rato en rojo permanente con el argumento de que dos botones
   iguales dejan el destructivo sin marcar. **Mirado con la agenda llena —diez
   filas, que es el tope— no se sostiene**: el rojo se repite en columna y la
   lista entera se lee como una fila de alarmas, en una pantalla a la que se
   viene a mirar los destinatarios y no a borrarlos. Y el argumento tampoco era
   bueno: un lápiz y una papelera tienen siluetas que no se parecen en nada, así
   que distinguirlos no dependía del color.

   La escena `destinos-diez` de `tools/screenshots.py` existe para esto: es lo
   que hay que tener delante para decidirlo, y la próxima vez se mira ahí en vez
   de discutirlo. */
.fn-destino-accion--quita:hover {
  border-color: var(--peligro);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  color: var(--peligro-alto);
}
/* El lápiz cuando las comprobaciones están en pausa. **No es un botón que no
   hace nada**: es un botón apagado al lado de un aviso que dice por qué y hasta
   cuándo, y el motivo va también en su `title` y en su texto para quien no ve
   ninguno de los dos. */
.fn-destino-accion--apagada {
  border-color: var(--borde);
  color: var(--texto-tenue);
  cursor: default;
}
.fn-destino-accion--apagada:hover {
  border-color: var(--borde);
  background-color: transparent;
  color: var(--texto-tenue);
}

/* El estado vacío ocupa el ancho entero y no se centra: lo que hay dentro es
   una explicación de para qué sirve esto, y un párrafo centrado de cuatro
   líneas se lee peor que uno alineado. */
.fn-destinos-vacio { padding: var(--e5); max-width: 60ch; }
.fn-destinos-vacio strong { color: var(--texto-alto); }

.fn-destino-ayuda { margin: 0; color: var(--texto-suave); font-size: 0.875rem; }

@media (max-width: 560px) {
  /* El botón de agregar baja a su propia línea y ocupa el ancho: en 380 px, al
     lado del contador se quedaba en dos palabras partidas. */
  .fn-destinos-alto { gap: var(--e3); }
  .fn-destinos-anadir { width: 100%; justify-content: center; }
  /* La fila se parte en dos: arriba el icono y el nombre, debajo los botones a
     la derecha. En una sola línea, el nombre se quedaba en cuatro letras. */
  .fn-destino { flex-wrap: wrap; }
  .fn-destino-datos { flex-basis: calc(100% - 28px - var(--e3)); }
  .fn-destino-acciones { margin-left: auto; }
}

/* ==========================================================================
   El carrito de la tienda pública
   ==========================================================================

   Tres piezas y ninguna es una página: el selector de destinos de la ficha, la
   barra flotante que dice lo que llevas y el panel que lo enseña. **Todo el
   color sale del acento del vendedor**, como el resto de la tienda: el destino
   elegido, la barra y el botón de enviar. Lo único que no es suyo son el rojo
   de quitar —que es estado— y el dorado de la moneda, que es del juego.

   El carrito **no existe sin JavaScript**, así que ninguna de las tres piezas
   se ve sin él: la barra nace `hidden` y la enciende el guion, y el panel es un
   velo más de los que ya había. */

/* --- El selector de destinos de la ficha ---------------------------------
   Filas y no tres botones en rejilla, que es lo que había cuando aquí se
   elegía plataforma: son hasta diez y cada uno lleva un nombre que puede ser
   largo. Con diez, la lista hace su propio scroll y el artículo y los botones
   de la ficha se quedan quietos. */
/* Nace apagado en la ficha de quien no tiene ningún destino, y lo enciende el
   guion al crear el primero: sin esta línea saldría un selector vacío encima de
   la invitación a crear uno. */
.fn-destinatarios[hidden] { display: none; }

.fn-destinatarios {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  border: 0;
  /* **Un `fieldset` trae `min-width: min-content` del navegador**, y eso le
     impide encoger por debajo de lo que mida su contenido más ancho: con un
     nick largo —que va en `nowrap` para poder recortarlo— las filas se salían
     del diálogo por la derecha y la plataforma quedaba fuera de la pantalla.
     Sin esta línea, el recorte del nombre no llega a activarse nunca porque
     nada le aprieta. Se vio en una foto. */
  min-width: 0;
  max-height: 44vh;
  overflow-y: auto;
}


/* **Va por `.fn-destinatarios .fn-destinatario` y no por la clase sola**, y no
   es manía: `.fn-form label` —que existe desde la primera versión de estos
   diálogos— trae `display: block`, y un selector de clase+elemento pesa más que
   uno de clase. Con la clase sola, la fila se pintaba como un bloque: el icono
   pegado al nombre, la plataforma detrás en vez de al final, y el nombre largo
   sin recortar. **Se vio en una foto, no en un test.** */
.fn-destinatarios .fn-destinatario {
  display: flex;
  /* Y el `margin-bottom` que trae esa misma regla, que aquí sobra: la lista ya
     separa sus filas con el `gap`. */
  margin: 0;
  align-items: center;
  gap: var(--e3);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-size: 0.875rem;
  text-decoration: none;
  cursor: pointer;
  /* La curva ya va dentro de `--t-rapida`: con las dos, la declaración entera
     es inválida y el navegador la tira sin decir nada. */
  transition: background-color var(--t-rapida),
              border-color var(--t-rapida),
              color var(--t-rapida);
}
.fn-destinatarios .fn-destinatario img {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: var(--r-menudo);
}
/* El `+` de «Otro» es lo único accionable de la lista que no es una opción, y
   va en el acento: es lo que dice que ahí se va a otro sitio. */
.fn-destinatarios .fn-destinatario .icono {
  flex: none;
  color: var(--acento);
}

.fn-destinatario-nombre {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
  font-weight: 600;
}
/* La plataforma es el dato de apoyo y se distingue por tamaño y por tono, no
   por color propio (CLAUDE.md, Paleta). `--texto-suave` y no `--texto-tenue`
   porque la fila sube a `--superficie-3` al pasar por encima, y ahí el tenue
   ya no llega a AA. */
.fn-destinatario-donde {
  flex: none;
  color: var(--texto-suave);
  font-size: 0.75rem;
}

.fn-destinatarios .fn-destinatario:hover {
  background-color: var(--superficie-3);
  border-color: var(--acento-alto);
}

/* El elegido. **El color no es el único canal**: el borde se engorda, igual
   que en el selector de plataforma. El relleno es `--acento-tenue`, sobre el
   que el texto alto se lee en los dos temas. */
.fn-destinatario-radio:checked + .fn-destinatario {
  border: 2px solid var(--acento);
  padding: calc(var(--e2) - 1px) calc(var(--e3) - 1px);
  background-color: var(--acento-tenue);
}
/* El foco se pinta sobre la etiqueta porque el radio está escondido: sin esto,
   quien navega con el teclado no vería dónde está. */
.fn-destinatario-radio:focus-visible + .fn-destinatario {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

.fn-ficha-sin-destinos { margin: 0; color: var(--texto); }

/* Los dos motivos del aviso de que no alcanza. Se turnan, así que el que no
   toca se apaga con `hidden` y necesita su línea (CLAUDE.md, Reglas de UI). */
.fn-falta-motivo[hidden] { display: none; }
.fn-falta-motivo {
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}
/* Qué hacer, **en texto llano y sin cifras dentro**: las cifras ya están
   arriba, alineadas, y repetirlas dentro de la frase era lo que descuadraba el
   renglón. */
.fn-falta-que-hacer { margin: 0; color: var(--texto); }

/* --- La barra flotante ---------------------------------------------------
   Sólo está cuando hay algo dentro, así que no se le reserva sitio: flota
   sobre la rejilla y lo único que hace la página es dejarle aire debajo
   mientras existe (`.fn--con-carrito`). Va por debajo de los velos (z-index
   40) para que un diálogo abierto la tape. */
.fn-carrito-barra[hidden] { display: none; }

.fn-carrito-barra {
  position: fixed;
  left: 50%;
  bottom: var(--e4);
  transform: translateX(-50%);
  z-index: 30;
  display: flex;
  max-width: calc(100% - 2 * var(--e4));
}
.fn-carrito-abrir {
  gap: var(--e3);
  padding: var(--e3) var(--e5);
  font-size: 0.9375rem;
}
/* La cifra y la palabra, dentro de la frase que las explica. */
.fn-carrito-cuantos { white-space: nowrap; }
/* El precio de la barra hereda el color del botón —el que se calcula para que
   se lea sobre el acento del vendedor— en vez del `--texto-alto` de la
   tarjeta, que ahí no pinta nada. */
.fn-carrito-abrir .fn-precio { color: inherit; }

/* El acuse de que algo ha entrado: el mismo pulso de 320 ms que el de copiar.
   Se apaga solo con `prefers-reduced-motion`, que lo para todo. */
.fn-carrito-barra--suma .fn-carrito-abrir { animation: pulso 320ms var(--curva); }

/* El aire de debajo, mientras la barra está. Es lo que impide que la última
   fila de tarjetas se quede debajo de ella sin forma de verla. */
.fn--con-carrito .fn-cuerpo { padding-bottom: 96px; }

/* --- El panel ------------------------------------------------------------
   Es un velo más de los que ya había —mismo fondo, mismo cierre, misma tecla
   Escape— y sólo se ensancha: aquí hay una lista, y a 420px una línea con
   lámina, nombre, destinatario y precio no cabe. */
.fn-carrito-panel { max-width: 520px; }

/* --- El panel, cuando la ventana es BAJA ---------------------------------
   **Condicionado al alto y no al ancho**, que es lo que tiene el problema.

   En escritorio el panel no llevaba techo: lo acotaba la lista de dentro
   (`.fn-carrito-lista`, 48vh), y el resto —la cabecera, el resumen y el botón
   de enviar— se sumaba por encima. Con el carrito lleno eso daba 628px en una
   ventana de 600 y 571 en una de 480, y el sobrante **caía por arriba**: el
   velo centra con `align-items: center`, así que el desbordamiento se reparte a
   los dos lados y el de arriba **no se alcanza** —el scroll no va en negativo,
   y `scrollTop` máximo era 0 medido en las cinco alturas—. A 480px eso se comía
   «Tu carrito» y el **botón de cerrar**.

   El arreglo es el que ya tenía el móvil —techo al panel y scroll propio,
   quitándoselo a la lista para no dejar dos barras anidadas—, pero **la
   condición allí es el ancho** (`max-width: 560px`), y una ventana de 1280x480
   recibe las reglas de escritorio. Un teléfono estaba cubierto; una portátil
   baja, no.

   **700px y no 660**: el recorte empieza por debajo de 657 —medido, el panel
   mide 657 en una ventana de 660 y entra por un pixel—, así que el corte va
   con margen. Por encima de 700 no cambia nada: `max-height` sólo actúa cuando
   el contenido lo pasa.

   Las dos declaraciones se repiten en el bloque de 560px de ancho y se quedan
   ahí: allí además el panel se pega abajo y pierde las esquinas, que es otra
   forma y no ésta. */
@media (max-height: 700px) {
  .fn-carrito-panel {
    max-height: 88vh;
    overflow-y: auto;
  }
  /* Sin esto quedarían dos barras, una dentro de otra, y la de fuera no
     llegaría a la cabecera hasta agotar la de dentro. */
  .fn-carrito-lista { max-height: none; }
}

.fn-carrito-lista {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  list-style: none;
  /* Con quince líneas el panel pasaría de la pantalla: la lista hace su propio
     scroll y el resumen y el botón de enviar no se van de la vista. */
  max-height: 48vh;
  overflow-y: auto;
}

.fn-carrito-linea {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
/* La lámina de la línea es la de la ficha en pequeño: mismo respaldo con el
   nombre sobre el color de la rareza cuando no hay render. */
.fn-carrito-lamina { width: 56px; }

.fn-carrito-datos {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: var(--e1);
}
.fn-carrito-datos .fn-nombre { font-size: 0.875rem; }
.fn-carrito-datos .fn-precio { font-size: 0.875rem; }

/* A quién va esta línea. El destinatario es **por línea y no por carrito**, así
   que va aquí y no en el resumen. */
.fn-carrito-para {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-width: 0;
  font-size: 0.8125rem;
}
.fn-carrito-icono { flex: none; border-radius: var(--r-menudo); }
.fn-carrito-quien {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto);
}
.fn-carrito-donde { flex: none; color: var(--texto-suave); }

/* Quitar es lo único destructivo del panel y va en el rojo de estado, que no es
   del vendedor. No pide confirmación: lo que se deshace es una línea de una
   lista que no ha cobrado nada. */
.fn-carrito-quitar {
  flex: none;
  display: inline-flex;
  padding: var(--e2);
  border: 1px solid transparent;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto-suave);
  cursor: pointer;
  transition: color var(--t-rapida), border-color var(--t-rapida),
              background-color var(--t-rapida);
}
.fn-carrito-quitar:hover {
  border-color: var(--peligro);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  color: var(--peligro-alto);
}

.fn-carrito-vacio { margin: 0; color: var(--texto-suave); }

/* --- Las tres líneas de la resta ----------------------------------------
   Rótulo a la izquierda, cifra a la derecha, y las tres cifras **alineadas
   entre sí**: es una resta y se lee como una resta. Antes era una frase
   corrida con las cifras incrustadas, que se partía por donde le tocaba al
   renglón.

   Las usa también el diálogo de que no alcanza, que tenía el mismo problema y
   se arregla igual: mismo bloque, mismas clases. */
/* Las usa también el popup del envío, y ahí se encienden y se apagan: sin esta
   línea el `display` del autor le gana al `[hidden]` del navegador y la fila
   sale igual (CLAUDE.md, Reglas de UI). */
.fn-carrito-fila[hidden] { display: none; }
.fn-carrito-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e3);
  margin: 0;
}
.fn-carrito-rotulo { color: var(--texto-suave); }

/* El restante es el dato con el que se decide, así que es el que se destaca: su
   propia raya encima y un escalón más de tamaño. **Ni él ni el total del
   carrito van en rojo**: no son un error, son lo que cuesta y lo que queda; el
   rojo de la casa es de estado y aquí no hay ninguno. */
.fn-carrito-fila--resto {
  padding-top: var(--e2);
  border-top: 1px solid var(--borde);
}
.fn-carrito-fila--resto .fn-carrito-rotulo { color: var(--texto); }
.fn-carrito-fila--resto .fn-precio { font-size: 1.0625rem; }

/* El resumen: tres cifras, cada una dentro de su frase. Ni tarjeta de métrica
   ni número gigante (CLAUDE.md, Cifras). */
.fn-carrito-resumen[hidden] { display: none; }
.fn-carrito-resumen {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  padding-top: var(--e3);
  border-top: 1px solid var(--borde);
  color: var(--texto);
  font-size: 0.875rem;
}
.fn-carrito-resumen p { margin: 0; }
.fn-carrito-queda { color: var(--texto-suave); }

.fn-carrito-enviar { justify-content: center; }
/* Apagado mientras no exista el envío. Se pinta como apagado —no como un botón
   normal que no hace nada— y debajo va el motivo escrito. */
.fn-carrito-enviar[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}
.fn-carrito-nota { margin: 0; text-align: center; }

/* --- Móvil ---------------------------------------------------------------
   Es donde peor se portan las dos piezas: la barra flotante se come el pulgar
   y el panel no cabe centrado. La barra pasa a ocupar el ancho —un solo blanco
   grande en vez de una pastilla en medio— y el panel se pega abajo, que es
   donde está la mano. */
@media (max-width: 560px) {
  .fn-carrito-barra {
    left: var(--e3);
    right: var(--e3);
    bottom: var(--e3);
    transform: none;
    max-width: none;
  }
  .fn-carrito-abrir { width: 100%; justify-content: space-between; }
  .fn--con-carrito .fn-cuerpo { padding-bottom: 88px; }

  /* El panel, pegado abajo y con el alto que le quepa: centrado, con el teclado
     abierto o en una pantalla de 380px se le iba media lista fuera. */
  .fn-velo--abajo { align-items: flex-end; padding: 0; }
  .fn-carrito-panel {
    max-width: none;
    max-height: 88vh;
    overflow-y: auto;
    /* Las esquinas de abajo se comen el borde de la pantalla: redondearlas
       dejaría dos triángulos de fondo debajo del panel. */
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
  .fn-carrito-lista { max-height: none; }
  .fn-carrito-lamina { width: 48px; }
  .fn-destinatarios { max-height: 38vh; }

  /* Los dos botones de la ficha no caben en una línea en 380px, y `.acciones`
     los parte: partidos a lo ancho se leen como dos decisiones y no como una
     fila que se ha roto por la mitad. */
  .fn-ficha-acciones .boton { flex: 1 1 100%; justify-content: center; }
}


/* ==========================================================================
   El popup del envío
   ==========================================================================

   Lo que se mira mientras el pedido sale, y lo que cuenta cómo ha quedado. No
   es un diálogo de confirmación —cuando esto aparece ya está cobrado— así que
   no hay dos botones enfrentados: hay un círculo que gira, y después una lista.

   Es **el mismo velo y la misma tarjeta** que el carrito y la ficha: lo único
   suyo es lo de dentro. Un diálogo con otra caja se leería como otra pantalla. */
.fn-envio-panel { max-width: 560px; }
/* El panel se enfoca a mano mientras no hay aspa que enfocar, y entonces no
   tiene que pintar su propio foco encima del de la tarjeta. */
.fn-envio-panel:focus { outline: none; }

/* --- Mientras dura ------------------------------------------------------- */
.fn-envio-espera[hidden] { display: none; }
.fn-envio-espera {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e3);
  padding: var(--e6) 0;
  text-align: center;
}
.fn-envio-espera p { margin: 0; }
.fn-envio-espera-texto { color: var(--texto); }

/* El círculo. Un anillo al que le falta un cuarto, girando: el trozo que falta
   es lo que hace que se vea el giro. El color es el del acento, que es el único
   que puede decir "el panel está haciendo algo". */
.fn-circulo {
  width: 40px;
  height: 40px;
  border: 3px solid var(--superficie-3);
  border-top-color: var(--acento);
  border-radius: var(--r-pastilla);
  animation: giro 900ms linear infinite;
}

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

/* Quieto. `prefers-reduced-motion` se respeta de verdad (CLAUDE.md): se para el
   giro y se queda el anillo entero en el acento, que es un cambio de color que
   persiste. Lo que hay que leer sigue estando en el texto de debajo, que es un
   `role="status"`: quien no ve el movimiento lo oye. */
@media (prefers-reduced-motion: reduce) {
  .fn-circulo {
    animation: none;
    border-color: var(--acento);
  }
}

/* --- El resultado --------------------------------------------------------
   Una frase que dice cuál de los tres finales ha sido. Va con su tono, pero el
   tono **no es el canal**: la frase dice lo mismo con palabras. */
.fn-envio-resumen {
  margin: 0;
  color: var(--texto);
}
.fn-envio-resumen--ok { color: var(--ok-alto); }
.fn-envio-resumen--sale { color: var(--peligro-alto); }
.fn-envio-resumen--espera { color: var(--texto-alto); }

.fn-envio-lista[hidden] { display: none; }
.fn-envio-lista {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  list-style: none;
  /* Con veinticinco líneas el panel pasaría de la pantalla: la lista hace su
     propio scroll y el saldo y los botones no se van de la vista. Es lo mismo
     que hace la lista del carrito y por lo mismo. */
  max-height: 46vh;
  overflow-y: auto;
}

.fn-envio-item {
  display: flex;
  align-items: flex-start;
  gap: var(--e3);
  padding: var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.fn-envio-lamina { width: 48px; }
.fn-envio-datos {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: var(--e1);
}
.fn-envio-datos .fn-nombre { font-size: 0.875rem; }
.fn-envio-datos .fn-precio { font-size: 0.8125rem; }

/* Quién la envió, en la que salió. Mismo escalón y mismo tono que en Órdenes:
   es el mismo dato leído en dos sitios. Se enciende y se apaga, así que su
   `display` necesita su propia línea (CLAUDE.md, Reglas de UI). */
.fn-envio-quien[hidden] { display: none; }
.fn-envio-quien {
  display: block;
  color: var(--texto-suave);
  font-size: 0.75rem;
}

/* El motivo, en la frase que sale de la tabla de códigos. Es lo único de la
   línea que hay que leer entero, así que va a ancho completo y en el color del
   cuerpo: pintarlo de rojo lo convertiría en una alarma, y lo que es es una
   explicación. */
.fn-envio-motivo {
  margin-top: var(--e1);
  padding-left: var(--e2);
  border-left: 2px solid var(--borde-alto);
  color: var(--texto);
  font-size: 0.8125rem;
  /* El nick del destinatario va dentro y es una sola palabra de hasta 64
     caracteres. Sin esto se sale del panel. */
  overflow-wrap: anywhere;
}

/* Las cuentas por las que pasó, una por línea. */
.fn-envio-intentos[hidden] { display: none; }
.fn-envio-intentos {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--e1) 0 0;
  padding-left: var(--e2);
  list-style: none;
}
.fn-envio-intento {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
  color: var(--texto-tenue);
  font-size: 0.75rem;
}
.fn-envio-intento-bot { flex: none; color: var(--texto-suave); }
.fn-envio-intento-motivo { min-width: 0; overflow-wrap: anywhere; }
.fn-envio-intento--ok .fn-envio-intento-bot { color: var(--ok-alto); }

/* El estado de la línea, escrito y con su tono. Mismos colores que la pastilla
   de Órdenes: es el mismo dato visto en dos sitios. */
.fn-envio-estado { flex: none; }
.fn-envio-estado--ok {
  border-color: var(--ok);
  background-color: color-mix(in srgb, var(--ok) 16%, transparent);
  color: var(--ok-alto);
}
.fn-envio-estado--sale {
  border-color: var(--peligro);
  background-color: color-mix(in srgb, var(--peligro) 16%, transparent);
  color: var(--peligro-alto);
}

/* El saldo final. **Del libro**, no restado aquí. Reusa las filas del resumen
   del carrito: es la misma resta leída en el mismo sitio de la pantalla. */
.fn-envio-saldo[hidden] { display: none; }
.fn-envio-a-bots[hidden] { display: none; }
.fn-envio-a-bots { justify-content: center; }
.fn-envio-ordenes { justify-content: center; }

/* Las dos salidas, en fila y con «Ver en Órdenes» primero: es la que lleva a
   donde queda escrito lo que pasó. En una columna estrecha se apilan, que es
   lo que hace `flex-wrap` con los dos a `flex: 1`. */
.fn-envio-pie {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}
.fn-envio-pie[hidden] { display: none; }
.fn-envio-pie > * { flex: 1 1 10rem; justify-content: center; }

/* El confeti del título, sólo cuando ha salido todo. */
.fn-envio-confeti { display: inline-flex; }
.fn-envio-confeti[hidden] { display: none; }

@media (max-width: 560px) {
  .fn-envio-panel { max-width: none; }
  /* **La lista sigue haciendo su propio scroll**, al revés que la del carrito,
     que en móvil lo suelta y deja crecer el panel. Aquí no se puede: el panel
     está centrado en el velo, y un flex centrado que no cabe **se sale por
     arriba** —el título y el resumen quedan fuera de la pantalla y no hay forma
     de subir hasta ellos—. Con la lista acotada, el panel cabe siempre y lo que
     se desplaza es lo que sobra, que es la lista. Se vio en una foto. */
  .fn-envio-lista { max-height: 42vh; }
  .fn-envio-lamina { width: 40px; }
  .fn-envio-item { gap: var(--e2); }
}

/* ==========================================================================
   La pestaña Bots de la tienda pública
   ==========================================================================

   Casillas y nada más: es una pantalla de preferencia, no de diagnóstico. No
   hay estados, ni semáforos, ni tarjetas con cifras —no hay nada que
   diagnosticar, porque esta pantalla no le pregunta nada a Epic—.

   La casilla es **la misma pieza** para «Todos» y para cada bot: mismo cuadro,
   mismo tamaño y mismo gesto, porque hacen lo mismo. Todo el color sale del
   acento del vendedor; el verde del guardado y el rojo del aviso son estado. */

.fn-bots-ayuda {
  margin: 0;
  max-width: 68ch;
  color: var(--texto);
}
.fn-bots-ayuda strong { color: var(--texto-alto); font-weight: 600; }
.fn-bots-ayuda .cifras { color: var(--texto-alto); }

.fn-bots-hecho,
.fn-bots-error { margin: 0; }

.fn-bots-form {
  display: flex;
  flex-direction: column;
  gap: var(--e4);
}

/* Arriba, «Todos» a la izquierda y el recuento a la derecha: el control y el
   dato que lo explica, en la misma línea. */
.fn-bots-alto {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--e3);
}
/* Nace escondido y lo enciende el guion: sin JavaScript no marca nada. */
.fn-bots-todos[hidden] { display: none; }
.fn-bots-todos { display: inline-flex; }

.fn-bots-contador { margin: 0; color: var(--texto); }
.fn-bots-contador .cifras { color: var(--texto-alto); font-weight: 600; }

/* La rejilla: tantas columnas como quepan con 200px de ancho mínimo. Un bot es
   una línea corta —un cuadro y un nick—, así que en ancho caben cinco o seis y
   se ven todos de un vistazo, que es lo que se viene a hacer aquí. */
.fn-bots-rejilla {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--e2);
}
.fn-bots-item { display: flex; min-width: 0; }

/* La casilla, que es la misma pieza para «Todos» y para cada bot. El `input` va
   escondido con `sr-solo` y no con `display:none`: así sigue recibiendo el foco
   del teclado y la barra espaciadora lo sigue marcando. */
.fn-bot {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: var(--e3);
  min-width: 0;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-size: 0.875rem;
  cursor: pointer;
  /* La curva ya va dentro de `--t-rapida`. */
  transition: background-color var(--t-rapida),
              border-color var(--t-rapida),
              color var(--t-rapida);
}

/* El cuadro. **La forma es el canal, no el color**: vacío con filo cuando no
   está marcado y relleno con el visto cuando sí, así que quien no distinga el
   acento sigue viendo cuál está puesto. Dentro de un control, un escalón menos
   de radio (CLAUDE.md, Radios). */
.fn-bot-marca {
  flex: none;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 2px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  color: transparent;
  transition: background-color var(--t-rapida),
              border-color var(--t-rapida),
              color var(--t-rapida);
}
.fn-bot-marca .icono { font-size: 18px; width: 18px; height: 18px; }

.fn-bot-nombre {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
  font-weight: 600;
}

.fn-bot:hover {
  background-color: var(--superficie-3);
  border-color: var(--acento-alto);
}

/* Marcada: el cuadro se rellena con el acento del vendedor y aparece el visto,
   que va en `--acento-texto` —el que se calcula para leerse encima de su
   acento, el mismo que usa el botón—. */
.fn-bot-check:checked + .fn-bot {
  border-color: var(--acento);
  background-color: var(--acento-tenue);
}
.fn-bot-check:checked + .fn-bot .fn-bot-marca {
  border-color: var(--acento);
  background-color: var(--acento);
  color: var(--acento-texto);
}

/* «Todos» con unas cuantas puestas: ni marcada ni sin marcar. Se pinta una
   raya, que es lo que significa —"algunas"— y es otra forma más, no otro color.
   La raya es un degradado plano y no un `box-shadow` interior: las sombras
   siguen prohibidas (CLAUDE.md, Profundidad). */
.fn-bot-check:indeterminate + .fn-bot .fn-bot-marca {
  border-color: var(--acento);
  background-image: linear-gradient(var(--acento), var(--acento));
  background-size: 10px 2px;
  background-position: center;
  background-repeat: no-repeat;
}

/* El foco se pinta sobre la etiqueta porque la casilla está escondida: sin
   esto, quien navega con el teclado no vería dónde está. */
.fn-bot-check:focus-visible + .fn-bot {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* «Todos» es la misma casilla y se distingue por lo que dice, no por el color:
   lo único que cambia es que no está dentro de la rejilla. */
.fn-bot--todos { flex: none; }

.fn-bots-acciones { margin-top: 0; }
.fn-bots-vacio { padding: var(--e5); max-width: 60ch; }

@media (max-width: 560px) {
  /* Dos columnas: un nick corto cabe, y a una sola columna una lista de trece
     bots se hace larguísima. */
  .fn-bots-rejilla { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* «Todos» y el recuento pasan a dos líneas: en 380px no caben al lado. */
  .fn-bots-alto { gap: var(--e2); }
  .fn-bots-acciones .boton { flex: 1 1 100%; justify-content: center; }
}

/* --- El pedido dentro de su tarjeta de Órdenes ---------------------------
   Una compra no es un número negativo en el libro: es lo que se pidió. Las
   líneas van dentro de la tarjeta, cada una con su nombre, a quién va y lo que
   costó —lo que se congeló al cobrar, no el catálogo de hoy—. */
.fn-orden-lineas {
  list-style: none;
  margin: 0;
  padding: var(--e2) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  border-top: 1px solid var(--borde);
}
.fn-orden-linea {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: 0.8125rem;
}
.fn-orden-linea-nombre {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
  font-weight: 600;
}
.fn-orden-linea-precio { font-size: 0.8125rem; }

/* --- Qué pasó con cada línea --------------------------------------------
   Lo que convierte un pedido a medias en algo que se entiende: quién lo envió,
   o por qué no salió y por qué cuentas pasó. Va **dentro** de la línea y en un
   escalón más pequeño, porque es la explicación de lo de arriba y no otro dato
   al mismo nivel. */
.fn-orden-linea-quien {
  color: var(--texto-suave);
  font-size: 0.75rem;
}
.fn-orden-linea-quien .mono { color: var(--texto); }

/* El motivo va en su caja: es lo único de la tarjeta que hay que leer entero,
   y suelto entre las líneas se perdía. Sin color de estado —el rojo de la casa
   es para lo que ha ido mal *ahora*, y esto es el relato de un pedido cerrado—
   pero con el filo de la izquierda, que es lo que lo separa. */
.fn-orden-linea-motivo {
  margin-top: var(--e1);
  padding-left: var(--e2);
  border-left: 2px solid var(--borde-alto);
  color: var(--texto);
  font-size: 0.75rem;
  /* Lleva dentro el nick del destinatario, que es una sola palabra de hasta 64
     caracteres: sin esto se sale de la tarjeta. */
  overflow-wrap: anywhere;
}

/* Las cuentas por las que pasó. Nombre a la izquierda y qué contestó a la
   derecha, una por línea: es una lista de hechos, no un párrafo. */
.fn-orden-intentos {
  list-style: none;
  margin: var(--e1) 0 0;
  padding-left: var(--e2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fn-orden-intento {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
  color: var(--texto-tenue);
  font-size: 0.6875rem;
}
.fn-orden-intento-bot { flex: none; color: var(--texto-suave); }
.fn-orden-intento-motivo { min-width: 0; overflow-wrap: anywhere; }
/* El que sí salió se distingue **además** por el color: el nombre en el verde
   de estado. La palabra sigue estando en su motivo, que es el canal de verdad. */
.fn-orden-intento--ok .fn-orden-intento-bot { color: var(--ok-alto); }

/* A quién va la línea. **El nombre se recorta**: el cliente escribe el nick que
   quiera y la tarjeta mide 200px, así que sin esto se sale por el borde sin
   avisar de que hay más. Se vio en una foto. */
.fn-orden-linea .fn-orden-para {
  min-width: 0;
  font-size: 0.75rem;
}
.fn-orden-linea .fn-orden-para .mono {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* El estado, por tono. `espera` es lo normal entre el cobro y el envío, así que
   va en el neutro de la casa y no en un color de alarma: un pedido pendiente no
   es un pedido con problemas. */
.fn-orden-estado--espera {
  border-color: var(--borde-alto);
  color: var(--texto-suave);
}

.fn-ordenes-hecho { margin: 0; }

/* El motivo de que el pedido no saliera, y el camino a Bots cuando es lo que
   lo arregla. Los dos nacen apagados. */
.fn-carrito-error[hidden],
.fn-carrito-a-bots[hidden] { display: none; }
.fn-carrito-error { margin: 0; }
.fn-carrito-a-bots { justify-content: center; }

/* «Estás teniendo el mismo problema» (`_fn-mismo-problema.html`): el aviso
   grande del tercer intento con un error del cliente. Es un diálogo de la
   tienda como los demás —`fn-velo` y `fn-dialogo`— con el filo de --aviso,
   que es estado y no un color de la casa. Las tres pantallas se esconden con
   `hidden`, así que llevan su línea. */
.fn-mp-pantalla[hidden],
.fn-mp-bots[hidden] { display: none; }
.fn-mp { border: 2px solid var(--aviso); }
.fn-mp .fn-dialogo-alto .icono { color: var(--aviso-alto); }
.fn-mp-pantalla {
  display: grid;
  gap: var(--e3);
}
.fn-mp-subtitulo {
  margin: 0;
  font-weight: 600;
  color: var(--texto-alto);
}
.fn-mp-texto,
.fn-mp-pregunta,
.fn-mp-cobro { margin: 0; color: var(--texto); }
.fn-mp-cobro { color: var(--texto-suave); }
.fn-mp-botones {
  display: grid;
  gap: var(--e2);
}
.fn-mp-botones .boton,
.fn-mp-bots { width: 100%; min-height: 40px; justify-content: center; }

/* ==========================================================================
   CONF CHECKOUT — la pantalla del Verificador de pagos (bloque 2b)
   ==========================================================================

   Todo sale de los tokens de `:root`. Ni un hex, y no es una regla de estilo:
   las páginas públicas redefinen esos mismos tokens bajo `[data-tema="claro"]`,
   así que un color escrito a mano aquí sería el único que no cambiaría de tema.
   Hay un test que lo vigila.

   Lo que se toca con el dedo mide **40px de alto como mínimo** —interruptores,
   botones de menú, asas, opciones de segmento—, que es el suelo por debajo del
   cual se falla el objetivo en un móvil.

   Y **todo cabe en 360px**: las rejillas caen a una columna, la fila de una
   moneda deja que el texto se recorte en vez de empujar, y el dato de una
   cuenta va con `min-width: 0` para que el `text-overflow` funcione de verdad.
   Sin eso, un número de cuenta largo saca la página por el lado. */

/* Sin JavaScript no hay sección. Se dice con palabras y se esconde en cuanto
   `base.html` marca el <html> con `con-js`, que lo hace en el <head> antes de
   pintar: así quien tiene JavaScript no llega a ver el aviso ni un fotograma. */
.ck-sinjs { display: flex; align-items: center; gap: var(--e2); }
.con-js .ck-sinjs { display: none; }

/* `minmax(0, 1fr)` y no la columna implícita, que es `auto`. **Sin esto la
   pantalla entera desbordaba a lo ancho**: una columna `auto` mide lo que mide
   su contenido mínimo, y las tarjetas —que además nacen con `min-width: auto` y
   por tanto se niegan a encoger por debajo de él— resolvían a 441 px dentro de
   un viewport de 380. Se vio midiendo en el navegador, no a ojo: el `.ck` medía
   356 y su `grid-template-columns` decía 441.

   Es el mismo `minmax(0, …)` de la lista de solicitudes, y por lo mismo. */
.ck {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e4);
}
.ck[hidden] { display: none; }

/* En sólo lectura no se enseña ningún botón de escritura: no se apagan, se
   quitan. Un botón deshabilitado invita a pulsarlo y no explica nada; el
   estado lo cuenta la tarjeta de licencia, que es donde está el porqué. */
.ck--solo-lectura .ck-solo-escritura { display: none; }

/* `min-width: 0` porque un hijo de rejilla nace con `min-width: auto` y eso es
   lo que le deja crecer por encima de su columna. Y `minmax(0, 1fr)` dentro, por
   lo mismo que en `.ck`: lo que hay dentro de una tarjeta —un número de cuenta
   largo, un `<h2>`— tiene que recortarse o partirse, no empujar. */
.ck-tarjeta {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e3);
  min-width: 0;
}
/* Su línea, porque hay tarjetas que nacen escondidas —la de Solicitudes la
   enciende el guion al recibir la lista—. La regla de raíz ya las cubre; ésta es
   la red, y dice aquí que este `display` puede toparse con un `hidden`. */
.ck-tarjeta[hidden] { display: none; }

.ck-tarjeta-alto {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--e3);
}

/* --- La cabecera y su etiqueta de estado -------------------------------- */

.ck-cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e3);
}

/* Es un botón porque cuando dice "No visible en tu tienda" hay un motivo
   detrás. Operativo va en --ok y el resto en --aviso: no es un error —nada se
   ha roto— es un estado que pide atención. */
.ck-estado {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  min-height: 40px;
  padding: 0 var(--e3);
  border: 1px solid var(--aviso);
  border-radius: var(--r-pastilla);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--texto);
  font-size: 0.8125rem;
  cursor: pointer;
}
.ck-estado[hidden] { display: none; }
.ck-estado[disabled] { cursor: default; }
.ck-estado--ok {
  border-color: var(--ok);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
}

/* El punto no es el único canal: al lado va la palabra. */
.ck-estado-punto {
  width: 8px; height: 8px;
  border-radius: var(--r-pastilla);
  background-color: var(--aviso-alto);
}
.ck-estado--ok .ck-estado-punto { background-color: var(--ok-alto); }

.ck-motivo {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}

.ck-error { display: flex; align-items: center; gap: var(--e2); }
.ck-error[hidden] { display: none; }

/* --- La licencia -------------------------------------------------------- */

.ck-lic-texto { margin: 0; color: var(--texto); }

/* Tres días o menos. En ámbar porque es lo que pide atención y no lo que ha
   fallado; la cifra sigue diciendo cuántos quedan, así que el color acompaña
   en vez de ser el único aviso. */
.ck-lic-urgente { color: var(--aviso-alto); }

.ck-lic-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--e3);
}
.ck-lic-form[hidden] { display: none; }
.ck-lic-form .ck-campo { flex: 1 1 14rem; }

/* --- Campos ------------------------------------------------------------- */

.ck-campo {
  display: grid;
  gap: var(--e1);
  min-width: 0;
}
.ck-campo[hidden] { display: none; }

.ck-rotulo {
  color: var(--texto-tenue);
  font-size: 0.75rem;
}

/* El icono de V-Bucks **dentro de una frase o de un rótulo**. `.vbuck` es un
   bloque porque casi siempre va en una fila flex junto a su cifra; aquí va en
   medio de un texto y como bloque partía la línea. Baja a la línea base con el
   mismo truco que los iconos de Material: alineado y un poco por debajo. */
.vbuck--en-linea {
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: -3px;
}

/* «Cambiar ítems» y «Volver a sortear» de cada ruleta, en el bloque de la
   Suerte de Global Store Link. Un `fieldset` porque agrupa seis campos bajo un
   nombre —y lo lee así un lector de pantalla—, sin el marco del navegador. Sin
   filo arriba tampoco: un borde de `fieldset` se parte alrededor de la
   leyenda y deja una raya colgando a su lado. Separa el aire. */
.suerte-acciones {
  margin: var(--e3) 0 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
.suerte-acciones > legend {
  padding: 0;
  margin-bottom: var(--e1);
  color: var(--texto-alto);
  font-weight: 600;
}

.ck-input {
  width: 100%;
  min-height: 40px;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font: inherit;
}
.ck-input:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
  border-color: var(--acento);
}
.ck-input:disabled {
  color: var(--texto-tenue);
  border-color: var(--borde);
}

.ck-ayuda {
  color: var(--texto-tenue);
  font-size: 0.75rem;
}
.ck-ayuda[hidden] { display: none; }

/* El error de un campo, pegado a él. En --peligro-alto, que es el que se lee;
   --peligro rellena y bordea pero no pasa AA como texto. */
.ck-error-linea {
  color: var(--peligro-alto);
  font-size: 0.75rem;
}
.ck-error-linea[hidden] { display: none; }

.ck-aviso-ambar {
  margin: 0;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--aviso);
  border-radius: var(--r-menudo);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--texto);
  font-size: 0.8125rem;
}
.ck-aviso-ambar[hidden] { display: none; }

.ck-vacio {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}
.ck-vacio[hidden] { display: none; }

/* --- Solicitudes, sólo para mirar --------------------------------------- */

/* La sección nace escondida: la enciende el guion al recibir la lista, y quien
   no tiene DELUX no recibe ninguna. Su línea de `[hidden]`, por la regla de
   siempre —la de raíz ya la cubre; ésta es la red y lo documenta aquí—. */
.ck-solicitudes[hidden] { display: none; }

.ck-sol-nota { margin: 0; color: var(--texto-suave); font-size: 0.8125rem; }
.ck-sol-nota[hidden] { display: none; }

/* `minmax(0, 1fr)` y no `1fr`: un item de rejilla no baja de su ancho mínimo de
   contenido, así que con `1fr` el correo de 40 caracteres del usuario empujaba
   la tarjeta entera fuera de la pantalla en vez de recortarse. Se vio en una
   foto; la regla del recorte estaba bien y lo que faltaba era dejarle encoger. */
.ck-sol-lista { display: grid; gap: var(--e2); grid-template-columns: minmax(0, 1fr); }

/* Una fila por solicitud. En móvil cae a una columna sola: son siete datos y
   una tabla de siete columnas en 380 px no es una tabla, es un scroll. */
.ck-sol {
  display: grid;
  min-width: 0;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: baseline;
  gap: var(--e1) var(--e3);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  font-size: 0.8125rem;
}
.ck-sol--pendiente { border-color: var(--aviso); }

.ck-sol-estado { color: var(--texto-tenue); }
.ck-sol--pendiente .ck-sol-estado { color: var(--aviso-alto); }

/* El usuario son hasta 40 caracteres desde el 3c y muchos un correo: se recorta
   y el nombre entero va en el `title`, que es lo que lo deja mirable sin que se
   coma el ancho de la fila. `min-width: 0` porque una columna `1fr` no se deja
   encoger sin él. */
.ck-sol-usuario {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
}

.ck-sol-monto, .ck-sol-vbucks { color: var(--texto-alto); }
.ck-sol-cuenta, .ck-sol-ticket, .ck-sol-fecha {
  grid-column: 1 / -1;
  color: var(--texto-tenue);
}
.ck-sol-comprobante { grid-column: 1 / -1; color: var(--texto-suave); }
.ck-sol-comprobante[hidden] { display: none; }
.ck-sol-motivo { grid-column: 1 / -1; color: var(--peligro-alto); }
.ck-sol-motivo[hidden] { display: none; }

@media (max-width: 560px) {
  .ck-sol-lista .ck-sol { grid-template-columns: minmax(0, 1fr); }
}

.ck-acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e3);
}
.ck-acciones[hidden] { display: none; }

/* --- El interruptor ----------------------------------------------------- */

/* La casilla de verdad sigue ahí y sigue siendo la que recibe el foco y el
   teclado: lo que se pinta es la pista de al lado. `appearance: none` en vez de
   esconderla con `display`, que la sacaría del árbol de accesibilidad. */
.interruptor {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  flex: none;
  cursor: pointer;
}
.interruptor input {
  appearance: none;
  position: absolute;
  width: 44px; height: 24px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.interruptor-pista {
  position: relative;
  width: 44px; height: 24px;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-3);
  transition: background-color var(--t-rapida) var(--curva),
              border-color var(--t-rapida) var(--curva);
}
.interruptor-pista::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 16px; height: 16px;
  border-radius: var(--r-pastilla);
  background-color: var(--texto-suave);
  transition: transform var(--t-rapida) var(--curva),
              background-color var(--t-rapida) var(--curva);
}
.interruptor input:checked + .interruptor-pista {
  border-color: var(--acento-hondo);
  background-color: var(--acento-hondo);
}
.interruptor input:checked + .interruptor-pista::after {
  transform: translateX(20px);
  background-color: var(--fondo);
}
.interruptor input:focus-visible + .interruptor-pista {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.interruptor input:disabled + .interruptor-pista { opacity: 0.5; cursor: default; }

.ck-fila-interruptor {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
}
.ck-fila-texto { min-width: 0; }
.ck-fila-texto h2, .ck-fila-texto h3 { margin: 0; }
/* **Un escalón por debajo del `h2` de su tarjeta**, y hace falta decirlo: la
   hoja fija `h1` en 1.5rem y `h2` en 1.125rem y **no fija `h3`**, así que un
   `h3` cae al 1.17em del navegador y sale MÁS grande que el `h2` que tiene
   encima. Se vio en Códigos de regalo, que es la primera tarjeta con dos
   interruptores: el de la franja se leía como un segundo título de sección.

   Se arregla aquí y no en el `h3` de la raíz a propósito: esa regla vale para
   siete plantillas y moverla es otro cambio. */
.ck-fila-texto h3 { font-size: 1rem; }
.ck-fila-texto p { margin: var(--e1) 0 0; }

/* --- El segmento (decimales, modo de fee, tipo de cuenta) ---------------- */

.ck-segmento { border: 0; margin: 0; padding: 0; min-width: 0; }
.ck-segmento-opciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e1);
  margin-top: var(--e1);
}
.ck-segmento-opcion {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  min-width: 44px;
  padding: 0 var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  font-size: 0.8125rem;
  cursor: pointer;
}
.ck-segmento-opcion input { position: absolute; opacity: 0; width: 0; height: 0; }
.ck-segmento-opcion:has(input:checked) {
  border-color: var(--acento-hondo);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
}
.ck-segmento-opcion:has(input:focus-visible) {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.ck-segmento-opcion:has(input:disabled) { cursor: default; opacity: 0.6; }
/* El filtro del bloque de la cripto: enlaces con la forma del segmento. */
a.ck-segmento-opcion { text-decoration: none; }
.ck-segmento-opcion[aria-current="page"],
.ck-segmento-opcion[aria-current="true"],
.ck-segmento-opcion[aria-checked="true"] {
  border-color: var(--acento-hondo);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
}

/* --- Cripto automático (CryptumPay) ------------------------------------ */

.ck-cripto { display: grid; gap: var(--e4); margin-top: var(--e4); }
.ck-cripto-form { display: grid; gap: var(--e4); }
.ck-cripto-herramientas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
}
.ck-cifra { color: var(--texto-alto); }
.ck-cripto-ejemplo {
  display: grid;
  gap: var(--e3);
  padding: var(--e4);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.ck-cripto-ejemplo h3 { font-size: 1rem; margin: 0; }
.ck-cripto-cuenta {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--e2) var(--e4);
  margin: 0;
}
.ck-cripto-cuenta dt { color: var(--texto-suave); }
.ck-cripto-cuenta dd { margin: 0; text-align: right; }
.ck-cripto-cubrir { display: grid; gap: var(--e3); justify-items: start; }
.ck-cripto-cubrir[hidden] { display: none; }
/* Un neto negativo (el contraasiento de una disputa): en el rojo que se lee, y
   con su signo escrito, que el color nunca es el único canal. */
.ck-negativo { color: var(--peligro-alto); }
/* Lo mismo en la tabla del admin: un pendiente negativo, con su signo. */

/* «Por revisar»: el dato de apoyo de una celda va debajo del principal, y las
   dos acciones una encima de otra. Con seis columnas, en fila no caben en la
   caja y se quedaban fuera de la vista, detrás del desplazamiento lateral. */
.adm-sub { display: block; }
/* La tabla no parte líneas (`.cl-tabla`); el motivo es una frase y sí. */
.adm-motivo { white-space: normal; min-width: 14rem; }
.adm-revisar-acciones .boton { display: flex; width: 100%; justify-content: center; }
.adm-revisar-acciones .boton + .boton { margin-top: var(--e2); }

/* Una cantidad de V-Bucks dentro de una celda o de una frase de la
   administración: el icono es `display: block`, así que necesita una caja en
   línea que lo ponga al lado de la cifra. */
.adm-vbucks {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  vertical-align: middle;
}

.adm-negativo { color: var(--peligro-alto); }
/* Las pestañas de Pagos automáticos: el segmento de siempre, sobre la tabla. */
.adm-pestanas { margin-bottom: var(--e4); }

/* --- La lista de monedas ------------------------------------------------ */

/* La misma `minmax(0, …)` de `.ck`: sin ella la columna mide lo que mide su
   contenido mínimo y una fila de moneda larga empuja la lista entera. */
.ck-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e2);
}

.ck-moneda {
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  min-width: 0;
}
.ck-moneda--abierta { border-color: var(--borde-alto); }
.ck-moneda--arrastrando { border-color: var(--acento-hondo); opacity: 0.85; }

/* `flex-wrap` y `min-width: 0`: la cabecera de una moneda son el asa, el
   código, la tasa, el estado y dos botones, y en 360 px eso no cabe en una
   línea. Sin envolver, la fila empujaba la tarjeta. */
.ck-moneda-alto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2);
  min-width: 0;
}
.ck-moneda-alto > * { min-width: 0; }
/* El botón que abre la moneda es lo que encoge: se lleva el hueco que quede en
   su línea y su resumen se recorta. Sin esto el asa quedaba sola en una línea,
   porque el bloque del código no cabía a su lado. */
.ck-moneda-alto .ck-moneda-abrir { flex: 1 1 0; }

.ck-asa {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px; min-height: 40px;
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto-tenue);
  cursor: grab;
  touch-action: none;
}
.ck-asa[hidden] { display: none; }
.ck-asa:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

/* El cuerpo de la fila. `min-width: 0` es lo que permite que el resumen se
   recorte en 360px en vez de empujar el interruptor fuera de la pantalla. */
.ck-moneda-abrir {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e1) var(--e3);
  min-height: 40px;
  padding: var(--e1) var(--e2);
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto);
  text-align: left;
  cursor: pointer;
}
.ck-moneda-abrir:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

/* El nombre de la moneda: "PEN", "USD (PayPal)". Un solo texto desde que dejó
   de llevar el símbolo delante —el símbolo es de los importes—, así que ya no
   hace falta la caja flexible que separaba las dos piezas. */
.ck-moneda-nombre { color: var(--texto-alto); }

.ck-moneda-resumen {
  flex: 1 1 8rem;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-tenue);
  font-size: 0.75rem;
}
/* Lo que le falta para valer, en ámbar. **El color no es el único canal**: la
   frase dice "Sin tasa" con todas las letras. */
.ck-moneda-resumen.ck-falta { color: var(--aviso-alto); }

.ck-chevron { flex: none; color: var(--texto-tenue); }
.ck-moneda--abierta .ck-chevron { transform: rotate(180deg); }

/* Subir y bajar. Van **en la cabecera de la moneda, junto al chevron**, y no en
   una fila propia debajo: son la alternativa de teclado al arrastre, no una
   acción de la moneda, y una fila para ellos empujaba todo el contenido hacia
   abajo en cada fila de la lista.

   `flex: none` para que no crezcan y no le quiten sitio al resumen, que es lo
   que se lee. */
.ck-mover {
  display: flex;
  flex: none;
  gap: var(--e1);
}
.ck-mover[hidden] { display: none; }
.ck-mover-boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px; min-height: 40px;
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto-suave);
  cursor: pointer;
}
.ck-mover-boton:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

.ck-moneda-detalle {
  display: grid;
  gap: var(--e4);
  padding: var(--e3);
  border-top: 1px solid var(--borde);
}
.ck-moneda-detalle[hidden] { display: none; }

/* Dos columnas donde caben y una donde no. El `minmax(0, 1fr)` y no `1fr` a
   secas: con `1fr` una columna no baja de su contenido y en 360px la rejilla
   se sale. */
.ck-rejilla {
  display: grid;
  gap: var(--e3);
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
}

.ck-fee {
  display: grid;
  gap: var(--e3);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie);
}
.ck-fee-detalle { display: grid; gap: var(--e3); }
.ck-fee-detalle[hidden] { display: none; }

/* El ejemplo en vivo. En la superficie de dentro de un control, con la cifra en
   crema: es el dato que el vendedor viene a mirar mientras teclea. */
.ck-ejemplo {
  margin: 0;
  padding: var(--e2) var(--e3);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-3);
  color: var(--texto-alto);
  font-size: 0.8125rem;
}

/* --- Tasa y cargos ------------------------------------------------------ */

/* El bloque de la tasa, con su propio Guardar dentro. **No es una tarjeta**:
   va dentro del detalle de una moneda, que ya es una, y otro escalón de
   superficie ahí dentro sería una caja dentro de una caja. Lo que lo separa de
   las cuentas es su título y el filo de abajo, que es lo que hace ver que el
   botón de dentro guarda lo de dentro y no lo de después. */
.ck-bloque {
  padding-bottom: var(--e5);
  border-bottom: 1px solid var(--borde);
  display: flex;
  flex-direction: column;
  gap: var(--e4);
}
/* Los tres pasos del diálogo de importar nacen escondidos —se enseña uno cada
   vez— y esta regla es la de siempre: su `display: flex` le ganaría al del
   navegador. La de raíz ya lo cubre; ésta es la red y lo documenta aquí. */
.ck-bloque[hidden] { display: none; }

.ck-bloque-titulo {
  margin: 0;
  font-size: 1rem;
}

/* Las acciones de un bloque, con su marca de «sin guardar» al lado. Centradas
   entre ellas para que la marca no baile respecto del botón. */
.ck-acciones--bloque { align-items: center; }

/* Lo escrito y sin mandar. En el ámbar de aviso —"al límite", que es lo que
   significa— y **el color no es el único canal**: lleva su icono y la frase
   escrita. No es un error y no va en rojo: un formulario a medio escribir es
   lo normal mientras se escribe. */
.ck-sin-guardar {
  display: flex;
  align-items: center;
  gap: var(--e1);
  margin: 0;
  color: var(--aviso-alto);
  font-size: 0.8125rem;
}
.ck-sin-guardar[hidden] { display: none; }
.ck-sin-guardar .icono { width: 18px; height: 18px; font-size: 18px; }

/* --- Las cuentas de pago ------------------------------------------------ */

.ck-cuentas { display: grid; gap: var(--e3); }

.ck-cuenta {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 40px;
  padding: var(--e1) var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie);
}
/* Apagada: se ve pero no manda. Se atenúa **y** el menú dice "Activar", así que
   la opacidad no es el único canal. */
/* **En los hijos y no en la fila**, como `.cuenta--dormida`: una opacidad en la
   fila la convierte en contexto de apilamiento, y su menú salía al 55% —con
   la fila de debajo transparentándose— y encerrado detrás de la siguiente.
   La caja del menú se queda fuera; su botón se atenúa como el resto. */
.ck-cuenta--apagada > :not(.ck-menu),
.ck-cuenta--apagada .ck-menu-boton { opacity: 0.55; }

/* Mientras se arrastra, el mismo filo que la moneda: es el mismo gesto sobre
   la lista de al lado y no tendría sentido que se señalara distinto. */
.ck-cuenta--arrastrando { border-color: var(--acento-hondo); opacity: 0.85; }


.ck-cuenta-icono { display: inline-flex; flex: none; color: var(--texto-tenue); }
.ck-cuenta-icono[hidden] { display: none; }

.ck-cuenta-texto {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
}
.ck-cuenta-quien {
  color: var(--texto);
  font-size: 0.8125rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ck-cuenta-dato {
  color: var(--texto-tenue);
  font-size: 0.75rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* El menú de una cuenta: `<details>` nativo, sin una línea de guion. */
.ck-menu { position: relative; flex: none; }
/* La fila con el menú abierto, por encima de las de después. Lo mismo que
   `.cuenta:has(.menu[open])`, ver allí. */
.ck-cuenta:has(> .ck-menu[open]) { position: relative; z-index: 2; }
.ck-menu--arriba > .ck-menu-caja { top: auto; bottom: calc(100% + var(--e1)); }
.ck-menu-boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px; min-height: 40px;
  border-radius: var(--r-menudo);
  color: var(--texto-suave);
  cursor: pointer;
  list-style: none;
}
.ck-menu-boton::-webkit-details-marker { display: none; }
.ck-menu-boton:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

.ck-menu-caja {
  position: absolute;
  right: 0;
  z-index: 3;
  display: grid;
  min-width: 11rem;
  padding: var(--e1);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.ck-menu-caja button {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 40px;
  padding: 0 var(--e2);
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ck-menu-caja button:hover { background-color: var(--superficie-3); }
.ck-menu-caja button:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: -3px; }
.ck-menu-caja .ck-menu-peligro { color: var(--peligro-alto); }

/* --- Redes y contacto, en el panel -----------------------------------------
   La fila es la de una cuenta de pago (mismo gesto, misma lista); lo único
   propio es el contador de aperturas y la marca del canal que no vale. */
.redes-lista { list-style: none; margin: 0; padding: 0; }
.red-aperturas { flex: none; }
.red-aperturas .cifras { color: var(--texto-alto); }
.red-fila--mal { border-color: var(--peligro); }

/* --- Canales ------------------------------------------------------------ */

.ck-lista--canales { gap: var(--e3); }

.ck-canal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
}
.ck-canal-nombre {
  flex: 0 0 6rem;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}
.ck-canal .ck-input { flex: 1 1 10rem; min-width: 0; }
.ck-canal .ck-error-linea { flex: 1 1 100%; }

/* --- Prefijo ------------------------------------------------------------ */

.ck-previa { margin: 0; color: var(--texto-suave); font-size: 0.8125rem; }
.ck-previa .mono { color: var(--texto-alto); }

/* El botón de una acción destructiva. **No existía**: el marcado lo pedía y no
   había regla detrás, así que «Eliminar moneda» salía igual que «Guardar» —el
   morado de acción, que es lo contrario de lo que hace—.

   Se tiñe con el mismo par que el resto de lo destructivo del panel
   (`.menu-lista .peligroso`): `--peligro` rellena y bordea, `--peligro-alto` es
   el que se lee. Sin el degradado del acento, que aquí no pinta nada: lo que
   define este botón es que **no** es la acción principal.

   `background-image: none` es obligatorio y no sobra: `.boton` trae
   `--grad-acento` y sin quitarlo el morado se vería por encima del relleno. */
.boton--peligro {
  background-color: color-mix(in srgb, var(--peligro) 18%, transparent);
  background-image: none;
  border-color: var(--peligro);
  color: var(--peligro-alto);
}
.boton--peligro:hover {
  background-color: color-mix(in srgb, var(--peligro) 30%, transparent);
  border-color: var(--peligro-alto);
  color: var(--peligro-alto);
}

/* El «Eliminar» del menú de una cuenta de pago: el mismo par de tokens, con la
   forma del menú del panel en vez de la del botón. */
.ck-menu-caja .ck-menu-peligro:hover {
  background-color: color-mix(in srgb, var(--peligro) 20%, transparent);
  color: var(--peligro-alto);
}

/* --- Los diálogos ------------------------------------------------------- */

.ck-dialogo { gap: var(--e3); }
.ck-dialogo-alto {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--e3);
}
.ck-dialogo-alto h2 { margin: 0; }

.ck-descartadas { display: flex; flex-wrap: wrap; gap: var(--e2); }
.ck-descartada {
  min-height: 40px;
  padding: 0 var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  color: var(--texto);
  font: inherit;
  cursor: pointer;
}
.ck-descartada:hover { border-color: var(--acento-hondo); color: var(--acento-alto); }
.ck-descartada:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

.ck-qr-previa { margin: 0; display: grid; gap: var(--e1); justify-items: start; }
.ck-qr-previa[hidden] { display: none; }
.ck-qr-previa img {
  max-width: 160px;
  max-height: 160px;
  border-radius: var(--r-menudo);
  background-color: var(--superficie-3);
}

@media (prefers-reduced-motion: reduce) {
  .interruptor-pista, .interruptor-pista::after { transition: none; }
}

/* ==========================================================================
   El modal de recargar balance, en la tienda del cliente (bloques 3 y 3b)
   ==========================================================================

   Todo por tokens, **sin un solo hex**. Aquí eso importa más que en el panel:
   la tienda pública redefine los mismos tokens bajo `[data-tema="claro"]` y con
   el acento que elija el vendedor, así que un color escrito a mano sería el
   único que no cambiaría con el tema ni con la marca.

   **Cabe en 360 px**: las dos columnas caen a una, el dato de una cuenta se
   recorta en vez de empujar, y el modal ocupa el alto con `overflow-y` propio.
   Todo lo que se toca mide 40 px. */

/* El fondo tenue del primario del vendedor y el brillo que lo cruza. Es CSS
   puro: `overflow: hidden` y un pseudo-elemento que se mueve con `transform`,
   que es de lo barato de animar (CLAUDE.md, Microinteracciones).

   **No cambia lo que el botón significa ni su `aria-label`**: es una pista de
   «se puede pulsar», no un estado. Y lo lleva éste y no el de canjear porque
   aquél ya destaca por el color: dos cosas brillando al lado se anulan. */
.fn-cartera {
  flex: none;
  position: relative;
  overflow: hidden;
  background-color: color-mix(in srgb, var(--acento) 12%, transparent);
}
/* **El mismo brillo cruza la opción «Crypto Automatic» del modal de
   recargar**, y es la misma regla y la misma animación, no una copia: son la
   misma pista —«por aquí se recarga»— en los dos sitios. El chip lleva el
   `position` y el `overflow` en su propia regla, más abajo. */
.fn-cartera::after,
.fn-rec-chip--oro::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 40%;
  /* Empieza fuera por la izquierda y acaba fuera por la derecha. */
  left: -60%;
  background: linear-gradient(
    100deg,
    transparent,
    color-mix(in srgb, var(--acento-alto) 45%, transparent),
    transparent
  );
  animation: fn-brillo 3s var(--curva) infinite;
  /* En su propia capa, para que el barrido lo mueva el compositor y no
     recalcule estilos en cada fotograma. */
  will-change: transform;
  pointer-events: none;
}
/* **Quieto fuera de pantalla.** `fn-fuera` lo pone el guion de `/fn/` con un
   `IntersectionObserver`; sin JavaScript se queda como siempre. */
.fn-cartera.fn-fuera::after { animation-play-state: paused; }

@keyframes fn-brillo {
  /* El 35% del ciclo es el barrido y el resto es la espera: un brillo que no
     para es un anuncio, y esto es una pista. */
  0%   { transform: translateX(0); }
  35%  { transform: translateX(420%); }
  100% { transform: translateX(420%); }
}

/* En móvil se queda el icono. El texto **no se quita con `display: none`**: eso
   lo saca del árbol de accesibilidad y el botón se quedaría sin nombre — se le
   aplican las mismas reglas que a `.sr-solo`. */
@media (max-width: 700px) {
  .fn-cartera-texto {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.fn-recarga-velo[hidden] { display: none; }
.fn-sin-scroll { overflow: hidden; }

/* **Crece con la ventana, con tope.** Aquí el cliente mete dinero: el importe,
   la cuadrícula de bancos y el comprobante se miran de frente, y en un monitor
   se veía igual de estrecho que en un móvil.

   `max-width: none` no es opcional: `.fn-dialogo` clava un `max-width: 420px`,
   y un `max-width` le gana siempre a un `width`, así que sin deshacerlo la
   caja se quedaba en 420 dijera lo que dijera la línea de abajo. Era lo que
   pasaba: los 460 de antes no llegaron a verse nunca.

   El tope son 40rem. Por encima, la columna de un formulario de dos campos
   deja de leerse como una columna. */
.fn-recarga {
  width: min(40rem, calc(100% - 2 * var(--e4)));
  max-width: none;
  max-height: calc(100% - 2 * var(--e4));
  overflow-y: auto;
  display: grid;
  gap: var(--e3);
  align-content: start;
}

.fn-recarga-alto {
  display: flex;
  align-items: center;
  gap: var(--e2);
}
.fn-recarga-alto h2 { flex: 1 1 auto; margin: 0; min-width: 0; }

/* El título con su cartera delante. `inline-flex` y no `flex` porque el icono
   va **dentro** del `<h2>`: lo que se alinea es el icono con la línea de texto,
   no el título con la caja. */
.fn-recarga-titulo { display: flex; align-items: center; gap: var(--e2); }
.fn-recarga-titulo-icono { display: inline-flex; flex: none; color: var(--acento); }
.fn-recarga-titulo-icono[hidden] { display: none; }

.fn-recarga-volver {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px; min-height: 40px;
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--texto-suave);
  cursor: pointer;
}
.fn-recarga-volver[hidden] { display: none; }

.fn-recarga-sub { margin: 0; }
.fn-recarga-sub[hidden] { display: none; }

/* La barra de pasos. `aria-hidden` no: cada uno lleva su `sr-solo`, porque un
   lector de pantalla tiene que poder decir por dónde va. */
.fn-pasos {
  display: flex;
  gap: var(--e1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.fn-pasos[hidden] { display: none; }
.fn-pasos li {
  flex: 1 1 0;
  height: 4px;
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-3);
}
.fn-pasos .fn-paso--hecho { background-color: var(--acento); }

.fn-rec-error { margin: 0; }
.fn-rec-error[hidden] { display: none; }

.fn-rec-pantalla { display: grid; gap: var(--e3); }
.fn-rec-pantalla[hidden] { display: none; }

/* --- A: las dos tarjetas ------------------------------------------------ */

/* **Tocar una ya es responder.** No hay un "siguiente" detrás: en un móvil, con
   el pago a medio hacer, cada paso de más es uno donde alguien se cae. */
.fn-rec-tarjetas {
  display: grid;
  gap: var(--e3);
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
}
.fn-rec-opcion {
  display: grid;
  gap: var(--e1);
  min-height: 84px;
  padding: var(--e4);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie-2);
  color: var(--texto);
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.fn-rec-opcion:hover { border-color: var(--acento); }
.fn-rec-opcion:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.fn-rec-opcion-titulo { color: var(--texto-alto); font-weight: 600; }
.fn-rec-opcion-sub { color: var(--texto-tenue); font-size: 0.8125rem; }

/* El icono en un círculo del acento del vendedor. Es lo que hace que las dos
   tarjetas se distingan **antes de leerlas**: una lleva un usuario con un visto
   y la otra uno con un más, y en un móvil eso se ve de un vistazo y el subtítulo
   no. El color nunca es el único canal — el título lo dice con palabras. */
.fn-rec-circulo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: var(--r-pastilla);
  background-color: color-mix(in srgb, var(--acento) 18%, transparent);
  color: var(--acento);
}

/* Un enlace que es un botón: no navega a ninguna parte, cambia de pantalla. */
.fn-rec-enlace {
  min-height: 40px;
  padding: 0 var(--e2);
  border: 0;
  background: none;
  color: var(--acento);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.fn-rec-enlace:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
/* El de «Ver estado de un pago», que lleva su recibo delante. */
.fn-rec-enlace--icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
}

/* --- Campos ------------------------------------------------------------- */

.fn-rec-campo { display: grid; gap: var(--e1); min-width: 0; }
.fn-rec-rotulo { color: var(--texto-tenue); font-size: 0.75rem; }

/* La pista de un campo. **El error va en el token de error y con su icono**: en
   el 3b era sólo un cambio de color, y un color no dice "esto está mal" a quien
   no lo distingue. El icono sólo sale en el estado malo — el bueno («Disponible»,
   «Cliente encontrado») no necesita que nadie lo mire dos veces. */
.fn-rec-pista {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  font-size: 0.8125rem;
  color: var(--texto-suave);
}
.fn-rec-pista[hidden] { display: none; }
.fn-rec-pista--ok { color: var(--ok-alto); }
.fn-rec-pista--mal { color: var(--peligro-alto); }

/* El sí explícito del cambio de PIN. Es una fila y no un bloque: la casilla y
   su frase son una sola cosa, y con la frase debajo parecerían dos.

   La casilla se queda en el control nativo a propósito — es lo que el teclado
   y el lector de pantalla ya saben manejar—, y el `accent-color` la pinta del
   acento sin tener que dibujarla. */
.fn-rec-confirma {
  display: flex;
  align-items: center;
  gap: var(--e2);
  cursor: pointer;
  color: var(--texto);
  font-size: 0.875rem;
}
.fn-rec-confirma input {
  flex: none;
  width: 18px;
  height: 18px;
  accent-color: var(--acento-hondo);
  cursor: pointer;
}
/* Su línea propia, por la regla de siempre: el error del comprobante nace con
   esta clase **y con `hidden`** en el marcado, así que la clase tiene que
   esconderse por sí misma y no de rebote por `.fn-rec-pista`. */
.fn-rec-pista--mal[hidden] { display: none; }
.fn-rec-pista-icono { display: none; flex: none; }
.fn-rec-pista--mal .fn-rec-pista-icono { display: inline-flex; }
.fn-rec-pista-icono .icono { font-size: 18px; width: 18px; height: 18px; }

/* La línea de debajo del campo: dice algo que ayuda, no corrige nada. */
.fn-rec-nota { margin: 0; font-size: 0.75rem; color: var(--texto-tenue); }
.fn-rec-nota[hidden] { display: none; }

/* En ámbar: pide atención y no es un error. El texto lo dice con palabras, así
   que el color acompaña en vez de ser el único canal. */
.fn-rec-ambar {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--aviso);
  border-radius: var(--r-menudo);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--texto);
  font-size: 0.8125rem;
}
.fn-rec-ambar[hidden] { display: none; }
.fn-rec-ambar .icono { flex: none; color: var(--aviso-alto); }

.fn-rec-seguir, .fn-rec-enviar { min-height: 44px; width: 100%; }

/* El usuario puede ser un correo de 40 caracteres sin un solo espacio, así que
   parte por donde haga falta antes que sacar el modal por el lado en 360 px. */
.fn-rec-quien {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}
.fn-rec-quien b { color: var(--texto-alto); }
.fn-rec-separador { color: var(--texto-tenue); }
.fn-rec-separador[hidden] { display: none; }

/* --- Elegir cómo pagar ------------------------------------------------- */

/* **Primero cómo pagar, después cuánto.** Sin forma de pago la pestaña es esto
   y nada más; al elegir, se pliega en `.fn-rec-forma` y salen los campos. */
.fn-rec-formas { display: grid; gap: var(--e3); }
.fn-rec-formas[hidden] { display: none; }
.fn-rec-autos-caja { display: grid; gap: var(--e2); }
.fn-rec-autos-caja[hidden] { display: none; }
.fn-rec-montos { display: grid; gap: var(--e3); }
.fn-rec-montos[hidden] { display: none; }

.fn-rec-titulo3 {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.875rem;
  font-weight: 600;
}

/* Las monedas de transferencia: **cuatro columnas en todos los anchos** y el
   código a secas. En 360 px un cuarto son unos 64 px, y un código de tres
   letras cabe con aire; lo que no quepa —un código largo del vendedor— se
   recorta, y el `title` lleva el nombre entero. */
.fn-rec-monedas {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--e2);
}
.fn-rec-monedas[hidden] { display: none; }
.fn-rec-chip {
  min-height: 44px;
  min-width: 0;
  padding: 0 var(--e1);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto-alto);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--t-rapida) var(--curva);
}
.fn-rec-chip:hover { border-color: var(--acento); }
.fn-rec-chip:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
/* Las recargas automáticas: **el oro de la tienda**, porque mueven dinero de
   verdad (CLAUDE.md, "El oro de la tienda", la cuarta pieza). Rellena y
   bordea; no se lee: llevan el filo de oro y el texto de siempre. El foco
   sigue siendo el del acento, como en el botón de canjear. */
.fn-rec-chip--oro {
  position: relative;   /* para el brillo de la cartera, que lo cruza */
  overflow: hidden;
  border-color: var(--canje-oro);
}
.fn-rec-chip--oro:hover { border-color: var(--canje-oro); }
/* Una por fila, con su icono, su nombre y «al instante» a la derecha. */
.fn-rec-autos { display: grid; gap: var(--e2); }
.fn-rec-chip--fila {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 48px;
  padding: 0 var(--e3);
  text-align: left;
}
.fn-rec-auto-icono { display: inline-flex; flex: none; color: var(--acento); }
.fn-rec-auto-icono [data-icono] { display: inline-flex; }
.fn-rec-auto-icono [data-icono][hidden] { display: none; }
.fn-rec-auto-nombre {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fn-rec-auto-sub {
  flex: none;
  color: var(--texto-suave);
  font-size: 0.75rem;
  font-weight: 400;
}

/* La forma elegida, plegada: lo mismo que la barra del banco elegido —filo y
   fondo del acento— con el rótulo «Pagas con» encima del nombre. */
.fn-rec-forma {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 56px;
  min-width: 0;
  padding: var(--e2) var(--e2) var(--e2) var(--e3);
  border: 1px solid var(--acento);
  border-radius: var(--r-control);
  background-color: var(--acento-tenue);
}
.fn-rec-forma[hidden] { display: none; }
.fn-rec-forma-icono { display: inline-flex; flex: none; color: var(--acento-alto); }
.fn-rec-forma-icono[hidden] { display: none; }
.fn-rec-forma-icono [data-icono] { display: inline-flex; }
.fn-rec-forma-icono [data-icono][hidden] { display: none; }
.fn-rec-forma-texto {
  display: grid;
  flex: 1 1 auto;
  min-width: 0;
}
.fn-rec-forma-rotulo { color: var(--texto-suave); font-size: 0.75rem; }
/* El nombre parte línea antes que recortarse: en 360 px «PayPal Automatic»
   no cabe en una al lado de «Cambiar», y cortado ya no dice cuál es. */
.fn-rec-forma-nombre {
  overflow-wrap: anywhere;
  color: var(--texto-alto);
  font-weight: 600;
}
.fn-rec-forma-sub { color: var(--texto); font-size: 0.75rem; }
.fn-rec-forma-sub[hidden] { display: none; }
.fn-rec-forma-cambiar {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  flex: none;
  min-height: 40px;
  padding: 0 var(--e1);
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--acento-alto);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}
.fn-rec-forma-cambiar[hidden] { display: none; }
.fn-rec-forma-cambiar:hover { text-decoration: underline; }
.fn-rec-forma-cambiar:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* --- Los dos campos atados --------------------------------------------- */

.fn-rec-par {
  display: grid;
  gap: var(--e3);
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
}

/* --- La tarjeta dorada -------------------------------------------------- */

/* La tarjeta del resumen: lo que recibe y lo que paga, uno debajo del otro.
   **Va con el acento del vendedor y no con el oro de los V-Bucks.**

   El oro es un token de marca del panel, y la tienda **no tiene más color que
   el que elige su dueño**: eso ya está decidido, tiene test
   (`test_ninguna_regla_de_la_tienda_pinta_con_un_color_de_la_casa`) y tres
   incidentes detrás —el degradado gris del diálogo, el halo ámbar de la
   cabecera y el de las tarjetas de Cuenta—, los tres descubiertos mirando la
   página en el tema claro.

   El icono de V-Bucks sigue estando y sigue siendo el autoalojado; lo que hace
   es lo mismo que en el resto de la tienda: teñirse con `currentColor`. Así la
   pieza es de la marca del vendedor y la moneda se reconoce igual. */
.fn-rec-resumen {
  display: grid;
  gap: var(--e2);
  padding: var(--e4);
  border: 1px solid var(--acento);
  border-radius: var(--r-tarjeta);
  background-image: linear-gradient(163deg,
    color-mix(in srgb, var(--acento) 22%, transparent) 0%,
    color-mix(in srgb, var(--acento) 8%, transparent) 100%);
  transition: opacity var(--t-media) var(--curva);
}
/* Con los campos vacíos o en cero, atenuada y con rayas: no hay nada que decir
   todavía, y un cero grande se lee como un importe de cero. */
.fn-rec-resumen--vacio { opacity: 0.5; }

.fn-rec-resumen-fila {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e3);
  margin: 0;
}
.fn-rec-resumen-rotulo {
  color: var(--texto-tenue);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
}
/* **En la fuente de rótulo y un escalón más.** Es lo que el cliente viene a
   mirar mientras teclea, y en el móvil se perdía entre dos campos y un aviso.

   Righteous **no tiene cifras tabulares** —no lleva `tnum` y sus dígitos son
   proporcionales—, así que un `1` ocupa menos que un `8` y la cifra cambia de
   ancho al escribir. No se compensa con `tabular-nums`, que ahí no haría nada:
   lo que sujeta la pieza es que la fila va con `space-between` y la cifra crece
   hacia la izquierda, con el borde derecho quieto. Es una decisión, y por eso
   `test_righteous_es_solo_de_rotulo` la nombra en vez de fallar. */
.fn-rec-resumen-valor {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  font-family: 'Righteous', 'IBM Plex Sans', system-ui, sans-serif;
  font-weight: 400;
  color: var(--texto-alto);
  font-size: 1.5rem;
  letter-spacing: 0;
}

.fn-rec-cargo, .fn-rec-minimo {
  margin: 0;
  color: var(--texto-tenue);
  font-size: 0.75rem;
}
.fn-rec-cargo[hidden], .fn-rec-minimo[hidden] { display: none; }
.fn-rec-minimo { color: var(--aviso-alto); }

/* Los tres diálogos de la pestaña Cuenta: usuario, PIN y transferir.
   Reusan el velo y la tarjeta de los demás diálogos de la tienda; lo único
   suyo es el ancho, que es el de un formulario corto y no el del modal de
   recarga. */
.fn-cuenta-velo[hidden] { display: none; }
.fn-cuenta-velo .fn-dialogo {
  width: min(26rem, calc(100% - 2 * var(--e4)));
  display: flex;
  flex-direction: column;
  gap: var(--e3);
}

/* El pie de la tarjeta de la Suerte: el botón ocupa la fila entera, es la
   única acción de su tarjeta. */
.fn-cuenta-pie .boton,
.fn-cuenta-pie .fn-canjear { width: 100%; justify-content: center; }



/* --- La Suerte del día ---------------------------------------------------

   Las dos ruletas, debajo de las cuatro tarjetas de la cuenta. Es una sección
   de premio: **poco texto, la cara del artículo grande y un botón**.

   El oro es `--canje-oro`, el mismo del botón de canjear un código y del de
   enviar V-Bucks, y por el mismo motivo: aquí se mueven V-Bucks de verdad y la
   acción tiene que reconocerse igual en las treinta tiendas en vez de teñirse
   del acento de cada vendedor. **Rellena y bordea; no se lee** — el porcentaje
   y las cifras van en el texto del tema, nunca en oro (CLAUDE.md, «El oro de
   la tienda»). Su alcance lo fija
   `test_el_oro_del_canje_es_una_excepcion_acotada`.

   **Sin color propio**: todo lo demás sale del tema del vendedor, que es la
   regla de la tienda entera. */
/* **Una sola tarjeta**, la de la ruleta de hoy: del ancho de las de Cuenta,
   que es la columna en la que vive. */
.fn-suerte-fichas {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e4);
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
}

/* «El viernes hay superdescuento de −6%: faltan 1 d 3 h», debajo de la
   tarjeta. Es una nota y no un aviso: en el texto suave del tema, con la
   estrella del viernes delante. Sin caja: va sobre la tarjeta de la sección. */
.fn-suerte-viernes {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  max-width: 560px;
  margin: var(--e3) auto 0;
  font-size: 0.9375rem;
  text-align: center;
}
.fn-suerte-viernes .icono { flex: none; width: 18px; height: 18px; }

/* --- La franja de la Suerte, debajo del buscador ---------------------------
   **La misma forma que la del código de regalo** (`.fn-franja-codigo`): el
   tinte y el filo del acento del vendedor, y el texto del tema encima. **No
   lleva el oro**: el botón lleva a la ruleta y no mueve V-Bucks, y el oro está
   acotado a lo que sí (CLAUDE.md, «El oro de la tienda»). */
.fn-suerte-franja {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2) var(--e3);
  margin: 0 0 var(--e3);
  padding: var(--e3) var(--e4);
  border: 1px solid color-mix(in srgb, var(--acento) 45%, transparent);
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--acento) 10%, transparent);
  color: var(--texto);
  font-size: 0.9375rem;
}
.fn-suerte-franja > .icono { flex: none; color: var(--acento); }
.fn-suerte-franja > span { flex: 1 1 14rem; min-width: 0; }
.fn-suerte-franja .cifras { color: var(--texto-alto); font-weight: 600; }
.fn-suerte-franja-boton { flex: none; }

/* La tarjeta, con la misma forma que las cuatro de arriba: el dato en medio y
   el pie abajo del todo. Comparte `.fn-cuenta-pie`, que ya estira su botón. */
.fn-suerte-tarjeta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e2);
  padding: var(--e6) var(--e5);
  text-align: center;
}

/* **El filo de oro sólo en las dos que ofrecen algo**: la que se puede girar y
   la que ya tiene premio. La bloqueada y la que espera se quedan como sus
   vecinas — un filo dorado en una tarjeta sin botón promete algo que no hay. */
.fn-suerte-tarjeta--girar,
.fn-suerte-tarjeta--tocado { border-color: var(--canje-oro); }

.fn-suerte-rotulo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--e2);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.fn-suerte-rotulo .icono { width: 18px; height: 18px; }

/* El porcentaje y su frase. **La cifra destacada y la frase en el cuerpo**: es
   la misma regla que la tarjeta del vencimiento —"34 días" con el "días" a
   28px es un rótulo y no una frase—. */
.fn-suerte-promesa {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.9375rem;
}
.fn-suerte-premio {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e1);
  margin: 0;
}
/* El porcentaje, **en pastilla y junto al nombre de la ruleta**. Estaba suelto
   y en grande encima del premio, y ahí competía con el nombre del item: son
   dos datos y el que se mira es el segundo.

   Es el oro de la sección **rellenando**, con la cifra encima en el color que
   calcula el servidor contra ese fondo — la misma pareja que el botón de
   canjear. Aquí el oro no se lee: se lee lo de encima. */
.fn-suerte-pct {
  flex: none;
  padding: 1px var(--e2);
  border-radius: var(--r-pastilla);
  background-color: var(--canje-oro);
  color: var(--canje-oro-texto);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  text-transform: none;
}
.fn-suerte-premio-nombre {
  margin: 0;
  color: var(--texto-alto);
  font-size: 1.0625rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Las cuatro casillas. Una rejilla de cuatro y no un carrusel: caben en una
   fila de móvil a 380 y el premio se decide entre lo que se ve, no entre lo
   que hay que buscar desplazando. */
.fn-suerte-ruleta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--e2);
  width: 100%;
  margin: var(--e2) 0 0;
  padding: 0;
  list-style: none;
}
/* La casilla es una tarjeta de oferta en pequeño: la lámina a sangre arriba y
   el bloque de texto debajo, con su velo. **El borde se declara aquí y el
   color lo pone la regla compartida de más arriba**: sin `border-style` no
   habría filo que teñir.

   `padding: 0` y `overflow: hidden` para que la lámina llegue a los cuatro
   lados —el degradado de la oferta es lo que hay que ver— y el aire viva
   dentro del bloque de texto, que es donde lo necesita. */
.fn-suerte-casilla {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  overflow: hidden;
  transition: transform var(--t-rapida), background-color var(--t-rapida);
}
/* El bloque de texto. Cuando la casilla lleva color, la regla compartida le
   pone encima el velo del tema y el color que calculó el servidor. */
.fn-suerte-casilla-datos {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  padding: var(--e1);
}
/* El nombre debajo de la miniatura, en una línea y recortado: cuatro nombres
   de dos renglones convierten la ruleta en un párrafo. */

.fn-suerte-casilla-nombre {
  width: 100%;
  overflow: hidden;
  color: var(--texto-tenue);
  font-size: 0.6875rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fn-suerte-casilla .fn-lamina { width: 100%; border-radius: 0; }

/* La casilla por la que va pasando la ruleta, y la que se queda al parar. Las
   pone el guion; **sin él no se marca ninguna** y la página vuelve del POST
   con el resultado ya pintado en la tarjeta. */
/* **La marca de la ruleta es filo y salto, no fondo — y ya no puede ser
   fondo.** Lo era, y desde que la casilla lleva el degradado de su oferta
   cualquier `background-color` queda **debajo de una imagen opaca**: la
   animación seguía corriendo y no se veía nada. Era ésta la que se rompió al
   dar color a las casillas, y no se vio porque no falla — deja de notarse.

   El filo y el desplazamiento se ven sobre cualquier degradado de Epic, que es
   lo que hace falta aquí: son trescientos fondos distintos y ninguno se puede
   medir de antemano.

   El de la que va pasando sale **del propio texto calculado** cuando la
   casilla va teñida —el mismo recurso que el borde de una tarjeta de oferta—,
   y del borde del tema cuando no. El de la ganadora es el oro de la sección y
   va más grueso: es el resultado, no el paseo. */
.fn-suerte-casilla--pasa,
.fn-suerte-casilla--toca { transform: translateY(-2px); }

.fn-suerte-casilla--pasa {
  outline: 2px solid var(--borde-alto);
  outline-offset: -2px;
}
.fn-suerte-casilla--color.fn-suerte-casilla--pasa {
  outline-color: color-mix(in srgb, var(--tarj-texto) 55%, transparent);
}
/* El filo de la casilla ganadora **con `outline` y no con `box-shadow`**: la
   hoja no admite sombras (CLAUDE.md, Profundidad) y aquí tampoco hace falta
   ninguna — lo que hay que hacer es señalar la casilla, no levantarla. */
.fn-suerte-casilla--toca {
  outline: 3px solid var(--canje-oro);
  outline-offset: -3px;
}

/* La cara del premio, grande. Es lo que se mira. */
.fn-suerte-lamina {
  width: 100%;
  max-width: 180px;
  border-radius: var(--r-menudo);
}
/* Lo que ya no está, en gris: **es la mitad de lo que dice la tarjeta**. La
   palabra sigue debajo —el color nunca es el único canal— y el botón de
   comprar no existe en ese estado. */
.fn-suerte-lamina--ida { filter: grayscale(1); opacity: 0.55; }

/* El precio tachado y el nuevo, uno al lado del otro. El tachado en el texto
   de apoyo y más pequeño: lo que hay que leer es lo que se va a pagar. */

/* --- Los dos precios: una pieza para las cinco que los pintan ------------

   Las cuatro casillas, la tarjeta del premio, la de «Enviado», el recuadro del
   diálogo y el botón que cobra enseñan **el mismo par**, así que es una regla
   y no cinco: son el mismo dato y tienen que leerse igual, o el cliente ve
   tres cifras distintas de camino a pagar una.

   **Apilados.** Arriba el de tienda, tachado y pequeño; debajo el que se paga,
   en grande. Estaban uno al lado del otro y ahí los dos pesaban lo mismo: hay
   que poder leer lo que cuesta sin detenerse a ver cuál de los dos es. */
.fn-suerte-par {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
}
.fn-suerte-antes {
  color: var(--texto-tenue);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}
/* El que se paga, y **el brillo dorado por debajo**.

   Es el oro de la tienda haciendo lo único que sabe hacer: **rellenar, no
   leerse**. La cifra va en el color del texto —ningún oro llega a AA como
   texto sobre el blanco del tema claro, y está medido— y lo que va en oro es
   una barra que se desvanece por los dos lados, debajo del número.

   **Es un degradado y no una sombra**, y la diferencia importa: la hoja no
   admite `box-shadow` (CLAUDE.md, Profundidad) y esto no eleva nada. Un
   `filter: blur` habría sido una sombra con otro nombre. */
.fn-suerte-ahora {
  position: relative;
  padding-bottom: 5px;
  color: var(--texto-alto);
  font-size: 1.25rem;
  font-weight: 600;
}
.fn-suerte-ahora::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 4px;
  border-radius: var(--r-pastilla);
  background-image: linear-gradient(
    90deg, transparent, var(--canje-oro), transparent
  );
}

/* En una miniatura de cuatro por fila no cabe el escalón grande. */
.fn-suerte-par--menuda .fn-suerte-antes { font-size: 0.625rem; }
.fn-suerte-par--menuda .fn-suerte-ahora {
  padding-bottom: 4px;
  font-size: 0.8125rem;
}
.fn-suerte-par--menuda .fn-suerte-ahora .vbuck { width: 12px; height: 12px; }

/* Y dentro del botón, que ya es de oro: el brillo sobraría —oro sobre oro no
   se ve— y las dos cifras van en el color que el servidor calculó para ese
   fondo. */
.fn-suerte-par--grande .fn-suerte-ahora::after { content: none; }
.fn-suerte-par--grande .fn-suerte-antes,
.fn-suerte-par--grande .fn-suerte-ahora { color: inherit; }
.fn-suerte-par--grande .fn-suerte-antes { opacity: 0.75; }

/* La hora hasta la que vale, y la de volver a girar. Segundo dato de la
   tarjeta: más pequeño que el premio y más grande que la nota, igual que la
   fecha de la tarjeta del vencimiento. */
.fn-suerte-hasta {
  margin: 0;
  color: var(--texto);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}

/* El aviso de rotación, uno para las dos y encima de las tarjetas. Es una nota
   y no un `.aviso`: no ha pasado nada malo, es un dato para decidir. */
.fn-suerte-rota {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  margin: 0;
  text-align: center;
}
.fn-suerte-rota .icono { flex: none; width: 18px; height: 18px; }

.fn-suerte-error { margin: 0; }

/* El fallo del envío, **dentro del diálogo y no en la cabecera de la página**:
   ahí quedaba detrás del propio modal que el cliente tiene abierto. Va entre
   el selector y el botón, que es donde estaba mirando. */
.fn-suerte-fallo { margin: 0; }
.fn-suerte-fallo[hidden] { display: none; }

/* --- El premio ya enviado ------------------------------------------------
   La misma tarjeta, con la marca de que ya está hecho donde iba el
   porcentaje. **En verde de estado y no en el oro de la sección**: el oro dice
   «aquí se mueven V-Bucks» y esto ya no ofrece nada — es la confirmación de lo
   que pasó. El filo de la tarjeta tampoco va en oro, por lo mismo. */
.fn-suerte-enviado-marca {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--ok-alto);
  font-size: 0.9375rem;
  font-weight: 600;
}
.fn-suerte-enviado-marca .icono { width: 18px; height: 18px; }

/* --- El diálogo de comprar lo que tocó -----------------------------------
   Los dos precios dentro del recuadro del artículo, y **de qué ruleta sale la
   rebaja encima, como rótulo del bloque**.

   Estaba dentro del recuadro, al lado del precio, y en un móvil estrecho ese
   recuadro reparte su ancho entre la lámina, el nombre, los dos precios y la
   etiqueta: se caía a la derecha de la cifra y se leía como si fuera parte de
   ella. No lo es — es de qué ruleta viene el descuento, y eso encabeza lo de
   debajo.

   **Reusa las dos piezas de la tarjeta** (`.fn-suerte-rotulo` y su pastilla
   dorada) en vez de tener su propia etiqueta: es lo que hace que el diálogo y
   la tarjeta de la que sale se reconozcan como lo mismo. Lo único suyo es
   quedarse a la izquierda —aquí no hay tarjeta que centrar— y el aire de
   debajo. */
.fn-suerte-rotulo--dialogo {
  justify-content: flex-start;
  margin-bottom: var(--e3);
}

/* El botón que cobra: la píldora de oro de la sección, a lo ancho del
   formulario y con la cifra dentro. **Dice lo que se va a cobrar** y no
   «Comprar»: es la última cifra que el cliente ve antes del cargo. */
.fn-suerte-enviar {
  width: 100%;
  margin-top: var(--e3);
  min-height: 40px;
}
.fn-suerte-enviar .vbuck { width: 16px; height: 16px; }
.fn-suerte-enviar[disabled] { opacity: 0.5; cursor: not-allowed; }

/* El botón de girar y el de comprar comparten la píldora de oro de la tienda
   (`.fn-canjear`), y el pie de la tarjeta ya los estira a la fila entera. Lo
   único suyo es que el de comprar es un enlace y no un botón, así que hay que
   quitarle el subrayado que trae `a` de la casa. */
.fn-suerte-comprar { text-decoration: none; }

/* --- «Cambiar ítems» y «Volver a sortear» ---------------------------------

   En el pie, debajo de la píldora de girar o de enviar: el pie pasa a ser una
   columna, porque ahora lleva una acción principal y una secundaria. */
.fn-suerte-tarjeta .fn-cuenta-pie {
  flex-direction: column;
  align-items: stretch;
  gap: var(--e2);
}
.fn-suerte-tarjeta .fn-cuenta-pie .fn-cuenta-nota { margin: 0; text-align: center; }

/* **Secundario y con el filo de oro.** Mueve V-Bucks de verdad, así que va en
   el oro de la tienda (CLAUDE.md, «El oro de la tienda»), pero es la acción
   de segunda: **bordea y no rellena**, y el texto va en el color del tema,
   porque ningún oro se lee como texto sobre el blanco del tema claro. */
.fn-suerte-extra {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  min-height: 34px;
  padding: 0 var(--e4);
  border: 1px solid var(--canje-oro);
  border-radius: var(--r-pastilla);
  background-color: transparent;
  color: var(--texto-alto);
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
  transition: transform var(--t-rapida), background-color var(--t-rapida);
}
.fn-suerte-extra:hover {
  transform: translateY(-1px);
  background-color: var(--superficie-3);
}
.fn-suerte-extra:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }

/* La cifra de lo que cuesta, en línea con su frase o su botón. */
.fn-suerte-cuesta { display: inline-flex; align-items: center; gap: var(--e1); }
.fn-suerte-cuesta .vbuck { width: 16px; height: 16px; }

/* Tras girar: las cuatro, con la ganadora en el filo de oro (`--toca`) y las
   demás apagadas. **Se apaga la lámina y no la casilla entera**: con la
   opacidad en todo, el nombre de debajo bajaba con ella y en el tema claro se
   quedaba por debajo de AA. El texto conserva el color que calcula el
   servidor contra su velo; lo que se apaga es el dibujo, y el filo de oro de
   la ganadora hace el resto. Lo que ya salió además va tachado, que es el
   canal que no depende de ver bien el contraste. */
.fn-suerte-casilla--apagada .fn-lamina { opacity: 0.4; }
.fn-suerte-casilla--salio .fn-suerte-casilla-nombre { text-decoration: line-through; }
.fn-suerte-casilla--apagada.fn-suerte-casilla--pasa .fn-lamina { opacity: 1; }

/* El diálogo de confirmar. Poco texto: qué pierde, cuánto paga y el botón. */
.fn-suerte-confirma-pierde,
.fn-suerte-confirma-paga,
.fn-suerte-falta { margin: 0; }
.fn-suerte-confirma-paga,
.fn-suerte-falta { display: flex; align-items: center; gap: var(--e2); font-weight: 600; }
.fn-suerte-confirmar { width: 100%; min-height: 40px; }
.fn-suerte-confirmar[disabled] { opacity: 0.5; cursor: not-allowed; }

/* **El giro se apaga entero con `prefers-reduced-motion`.** El resultado sale
   directo, que es lo que pide la guía: se va el movimiento y se queda lo que
   informa. */
@media (prefers-reduced-motion: reduce) {
  .fn-suerte-casilla { transition: none; }
  .fn-suerte-casilla--pasa,
  .fn-suerte-casilla--toca { transform: none; }
  .fn-suerte-extra { transition: none; }
  .fn-suerte-extra:hover { transform: none; }
}

/* --- El cupón dentro del carrito ---------------------------------------- */

/* El precio de tienda tachado, delante del rebajado. Nace escondido: sin cupón
   no hay nada que tachar, y un hueco reservado movería todas las líneas del
   carrito para que se note en una. */
.fn-carrito-antes[hidden] { display: none; }
.fn-carrito-antes {
  display: inline-flex;
  align-items: center;
  margin-right: var(--e1);
  color: var(--texto-tenue);
  font-size: 0.8125rem;
}

/* Y de qué ruleta salió, escrito. **La palabra tiene que estar**: el tachado
   es un canal visual y por sí solo no explica por qué esa línea cuesta menos.
   En el texto del tema y no en oro, que aquí el oro no se lee. */
.fn-carrito-suerte[hidden] { display: none; }
.fn-carrito-suerte {
  display: block;
  color: var(--texto-suave);
  font-size: 0.75rem;
}

/* --- E: el pago --------------------------------------------------------- */

/* Lo que se acaba de decidir, en una línea. Sin él esta pestaña pide elegir una
   cuenta sin decir para cuánto, y el importe se quedó en la pestaña anterior. */
/* La cabecera de las dos pantallas del pago: el importe arriba y los V-Bucks
   debajo. Sustituye al recordatorio de una línea —"S/ 14.00 · 1,000 V"—, que
   decía lo mismo pero con las dos cifras pesando igual: quien está delante de
   la app del banco va a teclear el importe y no la cantidad de V.

   **Destacado no es gigante** (CLAUDE.md, "Cifras"): el importe va un escalón
   por encima del cuerpo, no en un cartel, y **en la tipografía del cuerpo**.
   La fuente de rótulo está reservada a las dos cifras del resumen de la
   pantalla anterior, que es una excepción escrita y con su motivo —se miran
   mientras se teclea—; aquí no se teclea nada. */
.fn-rec-pagar {
  margin: 0;
  padding: var(--e3);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  text-align: center;
}

.fn-rec-pagar-importe {
  margin: 0;
  font-size: 1.375rem;
  color: var(--texto-alto);
}

/* Los V-Bucks acompañan, así que van en pequeño y en el tono de apoyo. El
   icono y la cifra centrados entre ellos: son una sola cosa. */
.fn-rec-pagar-vbucks {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e1);
  margin: var(--e1) 0 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}

.fn-rec-pagar-vbucks .vbuck { width: 14px; height: 14px; }

/* **Una cuadrícula de bancos, no un acordeón.** El 3b los apilaba en un
   acordeón y abrir uno **empujaba a los de abajo fuera de la pantalla**: quien
   compara dos bancos deja de ver el segundo en cuanto mira el primero. Tres
   columnas los enseñan todos a la vez y ninguno se esconde. Un carrusel
   escondería lo mismo por el lado, y encima sin avisar.

   En 360 px siguen siendo tres: lo que se recorta es el nombre, no la fila.
   El nombre entero está en el `title`. */
.fn-rec-elegir { display: grid; gap: var(--e2); }
.fn-rec-elegir[hidden] { display: none; }

.fn-rec-rejilla {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--e2);
}

.fn-rec-banco {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--e1);
  min-height: 72px;
  min-width: 0;
  padding: var(--e2);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
  color: var(--texto);
  font: inherit;
  cursor: pointer;
  transition: border-color var(--t-rapida) var(--curva);
}
.fn-rec-banco:hover { border-color: var(--acento); }
.fn-rec-banco:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
.fn-rec-banco-icono { display: inline-flex; color: var(--acento); }
.fn-rec-banco-icono[hidden] { display: none; }
.fn-rec-banco-nombre {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
  font-size: 0.75rem;
  text-align: center;
}

/* --- El estado «elegido»: la barra y el detalle -------------------------- */

.fn-rec-elegido { display: grid; gap: var(--e2); }
.fn-rec-elegido[hidden] { display: none; }

/* La barra sustituye a la cuadrícula entera, así que dice **qué** se eligió y
   lleva delante lo que lo deshace. Sin ese botón, cambiar de banco costaría
   volver una pestaña atrás y perder el importe. */
.fn-rec-barra {
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 48px;
  min-width: 0;
  padding: 0 var(--e2) 0 var(--e3);
  border: 1px solid var(--acento);
  border-radius: var(--r-control);
  background-color: var(--acento-tenue);
}
.fn-rec-barra-icono { display: inline-flex; flex: none; color: var(--acento); }
.fn-rec-iconos { display: inline-flex; }
.fn-rec-iconos [data-icono] { display: inline-flex; }
.fn-rec-iconos [data-icono][hidden] { display: none; }
.fn-rec-barra-nombre {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-alto);
}
.fn-rec-cambiar-banco {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-width: 40px; min-height: 40px;
  border: 0;
  border-radius: var(--r-menudo);
  background: none;
  color: var(--acento);
  cursor: pointer;
}
.fn-rec-cambiar-banco:hover { color: var(--acento-alto); }
.fn-rec-cambiar-banco:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

.fn-rec-detalle { display: grid; }

.fn-rec-cuenta-cuerpo {
  display: grid;
  gap: var(--e2);
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.fn-rec-cuenta-cuerpo[hidden] { display: none; }

.fn-rec-cuenta-titular { margin: 0; color: var(--texto-suave); font-size: 0.8125rem; }
.fn-rec-cuenta-titular[hidden] { display: none; }

/* El deslizamiento de los dos estados: 200 ms, la curva de siempre y **sólo
   `transform` y `opacity`**, que es lo barato. Se apaga entero con
   `prefers-reduced-motion`, ahí abajo. */
@keyframes fn-rec-entrar {
  from { opacity: 0; transform: translateX(12px); }
  to   { opacity: 1; transform: none; }
}
.fn-rec-entra { animation: fn-rec-entrar 200ms var(--curva); }

/* El dato, con su botón de copiar. `min-width: 0` para que un número de cuenta
   largo se recorte en vez de sacar el modal por el lado en 360 px. */
.fn-rec-cuenta-dato {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  min-width: 0;
}
.fn-rec-cuenta-dato[hidden] { display: none; }
.fn-rec-cuenta-dato .mono {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto);
}
/* El botón de copiar, **el mismo en los dos temas**. Heredaba el fondo oscuro
   fijo del botón secundario, que en el tema claro quedaba como un agujero negro
   en mitad de una tarjeta blanca. Ahora es transparente, con el filo del borde y
   el icono en el acento del vendedor: la caja que se ve es la que se toca, y el
   color sale del tema como el resto.

   La caja mide 40 px —la zona táctil— y el icono 32, que es el cuadro que se
   ve. Al bajar de 20 px se baja también la caja del glifo, o queda descentrado
   (CLAUDE.md, Iconos). */
.fn-rec-copiar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  flex: none;
  min-width: 40px; min-height: 40px;
  padding: 0 var(--e1);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-menudo);
  background: none;
  color: var(--acento);
  font: inherit;
  cursor: pointer;
}
.fn-rec-copiar:hover { border-color: var(--acento); color: var(--acento-alto); }
.fn-rec-copiar:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}
/* El del ticket lleva texto al lado y ocupa la fila. */
.fn-rec-copiar--ancho { width: 100%; padding: 0 var(--e3); }

/* Al copiar, **el icono cambia de forma** y no sólo de color: un color no dice
   "copiado" a quien no lo distingue. */
.fn-rec-copiar-icono, .fn-rec-copiar-visto {
  display: inline-flex;
  flex: none;
}
.fn-rec-copiar-visto { display: none; }
.fn-rec-copiado { color: var(--ok-alto); border-color: var(--ok); }
.fn-rec-copiado .fn-rec-copiar-icono { display: none; }
.fn-rec-copiado .fn-rec-copiar-visto { display: inline-flex; }

/* El QR, **a un tamaño que se pueda escanear sin tocarlo**. Estuvo en 96px de
   miniatura, y eso no era un fallo de medida: era una decisión, pero le pedía
   al cliente descubrir que hay que pulsarlo. Un QR que hay que abrir antes de
   poder usarlo es un QR que no sirve de un vistazo, y esto es la pantalla en la
   que va a pagar.

   `width` y no `max-width`: la imagen la pone el vendedor y puede venir de
   cualquier tamaño. Con `max-width`, un QR de 64px se quedaba en 64 y uno de
   1200 llenaba la caja; con `width` sale siempre igual de grande, que es lo
   que se puede mirar y decidir.

   `image-rendering: pixelated` es por lo mismo: al estirar un QR pequeño, el
   suavizado de serie emborrona los módulos y la cámara deja de leerlo. Aquí lo
   que hay que conservar son los cuadrados, no la suavidad. */
.fn-rec-qr {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e2);
}
.fn-rec-qr[hidden] { display: none; }
.fn-rec-qr img {
  /* **A lo ancho de la tarjeta**, que es lo que hay que escanear. Estuvo en
     96px de miniatura —había que pulsarlo para verlo— y después en 240 fijos;
     con el diálogo ya crecido, lo que corresponde es que crezca con él. El
     tope está para que en un monitor no se convierta en un cartel.

     Sobre **blanco** y no sobre `--superficie-3`: un QR se lee por contraste
     entre sus módulos y el fondo, y el tema del vendedor no puede decidir
     sobre eso. Es la misma clase de excepción que el velo de la tarjeta de
     tienda: debajo hay algo que no elegimos nosotros. */
  width: min(20rem, 100%);
  padding: var(--e3);
  border-radius: var(--r-control);
  background-color: #FFFFFF;
  image-rendering: pixelated;
  cursor: zoom-in;
  transition: width var(--t-media) var(--curva);
}
/* El pie del QR: qué se puede hacer con él. Centrado y en el escalón de
   metadato — es una instrucción, no un dato. */
.fn-rec-qr-pie {
  color: var(--texto-tenue);
  font-size: 0.8125rem;
  text-align: center;
}
.fn-rec-qr-pie[hidden] { display: none; }
/* Y sigue pudiendo abrirse del todo: en un móvil estrecho, los 240px son casi
   la pantalla, pero quien quiera acercarlo lo tiene a un toque. */
.fn-rec-qr img.fn-rec-qr--grande { width: 100%; cursor: zoom-out; }

.fn-rec-ir {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0 var(--e3);
  border: 1px solid var(--acento);
  border-radius: var(--r-control);
  color: var(--acento-alto);
  text-decoration: none;
  cursor: pointer;
}
.fn-rec-ir[hidden] { display: none; }

/* Las notas del vendedor, en su caja y con el icono delante. **No es un dato
   que se copie**: es lo que hay que leer antes de pagar —«usa el QR, y si
   falla, yapea a este número»— y por eso se separa del número, que sí se
   copia. */
.fn-rec-cuenta-notas {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  font-size: 0.8125rem;
}
.fn-rec-cuenta-notas[hidden] { display: none; }
.fn-rec-notas-icono {
  flex: none;
  display: inline-flex;
  color: var(--acento);
}
.fn-rec-notas-icono .icono { font-size: 18px; width: 18px; height: 18px; }

/* --- F: el comprobante -------------------------------------------------- */

.fn-rec-subir {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  min-height: 44px;
  padding: var(--e3);
  border: 1px dashed var(--borde-alto);
  border-radius: var(--r-control);
  color: var(--texto-suave);
  cursor: pointer;
}
.fn-rec-subir[hidden] { display: none; }
.fn-rec-subir:hover { border-color: var(--acento); color: var(--acento-alto); }

.fn-rec-miniatura { margin: 0; display: grid; gap: var(--e2); justify-items: start; }
.fn-rec-miniatura[hidden] { display: none; }
.fn-rec-miniatura img {
  max-width: 120px;
  border-radius: var(--r-menudo);
  background-color: var(--superficie-3);
}
/* El nombre del fichero al lado de «Quitar»: una miniatura de 120 px de una
   captura de un móvil no se distingue de otra, y el nombre sí. */
.fn-rec-miniatura-pie {
  display: flex;
  align-items: center;
  gap: var(--e2);
  max-width: 100%;
  min-width: 0;
}
.fn-rec-archivo-nombre {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--texto-tenue);
  font-size: 0.75rem;
}

/* --- E: el final -------------------------------------------------------- */

.fn-rec-ticket {
  margin: 0;
  padding: var(--e4);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie-2);
  text-align: center;
}
.fn-rec-ticket .mono {
  color: var(--texto-alto);
  font-size: 1.375rem;
  letter-spacing: 0.04em;
}

.fn-rec-lista {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--e1) var(--e3);
  margin: 0;
}
.fn-rec-lista dt { color: var(--texto-tenue); font-size: 0.8125rem; }
.fn-rec-lista dd {
  margin: 0;
  min-width: 0;                /* o la columna `1fr` no se deja encoger */
  overflow-wrap: anywhere;
  color: var(--texto-alto);
}

.fn-rec-canales { display: grid; gap: var(--e2); }
.fn-rec-canales[hidden] { display: none; }
.fn-rec-canales-lista { display: flex; flex-wrap: wrap; gap: var(--e2); }
.fn-rec-canales-lista[hidden] { display: none; }
/* --- Los botones de canal, con el color de su marca --------------------- */

/* **Estos seis hex son la segunda excepción de la paleta**, y la única que no
   viene de un dato de la API: son las marcas registradas de sus dueños, tomadas
   del metadato de Simple Icons 16.31.0 (CC0), y un WhatsApp que no sea verde
   WhatsApp no se reconoce de un vistazo, que es justo para lo que está el botón.

   Viven aquí y no dentro de una regla `.fn-*` a propósito: son **datos de
   terceros**, no decisiones de diseño de nadie, y el test que prohíbe hexes en
   el modal los comprueba por nombre con esta lista —ni uno más—.

   Los logos van por `mask` y no por `<img>`: así el fichero SVG sigue siendo la
   única fuente y el logo toma el color del texto, que es lo que lo deja blanco
   sobre la marca sin tener que editar el SVG. */
:root {
  --canal-whatsapp:  #25D366;
  --canal-telegram:  #26A5E4;
  --canal-instagram: #FF0069;
  --canal-facebook:  #0866FF;
  --canal-discord:   #5865F2;
  --canal-x:         #000000;
  /* **El texto y el logo encima de la marca, y no siempre es blanco.** El 4b lo
     puso blanco en los seis y tres no llegaban a AA: WhatsApp 1,98, Telegram
     2,77 e Instagram 3,85. Un verde WhatsApp es un verde claro, y encima de un
     color claro lo que se lee es lo oscuro.

     Así que se elige **por marca, el que más contraste dé**, y el resultado es
     que los seis pasan AA con su color oficial intacto:

         WhatsApp  oscuro  9,86    Facebook  blanco   4,82
         Telegram  oscuro  7,06    Discord   blanco   4,61
         Instagram oscuro  5,08    X         blanco  21,00

     Son hex y no tokens del tema porque **no dependen del tema**: lo que hay
     debajo es el color de la marca, que es el mismo en claro y en oscuro.
     `--canal-tinta` es el mismo `--fondo` del panel; se escribe a mano por lo
     mismo que el velo de las tarjetas de tienda. Hay un test que recalcula los
     seis y falla por debajo de 4,5:1. */
  --canal-texto:     #FFFFFF;
  --canal-tinta:     #0D0B12;
}

.fn-rec-canal {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  min-height: 40px;
  padding: 0 var(--e3);
  /* **El borde va en los seis y no sólo donde hace falta.** X en negro se funde
     con el fondo oscuro (1,26:1) y WhatsApp, Telegram e Instagram se funden con
     el blanco del tema claro (1,98 / 2,77 / 3,85); entre las dos listas se
     llevan cinco de los seis, así que ponerlo en todos es una regla en vez de
     cinco excepciones, y además deja los botones del mismo tamaño. */
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--marca, transparent);
  color: var(--tinta, var(--canal-texto));
  text-decoration: none;
}
.fn-rec-canal:hover { border-color: var(--acento-alto); }
.fn-rec-canal:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

.fn-rec-canal-logo {
  flex: none;
  width: 18px; height: 18px;
  background-color: currentColor;
  -webkit-mask: var(--logo) center / contain no-repeat;
  mask: var(--logo) center / contain no-repeat;
}

.fn-rec-canal--whatsapp {
  --marca: var(--canal-whatsapp);
  --tinta: var(--canal-tinta);      /* el blanco no llega a AA sobre este */
  --logo: url("../img/canales/whatsapp.svg");
}
.fn-rec-canal--telegram {
  --marca: var(--canal-telegram);
  --tinta: var(--canal-tinta);      /* el blanco no llega a AA sobre este */
  --logo: url("../img/canales/telegram.svg");
}
.fn-rec-canal--instagram {
  --marca: var(--canal-instagram);
  --tinta: var(--canal-tinta);      /* el blanco no llega a AA sobre este */
  --logo: url("../img/canales/instagram.svg");
}
.fn-rec-canal--facebook {
  --marca: var(--canal-facebook);
  --logo: url("../img/canales/facebook.svg");
}
.fn-rec-canal--discord {
  --marca: var(--canal-discord);
  --logo: url("../img/canales/discord.svg");
}
.fn-rec-canal--x {
  --marca: var(--canal-x);
  --logo: url("../img/canales/x.svg");
}

/* El aviso de que el mensaje ya está en el portapapeles. Si el portapapeles
   falla, aquí sale el texto para copiarlo a mano — que es lo único que le queda
   a quien no puede pegar. */
.fn-rec-copiado-aviso {
  margin: 0;
  color: var(--ok-alto);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}
.fn-rec-copiado-aviso[hidden] { display: none; }
.fn-rec-copiado-aviso--mal { color: var(--texto-suave); }

/* El aviso destacado de «Solicitud enviada»: lleva el acento del vendedor,
   porque es lo que hay que mirar de esa pantalla después del ticket. */
.fn-rec-ojo {
  display: flex;
  align-items: flex-start;
  gap: var(--e2);
  margin: 0;
  padding: var(--e3);
  border: 1px solid var(--acento);
  border-radius: var(--r-menudo);
  background-color: color-mix(in srgb, var(--acento) 12%, transparent);
  color: var(--texto);
  font-size: 0.8125rem;
}
.fn-rec-ojo .icono { flex: none; color: var(--acento-alto); }
.fn-rec-ojo[hidden] { display: none; }

/* --- F: el estado ------------------------------------------------------- */

.fn-rec-estado {
  display: grid;
  gap: var(--e3);
  justify-items: start;
  padding: var(--e4);
  border: 1px solid var(--borde);
  border-radius: var(--r-tarjeta);
  background-color: var(--superficie-2);
}
.fn-rec-estado .fn-rec-lista { width: 100%; }
.fn-rec-estado[hidden] { display: none; }
.fn-rec-estado-nota { margin: 0; }
.fn-rec-estado-nota[hidden] { display: none; }

/* La etiqueta de estado, arriba de la tarjeta. **Icono y palabra**: el color
   dice de un vistazo cómo va y la palabra lo dice para quien no lo distingue.

   Los cuatro salen de los tokens de estado —el base rellena y bordea, el `-alto`
   se lee—, así que cambian con el tema como todo lo demás. El expirado va en el
   neutro: no es ni bueno ni malo, es que se acabó el tiempo. */
.fn-rec-etiqueta {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  padding: var(--e1) var(--e3);
  border: 1px solid var(--tono, var(--borde-alto));
  border-radius: var(--r-pastilla);
  background-color: color-mix(in srgb, var(--tono, var(--borde-alto)) 16%, transparent);
  color: var(--tono-alto, var(--texto-alto));
  font-weight: 600;
  font-size: 0.875rem;
}
.fn-rec-etiqueta-icono { display: inline-flex; flex: none; }
.fn-rec-etiqueta-icono[hidden] { display: none; }
.fn-rec-etiqueta-icono .icono { width: 18px; height: 18px; font-size: 18px; }

.fn-rec-etiqueta--pendiente { --tono: var(--aviso); --tono-alto: var(--aviso-alto); }
.fn-rec-etiqueta--aprobado { --tono: var(--ok); --tono-alto: var(--ok-alto); }
.fn-rec-etiqueta--rechazado { --tono: var(--peligro); --tono-alto: var(--peligro-alto); }
.fn-rec-etiqueta--expirado { --tono: var(--borde-alto); --tono-alto: var(--texto-suave); }

.fn-rec-estado-titulo { margin: 0; color: var(--texto-alto); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  /* El deslizamiento se va entero: lo que informa es que la cuadrícula ya no
     está y en su sitio hay una barra, y eso se ve sin moverse. */
  .fn-rec-resumen, .fn-rec-qr img, .fn-rec-banco, .fn-rec-entra {
    transition: none;
    animation: none;
  }
}

/* ==========================================================================
   Códigos de regalo (`/panel/codigos`)
   ==========================================================================

   Lo propio de esta pantalla, y **sólo lo propio**. El armazón —la tarjeta, el
   campo, el rótulo, el interruptor, la etiqueta de estado, el aviso ámbar, los
   diálogos— es el de `ck-`, que se comparte con CONF CHECKOUT: no son clases
   del Verificador, son las de una pantalla de configuración del panel, y esta
   sección es exactamente eso. Duplicarlas con otro prefijo serían doscientas
   líneas que hay que cambiar en dos sitios cada vez que se toque un borde.

   Aquí abajo está lo que no existía: la fila de un código, los contadores que
   filtran, el lote recién creado y la lista de canjes. */

/* Los campos de crear, en fila y con suelo: por debajo de eso un «1000» con su
   rótulo encima no cabe y el número se parte. En estrecho caen a una columna
   solos, que es lo que hace el `auto-fit`. */
.cg-form {
  display: grid;
  gap: var(--e3);
  min-width: 0;
}
.cg-form[hidden] { display: none; }

.cg-fila-campos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--e3);
}

/* --- Los contadores, que además filtran -------------------------------- */

/* Son botones y no pastillas muertas: el número ya está ahí y pulsarlo es lo
   que cualquiera intentaría hacer con él. El estado activo **no se dice sólo
   con el color**: lleva además `aria-pressed`, y el borde cambia. */
.cg-filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}

.cg-filtro {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  min-height: 36px;
  padding: 0 var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-pastilla);
  background-color: var(--superficie-2);
  color: var(--texto-suave);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  transition: border-color var(--t-rapida), color var(--t-rapida);
}
.cg-filtro:hover { border-color: var(--acento-hondo); color: var(--acento-alto); }
.cg-filtro:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }
.cg-filtro .cifras { color: var(--texto-alto); }

.cg-filtro--activo {
  border-color: var(--acento-hondo);
  background-color: var(--acento-tenue);
  color: var(--acento-alto);
}

/* «Por API» mira otra columna —`origen` y no `estado`— y aun así **se ve
   exactamente igual que los otros cuatro, y es una decisión**: es el mismo
   control, se usa igual y se apaga igual. Teñirlo diría que es otra cosa que se
   pulsa, y separarlo con un margen se descuadraría en cuanto la fila envuelve,
   que en 380px es siempre. Lo que lo distingue es la palabra. */

/* --- El cupo del día de la API, en la cabecera -------------------------- */

/* La etiqueta de estado y el cupo, juntos. **La cabecera reparte con
   `space-between`**, así que un tercer hijo suelto dejaría la etiqueta flotando
   en el medio y el cupo pegado al borde de enfrente. Con la caja, la cabecera
   sigue teniendo dos partes y estos dos se leen de una pasada. */
.cg-cabecera-datos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e3);
}


/* Va al lado de la etiqueta de «Operativo» y **no se le parece**: aquélla es un
   estado —y un botón, porque detrás hay un motivo— y esto es información. De
   ahí que no lleve pastilla, ni borde, ni fondo: es una frase con su cifra
   dentro y un medidor debajo, que es lo que CLAUDE.md pide para una cifra con
   techo. La misma discreción que la etiqueta «API» de la lista.

   Dos filas y no una porque el medidor va **debajo** de la frase y no más ancho
   que ella; en la cabecera, que es una fila flexible, eso obliga a envolverlo. */
.cg-cupo {
  display: grid;
  gap: var(--e1);
}
.cg-cupo[hidden] { display: none; }

.cg-cupo-texto {
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.8125rem;
}

/* La cifra en crema dentro de una frase en `--texto-suave`, como todas las del
   panel: lo que la destaca es el color, no el tamaño ni la familia. */
.cg-cupo-cifras { color: var(--texto-alto); }

/* Y al acercarse al tope pasa al aviso, **al 80% y no al llegar**: es la
   excepción de CLAUDE.md para los cupos que se consumen solos, y el motivo está
   allí escrito —a 100 de 100 ya no queda nada que hacer—. El tono es el mismo
   que el del medidor del plan, y el `-alto` porque esto ES texto. */
.cg-cupo-cifras--cerca { color: var(--aviso-alto); }

/* El medidor se queda con el de la casa y sólo se le quita el tope de 200px:
   aquí lo que lo acota es la frase de encima, que es más corta. */
.cg-cupo-medidor { max-width: 100%; }

/* --- La fila de un código ---------------------------------------------- */

.cg-lista {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--e2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.cg-codigo {
  display: grid;
  gap: var(--e2);
  min-width: 0;
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}

.cg-codigo-alto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e2);
}

/* El texto del código: es lo que se lee primero y lo que se dicta, así que va
   en crema y en monoespaciada con `tabular-nums`, que es lo que CLAUDE.md pide
   para ids y códigos. Se parte por donde sea antes que sacar la caja. */
.cg-texto {
  color: var(--texto-alto);
  font-size: 1rem;
  overflow-wrap: anywhere;
}

/* Los cuatro estados. El tono base bordea y rellena y el `-alto` es el que se
   lee, igual que en el resto de la paleta. Y **la palabra sigue estando**: el
   color no es el único canal. */
.cg-estado--activo {
  border-color: color-mix(in srgb, var(--ok) 55%, transparent);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  color: var(--ok-alto);
}
.cg-estado--agotado {
  border-color: color-mix(in srgb, var(--aviso) 55%, transparent);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--aviso-alto);
}
.cg-estado--vencido {
  border-color: color-mix(in srgb, var(--peligro) 55%, transparent);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  color: var(--peligro-alto);
}
/* Inactivo es el neutro: no es un problema, es una decisión del vendedor. */
.cg-estado--inactivo { color: var(--texto-suave); }

/* De dónde salió el código, y **sólo cuando salió de la API**. Se queda con el
   neutro de `.etiqueta` tal cual —`--superficie-3` con `--texto-suave`, que es
   AA sobre ella— y no coge ningún color de la paleta a propósito: **no es un
   estado** —no dice que algo vaya bien ni mal— y **no es accionable**, así que
   ni los tonos de estado ni el morado le corresponden. Discreta quiere decir
   exactamente eso: que se lea cuando se busca y que no llame cuando no.

   Nace escondida en el marcado, y ésta es su línea de `[hidden]` (CLAUDE.md,
   Reglas de UI): la de raíz ya la cubre, pero aquí queda dicho, junto a su
   bloque, que este elemento no sale siempre. */
.cg-origen[hidden] { display: none; }

/* La línea de datos: el tipo, el importe, los usos y —si lo tiene— la fecha.
   Todo dentro de la frase y en el cuerpo de texto, sin cifras grandes. */
.cg-datos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  color: var(--texto);
  font-size: 0.8125rem;
}
.cg-datos .cifras, .cg-datos .mono { color: var(--texto-alto); }
.cg-punto { color: var(--texto-tenue); }
.cg-vence { color: var(--texto-tenue); }
.cg-vence[hidden] { display: none; }
.cg-datos .vbuck { flex: none; }

/* Un código inactivo o vencido se apaga entero, no sólo su etiqueta: lo que se
   busca en esta lista son los que sirven. */
.cg-codigo--inactivo .cg-texto,
.cg-codigo--vencido .cg-texto { color: var(--texto-suave); }

.cg-acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
}

.cg-accion {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  min-height: 36px;
  padding: 0 var(--e3);
  border: 1px solid var(--borde-alto);
  border-radius: var(--r-control);
  background-color: transparent;
  color: var(--texto-suave);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  transition: border-color var(--t-rapida), color var(--t-rapida);
}
.cg-accion:hover { border-color: var(--acento-hondo); color: var(--acento-alto); }
.cg-accion:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }
.cg-accion .icono { font-size: 18px; width: 18px; height: 18px; }

.cg-accion--peligro:hover {
  border-color: var(--peligro);
  color: var(--peligro-alto);
}

/* El pulso de copiar: 320ms y `--ok-alto`, que es lo que dice CLAUDE.md. */
.cg-accion--copiado {
  border-color: var(--ok);
  color: var(--ok-alto);
}

/* --- El lote recién creado --------------------------------------------- */

/* Los códigos, uno por línea y en monoespaciada: es lo único que hay que poder
   leer de un vistazo para comprobar que se han copiado todos. */
.cg-lote {
  display: grid;
  gap: var(--e1);
  margin: 0;
  padding: var(--e3);
  max-height: 15rem;
  overflow-y: auto;
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  list-style: none;
}
.cg-lote-item { color: var(--texto-alto); }

/* --- Los canjes de un código ------------------------------------------- */

.cg-canjes {
  display: grid;
  gap: var(--e2);
  margin: 0;
  padding: 0;
  max-height: 18rem;
  overflow-y: auto;
  list-style: none;
}

.cg-canje {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  font-size: 0.8125rem;
}
/* El nombre lo elige el cliente y puede ser un correo largo: se recorta en vez
   de empujar la fila fuera del diálogo. */
.cg-canje-usuario {
  flex: 1 1 8rem;
  min-width: 0;
  color: var(--texto-alto);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cg-canje-vbucks {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--texto-alto);
}
.cg-canje-fecha { color: var(--texto-tenue); }

/* «Cuenta nueva» va en ámbar de aviso y no de energía: es un dato que puede
   pedir atención —dos altas seguidas desde el mismo sitio— y no algo que
   destaque por gusto. */
.cg-nuevo {
  border-color: color-mix(in srgb, var(--aviso) 55%, transparent);
  background-color: color-mix(in srgb, var(--aviso) 14%, transparent);
  color: var(--aviso-alto);
}
.cg-nuevo[hidden] { display: none; }

/* ==========================================================================
   Códigos de regalo en la tienda pública (`/fn/`)
   ==========================================================================

   El botón de oro, la franja de campaña y el modal. El modal reusa el armazón
   del de recargar —`.fn-velo`, `.fn-dialogo`, `.fn-recarga*`, `.fn-rec-*`—
   porque es el mismo diálogo de esta tienda; aquí abajo está sólo lo suyo. */

/* --- El botón, y el oro que no es del vendedor -------------------------- */

/* **El único color de esta página que no sale del tema, y tiene nombre
   propio.** `--canje-oro` y `--canje-oro-texto` los pone el servidor en el
   `.fn` de la página, para que este botón y el del modal —que viven en sitios
   distintos del marcado— salgan del mismo valor. Es fijo, salvo que el primario
   del vendedor le quede a menos de 30° de tono: entonces baja de claridad para
   que los dos se distingan. El porqué está en `core/store_link.canje_oro_para`.

   **No se escribe con `--vbucks` aunque coincida el hex.** La tienda no pinta
   con tokens de la casa y hay un test que lo vigila; esto es una excepción
   acotada, y una excepción con el nombre del token de otro sitio deja de estar
   acotada. Su alcance —qué selectores pueden usarlo— lo fija
   `test_el_oro_del_canje_es_una_excepcion_acotada`.

   Píldora **delgada**: 34px de alto, un escalón por debajo de los botones de
   la cabecera. Tiene que reconocerse, no competir con «Iniciar sesión». */
.fn-canjear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  flex: none;
  min-height: 34px;
  padding: 0 var(--e4);
  border: 1px solid var(--canje-oro);
  border-radius: var(--r-pastilla);
  background-color: var(--canje-oro);
  color: var(--canje-oro-texto);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform var(--t-rapida), filter var(--t-rapida);
}
/* El hover **no es sólo el color**: sube un pixel, que es el gesto del botón de
   toda la casa, y aclara. Quien no usa ratón tiene el foco, que es de 3px. */
.fn-canjear:hover { transform: translateY(-1px); filter: brightness(1.08); }
.fn-canjear:focus-visible { outline: 3px solid var(--acento-alto); outline-offset: 3px; }
.fn-canjear .icono { width: 18px; height: 18px; }

/* En la fila del reloj **es el que ocupa lo que sobra**: el reloj mide lo que
   mide y el resto es suyo. `min-width: 0` para que en la rejilla de móvil no
   imponga su ancho mínimo a la casilla, y `nowrap` para que no parta en dos
   renglones y se convierta en el botón alto de antes: cuando no hay sitio, lo
   que se recorta es el rótulo del reloj y nunca esto.

   Su alto y su tipografía no cambian —34px y 0.8125rem, los mismos que sin
   sesión—; lo que cambia es que ahora el reloj se estira a su lado en vez de
   quedarse un escalón por debajo. El icono baja a los 16px del reloj por lo
   mismo: en la misma fila, dos iconos de tamaños distintos se ven como dos
   tamaños de píldora. */
.fn-fila-sesion .fn-canjear {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
}
.fn-fila-sesion .fn-canjear .icono { width: 16px; height: 16px; }

/* Sin sesión va a lo ancho, debajo de la cabecera entera. Con sesión no: ahí es
   una pastilla más en la fila del reloj y se queda con su ancho. */
.fn-canje-ancho { padding: 0 var(--e4) var(--e3); }
.fn-canje-ancho .fn-canjear { width: 100%; }

/* --- La franja de campaña ---------------------------------------------- */

/* Va en el acento del vendedor y **no en el dorado**: el dorado es del botón,
   que es lo que se pulsa desde la cabecera; aquí lo accionable es el enlace de
   dentro y el resto es una pregunta. */
.fn-franja-codigo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  margin: 0 0 var(--e3);
  padding: var(--e3) var(--e4);
  border: 1px solid color-mix(in srgb, var(--acento) 45%, transparent);
  border-radius: var(--r-control);
  background-color: color-mix(in srgb, var(--acento) 10%, transparent);
  color: var(--texto);
  font-size: 0.9375rem;
}
.fn-franja-codigo .icono { flex: none; color: var(--acento); }

.fn-franja-codigo-boton {
  padding: 0;
  border: 0;
  background: none;
  color: var(--acento);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.fn-franja-codigo-boton:hover { color: var(--acento-alto); }
.fn-franja-codigo-boton:focus-visible {
  outline: 3px solid var(--acento-alto);
  outline-offset: 3px;
}

/* --- El modal ----------------------------------------------------------- */

.fn-canje-velo[hidden] { display: none; }

/* El regalo en círculo del título, que es lo que hace que el modal se reconozca
   como lo que abrió el botón.

   **Relleno sólido y no un tinte**, y no es una preferencia: con el tinte al
   20% el icono quedaba en **1,64:1 en el tema claro** —oro sobre oro pálido
   sobre blanco—, o sea invisible. Con el relleno entero, el par es el mismo que
   el del botón, que el servidor ya calcula y mide: 11,38:1. */
.fn-cj-circulo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--canje-oro);
  color: var(--canje-oro-texto);
}
.fn-cj-circulo .icono { width: 20px; height: 20px; }

/* Las franjas de dentro: la de «se acredita a» y la de «código válido». Las dos
   son una línea con su icono, su texto y —la segunda— la cifra a la derecha. */
.fn-cj-franja {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-size: 0.875rem;
}
.fn-cj-franja .icono { flex: none; width: 18px; height: 18px; }
.fn-cj-franja strong { color: var(--texto-alto); }

.fn-cj-franja--quien .icono { color: var(--texto-suave); }

/* Verde de `--ok`: el tono base bordea y rellena y el `-alto` es el que se lee.
   **Y la palabra sigue estando**: «Código válido» se lee sin distinguir el
   color, que es la regla de toda la casa. */
.fn-cj-franja--ok {
  border-color: color-mix(in srgb, var(--ok) 55%, transparent);
  background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  color: var(--ok-alto);
}
.fn-cj-franja--ok .icono { color: var(--ok-alto); }

/* La cifra, a la derecha del todo. `margin-left: auto` y no una rejilla: la
   franja envuelve en estrecho y con rejilla la cifra se quedaría colgando de
   una columna vacía.

   **Hereda el color de la franja y no lleva oro**: el icono de V-Bucks que
   tiene al lado ya dice que eso es dinero del juego, y un oro ahí no llegaba a
   AA en el tema claro. Es la misma decisión que `.fn-precio`, que también dejó
   de tener color propio. */
.fn-cj-franja-cifra {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  margin-left: auto;
}

/* El botón principal de cada pantalla, en dorado como el que abrió el modal.
   Hereda `--canje-oro` del `.fn` de la página, que es donde lo escribe el
   servidor: los dos botones tienen que ser el mismo color, también en las
   tiendas donde lo ha oscurecido.

   **Va con `.fn` delante, y hace falta**: `.fn .boton` pinta todos los botones
   de esta tienda con el acento del vendedor y pesa un punto más que una clase
   suelta. Sin eso, el botón que abre el modal salía dorado y el de dentro
   salía morado, que es justo lo contrario de lo que este color está haciendo
   aquí. El icono se trae aparte por lo mismo: `.fn .boton .icono` le fija el
   color del acento. */
.fn .fn-cj-boton {
  background-color: var(--canje-oro);
  background-image: none;
  border-color: var(--canje-oro);
  color: var(--canje-oro-texto);
}
.fn .fn-cj-boton:hover { color: var(--canje-oro-texto); filter: brightness(1.08); }
.fn .fn-cj-boton .icono { color: var(--canje-oro-texto); }

/* --- El final ----------------------------------------------------------- */

.fn-cj-fin { justify-items: center; text-align: center; }

/* Relleno sólido, por lo mismo que el círculo del título: el tinte dejaba el
   icono en 1,64:1 sobre blanco. */
.fn-cj-confeti {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background-color: var(--canje-oro);
  color: var(--canje-oro-texto);
}
.fn-cj-confeti .icono { width: 30px; height: 30px; }

/* «+1.000 a tu BALANCE». **Sin el icono de V-Bucks en esta línea**: es la frase
   del resultado, no una cifra de moneda suelta. El icono va en la de debajo,
   donde la cifra sí va sola.

   **Y la cifra no lleva color propio**: va en el crema de la frase, que es lo
   que CLAUDE.md pide para un dato dentro de una frase —lo que la destaca son el
   tamaño y el peso—. El oro de esta pantalla lo lleva el confeti de encima, que
   es un relleno y no texto. Teñirla de oro la dejaba en 1,63:1 sobre el blanco
   del tema claro. */
.fn-cj-sumado {
  margin: 0;
  color: var(--texto-alto);
  font-size: 1.375rem;
  font-weight: 600;
}

.fn-cj-balance {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  margin: 0;
  color: var(--texto-suave);
  font-size: 0.875rem;
}
.fn-cj-balance .mono { color: var(--texto-alto); }

/* --- Lo que se va con `prefers-reduced-motion` -------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* El barrido del botón de recargar y el salto del de canjear. **Lo que se
     queda es el color**, que es el que informa: los dos botones se siguen
     distinguiendo de los de al lado sin moverse. */
  .fn-cartera::after, .fn-rec-chip--oro::after, .fn-canjear {
    animation: none;
    transition: none;
  }
  .fn-canjear:hover { transform: none; }
}


/* --- Qué cuentas participan en el Global Friends Link ---------------------
   Una fila por cuenta: quién es y qué se sabe de ella a la izquierda, el
   interruptor a la derecha. Es la misma forma que `.pase-fila` de la tienda y
   que las filas de Amigos, y por lo mismo: lo que se compara de un vistazo al
   recorrerla es el recuento de amigos, y en columna eso se lee solo. */
.cuentas-enlace { display: flex; flex-direction: column; }

.lista .cuenta-enlace {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--e3);
}

/* El nombre y lo que se sabe de la cuenta, en columna: son una ficha, no una
   frase. `min-width: 0` para que un nombre largo parta en vez de desbordar.
   Y crece, para que el interruptor se quede a la derecha: con la frase de una
   cuenta llena debajo, la ficha ocupaba la fila entera y el interruptor caía
   abajo. Sólo parte por debajo de 12rem, más estrecho que cualquier teléfono. */
.cuenta-enlace-quien {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  min-width: 0;
  flex: 1 1 12rem;
}
/* Las etiquetas miden lo que dicen, no el ancho de la ficha. */
.cuenta-enlace-quien .etiqueta { align-self: flex-start; }
.cuenta-enlace-nombre { font-weight: 600; overflow-wrap: anywhere; }

/* El interruptor, su botón de sin-guion y el "Guardado", en fila. */
.cuenta-enlace-forma {
  display: flex;
  align-items: center;
  gap: var(--e2);
  flex: none;
}

/* Lo que se nota al guardar. Va en el verde de acierto y **con palabra**: el
   color no es el único canal. Nace escondido y lo enseña el guion. */
.cuenta-enlace-hecho {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  font-size: 0.875rem;
  color: var(--ok-alto);
}
.cuenta-enlace-hecho[hidden] { display: none; }

/* ==========================================================================
   Administración (`/admin`)
   ========================================================================== */

/* Lo que tiene la sección de propio es poco: la tabla es la de Clientes
   (`.cl-tabla`), las fichas son `.ficha` y el cajón de confirmar es el de
   siempre. Aquí sólo va el reparto de los formularios y cuatro detalles. */

/* Los formularios de una fila —filtros de la lista y acciones de la ficha—:
   campos con su rótulo encima y el botón al final, alineado con los campos.
   Envuelven solos: en móvil cada campo baja a su renglón. */
.adm-filtros {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--e3);
}
.adm-campo {
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  flex: 1 1 11rem;
  min-width: 0;
}
.adm-campo--ancho { flex: 1 1 16rem; }
.adm-rotulo { font-size: 0.75rem; color: var(--texto-tenue); }

/* La tabla de la sección: la de Clientes, con el nombre enlazado. El enlace va
   en el nombre y no en la fila, para que se alcance con el teclado. */
.adm-tabla td { vertical-align: middle; }
/* Las cabeceras de Clientes van sin relleno porque llevan un botón de ordenar
   dentro; aquí no ordenan, así que el relleno vuelve a la celda. */
.adm-tabla thead th { padding: var(--e2) var(--e3); }
/* Los precios de licencias: una fila por producto y un campo por duración.
   Arriba, el rótulo de cada columna; en estrecho el nombre del producto sube a
   su propia línea y cada campo lleva su rótulo encima. */
.adm-precios {
  display: grid;
  grid-template-columns: minmax(8rem, 1.4fr) repeat(3, minmax(0, 8rem));
  gap: var(--e3) var(--e4);
  align-items: center;
}
.adm-precios-plan { font-weight: 500; color: var(--texto-alto); }
.adm-precios-celda { display: flex; flex-direction: column; gap: var(--e1); min-width: 0; }
.adm-precios-celda .campo { width: 100%; }
/* El precio de la cuenta extra: un solo campo, del ancho de una celda. */
.adm-precios-extra { gap: var(--e1); max-width: 14rem; }
.adm-precios-extra .campo { width: 100%; }
.adm-precios-meses { display: none; }
@media (max-width: 40rem) {
  .adm-precios { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--e2) var(--e3); }
  .adm-precios-cabeza { display: none; }
  .adm-precios-plan { grid-column: 1 / -1; margin-top: var(--e2); }
  .adm-precios-meses { display: block; }
}
.adm-id { display: block; font-size: 0.8125rem; color: var(--texto-alto); }
.adm-enlace { color: var(--acento); text-decoration: none; }
.adm-enlace:hover { color: var(--acento-alto); text-decoration: underline; }
.adm-usuario { overflow-wrap: anywhere; }
/* El `detail` del AuditLog es texto largo: aquí sí parte, al revés que el
   resto de la tabla, que va en una línea. */
.adm-detalle { white-space: normal; min-width: 18rem; color: var(--texto-suave); }

/* Items manuales: la miniatura al lado del nombre y las tres acciones de la
   fila. La foto es lo que va *dentro* de la celda, así que el radio menudo. */
.adm-item { display: flex; align-items: center; gap: var(--e3); }
.adm-item-foto {
  width: 40px;
  height: 40px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-menudo);
  background: var(--superficie-2);
}
.adm-item-acciones { display: flex; flex-wrap: wrap; gap: var(--e2); }
.adm-item-fijar { flex: 0 0 auto; }
/* El icono de la moneda y su cifra, en la misma línea. */
.adm-item-precio { white-space: nowrap; }

/* Un Verificador vencido: es estado —«esto pide atención»— y va con la palabra
   escrita, así que el color no es el único canal. */
.adm-vencido { color: var(--aviso-alto); }

/* Un vendedor congelado. El par de siempre de lo destructivo: `--peligro`
   bordea y tiñe, `--peligro-alto` se lee. Lleva la palabra y un candado. */
.etiqueta--peligro {
  border-color: color-mix(in srgb, var(--peligro) 55%, transparent);
  background-color: color-mix(in srgb, var(--peligro) 14%, transparent);
  color: var(--peligro-alto);
}

.adm-migas { margin: 0 0 var(--e3); }
.adm-migas a {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  color: var(--acento);
  text-decoration: none;
  font-size: 0.875rem;
}
.adm-migas a:hover { color: var(--acento-alto); }
.adm-migas .icono { width: 16px; height: 16px; }

.adm-cabecera-etiquetas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e2);
  margin: 0;
}
.adm-error { margin-bottom: var(--e4); }

/* La ficha en dos columnas cuando hay sitio: nueve datos apilados en un
   escritorio son una lista larguísima de pares cortos. */
.adm-ficha {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: var(--e4);
}

/* El PIN del Verificador, la única vez que sale. */
.adm-pin { margin-bottom: var(--e5); }
.adm-pin-fila {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e3);
}
.adm-pin-valor {
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto-alto);
  font-size: 1.125rem;
  letter-spacing: 0.04em;
}

.adm-paginas {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e3);
}
.cl-paginas { margin-top: var(--e4); }

/* El Top compradores del panel: la misma tabla que la lista, con la medalla
   de los tres primeros (ver `--medalla-*`) al lado del número. */
/* El Top va arriba y la lista debajo, separada por el mismo aire. */
.cl-lista { margin-top: var(--e6); }
.cl-top-periodos { display: flex; gap: var(--e2); margin-bottom: var(--e3); }
.cl-top-periodos [aria-pressed="true"] {
  background-color: var(--acento-tenue);
  border-color: var(--acento);
  color: var(--acento-alto);
}
.cl-top-puesto { white-space: nowrap; }
.cl-top-medalla {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin-right: var(--e2);
  vertical-align: middle;
  border-radius: var(--r-pastilla);
  color: var(--medalla-tinta);
}
.cl-top-medalla[hidden] { display: none; }
.cl-top-medalla .icono { width: 18px; height: 18px; }
.cl-top-medalla--1 { background-color: var(--medalla-oro); }
.cl-top-medalla--2 { background-color: var(--medalla-plata); }
.cl-top-medalla--3 { background-color: var(--medalla-bronce); }
.adm-paginas[hidden], .cl-paginas[hidden] { display: none; }
.cl-recuento-de[hidden] { display: none; }
/* «FULL caducado», debajo de la pastilla y no al lado: al lado ensanchaba la
   columna del plan y el estado se salía de la caja a 1280. */
.adm-caducado { display: block; margin-top: 2px; }

/* La tanda de licencias recién generada: uno por línea y en monoespaciada, que
   es exactamente lo que copia «Copiar todos». */
.adm-lote {
  margin: 0;
  padding: var(--e3);
  max-height: 15rem;
  overflow: auto;
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto-alto);
  line-height: 1.7;
}
.adm-codigo { color: var(--texto-alto); }

/* Una cifra dentro de una frase del admin: en crema, como todas las cifras del
   panel (CLAUDE.md, Cifras). */
.adm-cifra { color: var(--texto-alto); }
/* En la ficha del admin un valor puede llevar cifra, pastilla y explicación.
   Sin esto el flex de `.ficha dd` los estrujaba en la misma línea: la cifra
   se partía en dos renglones y la pastilla se volvía un círculo. */
.adm-ficha dd { flex-wrap: wrap; }
.adm-ficha dd > .cifras,
.adm-ficha dd > .etiqueta { flex: none; }

/* --- Añadir cuentas extra (Mis cuentas) ---------------------------------
   La cantidad entre sus − / +: un escalón por encima del cuerpo y en crema,
   como cualquier cifra (CLAUDE.md, Cifras), no un cartel. */
.extra-cantidad {
  display: flex;
  align-items: center;
  gap: var(--e3);
}
.extra-cantidad-cifra {
  min-width: 3ch;
  text-align: center;
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--texto-alto);
}
.extra-rotulo { font-size: 0.75rem; color: var(--texto-tenue); }

/* --- Safe Web ------------------------------------------------------------
   La lista de webs que pueden incrustar /fn/ y /agregar/ (`safe_web.html`).
   El rótulo y lo que lo acompaña en una fila: el contador con su medidor, o
   el botón de copiar. */
.sw-alto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e2) var(--e4);
}
.sw-alto h2,
.sw-alto h3 { margin: 0; }
.sw-cupo { display: grid; gap: var(--e1); justify-items: end; }
.sw-cupo .contador { margin: 0; }
.sw-campo { flex: 1 1 16rem; min-width: 0; }

/* Una web por fila: la dirección se parte si no cabe —un dominio largo en un
   móvil— y el botón de quitar se queda a la derecha. */
.sw-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--e2);
}
.sw-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e3);
  padding: var(--e2) var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-control);
  background-color: var(--superficie-2);
}
.sw-origen { min-width: 0; overflow-wrap: break-word; color: var(--texto-alto); }
.sw-fila form { display: flex; flex: none; }

/* El código para pegar: se lee y se selecciona, no se edita (por eso no es un
   campo). Se parte en vez de desbordar la tarjeta. */
.sw-codigo {
  margin: 0;
  padding: var(--e3);
  border: 1px solid var(--borde);
  border-radius: var(--r-menudo);
  background-color: var(--superficie-2);
  color: var(--texto);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 0.8125rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}

/* La nota de la tienda dentro de un iframe (`fn_marco.js`): el plan B sin
   cookies y la espera de un pago en otra ventana. Es una `.nota` de la tienda;
   aquí sólo se colocan sus piezas, sin color propio. */
.fn-marco-nota {
  display: grid;
  gap: var(--e2);
  justify-items: start;
}
.fn-marco-nota p { margin: 0; }

/* --- Amigos > Agregar: «Desde todas mis cuentas» -------------------------
   El selector de desde dónde se envía, la vista previa con los motivos de las
   que se saltan (cada uno se despliega con su lista) y el resumen del último
   envío. */
.am-desde {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2) var(--e5);
  margin: 0;
  padding: 0;
  border: 0;
}
.am-desde-opcion {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  color: var(--texto);
}
.am-desde-opcion input:disabled + span { color: var(--texto-suave); }
.am-previa { display: flex; flex-direction: column; gap: var(--e3); }
.am-previa h3 { margin: 0; font-size: 1.0625rem; }
.am-motivos,
.am-motivo-cuentas {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
}
.am-motivo-cuentas { padding: var(--e2) 0 0 var(--e5); color: var(--texto-suave); }
.am-previa summary,
.am-resumen summary { cursor: pointer; color: var(--texto); }
.am-resumen {
  display: flex;
  flex-direction: column;
  gap: var(--e2);
  padding-top: var(--e4);
  border-top: 1px solid var(--borde);
}
.am-resumen h3 { margin: 0; font-size: 1.0625rem; }
.am-resumen p { margin: 0; }
