/* ═══════════════════════════════════════════════════════════════════════════
   CEM · Sistema de diseño
   ═══════════════════════════════════════════════════════════════════════════

   Antes de esto había 25 tamaños de letra (con 13,2 · 13,3 · 12,2 · 12,3 y
   12,8 px conviviendo), 26 valores de espaciado, 14 radios de esquina y 43
   colores escritos a mano fuera de los tokens. Eso es lo que se ve cuando algo
   «no se ve minimalista»: no es que sobren cosas, es que ninguna dos están
   alineadas con la misma regla.

   Aquí sólo hay:
     · seis tamaños de letra          12 · 14 · 16 · 20 · 26 · 34
     · una escala de espaciado de 8    4 · 8 · 16 · 24 · 40 · 64
     · un radio                       10px, y 999px sólo para lo redondo
     · tres superficies               fondo · papel · hueco
     · una sombra                     y sólo para lo que flota de verdad
     · dos duraciones                 .15s lo pequeño · .25s lo que entra

   Todo lo que se añada de aquí en adelante sale de esta lista. Si hace falta
   un valor nuevo, se cambia la escala — no se escribe una excepción.
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── superficies: tres, no siete ─────────────────────────────────────────
     Los grises llevan una pizca del azul de marca. Un gris neutro puro se lee
     como heredado; uno con una traza del acento, como elegido. */
  --fondo:#f7f8fa;          /* el lienzo de la aplicación */
  --papel:#ffffff;          /* lo que se apoya encima: tablas, modales */
  --hueco:#eef0f3;          /* lo hundido: encabezados, campos, la fila activa */

  /* ── tinta: tres niveles ─────────────────────────────────────────────── */
  --tinta:#15181c;          /* el dato */
  --tinta-2:#5a616b;        /* la explicación */
  --tinta-3:#6c7179;        /* lo accesorio */
  --filete:#dfe3e8;         /* la línea que separa */
  --filete-fuerte:#c2c8d0;  /* la línea que remata: totales, cabeceras */

  /* ── marca y estados ──────────────────────────────────────────────────── */
  --primary:#0d2440;        /* la tinta de marca: botones, activo, énfasis */
  --primary-suave:#e8edf5;  /* su fondo tenue */
  --secondary:#0b7268;      /* el teal institucional */
  --secondary-container:#d6f2ee;
  /* El dorado sobre su propio fondo suave daba 3,09:1 — por debajo del mínimo
     legible. Oscurecido conserva el tono y llega a 4,6. */
  --gold:#856517;           /* el dorado, sólo en credenciales */
  --on-gold:#ffffff;        /* lo que se escribe ENCIMA del dorado */
  /* Lo que se escribe ENCIMA del teal. No lo fija ninguna paleta: en todas, el
     secundario sigue la misma polaridad que el primario —oscuro de día, claro
     de noche—, así que la tinta del primario vale también aquí. Una paleta que
     algún día necesite otra cosa sólo tiene que declarar este nombre. */
  --on-secondary:var(--on-primary);
  --gold-suave:#f6ecd2;

  --ok:#166534;      --ok-suave:#e3f2e7;
  --warn:#9a5b06;    --warn-suave:#fbeed8;
  --error:#a51d1d;   --error-suave:#fbe4e3;
  --info:#1f4fa8;    --info-suave:#e4ebf9;

  /* ── tipografía: seis pasos y ni uno más ──────────────────────────────── */
  --t-xs:12px;   /* pies, etiquetas de columna */
  --t-sm:14px;   /* el texto de trabajo — el cuerpo */
  --t-md:16px;   /* subtítulos, entradilla */
  --t-lg:20px;   /* título de pantalla */
  --t-xl:26px;   /* una cifra que importa */
  --t-2xl:34px;  /* la cifra de portada */

  --peso:400;
  --peso-fuerte:600;   /* no hay 700: dos pesos, no dos y medio */

  /* ── espaciado: una escala de 8 ───────────────────────────────────────── */
  --e0:4px; --e1:8px; --e2:16px; --e3:24px; --e4:40px; --e5:64px;

  /* El aire ENTRE cuadros, que es distinto del relleno de dentro. Iban los dos
     a 24 px y por eso la pantalla se leía apretada: sin un hueco mayor que el
     margen interior, dos tarjetas seguidas parecen una sola cosa partida. El
     selector de densidad mueve este valor, no la escala entera. */
  --aire:var(--e4);

  /* ── forma ────────────────────────────────────────────────────────────── */
  --r:10px;          /* todo */
  --r-full:999px;    /* sólo lo redondo de verdad: avatares, chips */

  /* ── elevación: una sombra, y sólo para lo que flota ──────────────────── */
  --sombra:0 12px 32px -8px rgba(13,36,64,.22), 0 2px 8px -2px rgba(13,36,64,.10);

  /* ── movimiento: dos duraciones ───────────────────────────────────────── */
  --rapido:.15s;     /* hover, foco, lo que responde al dedo */
  --entra:.25s;      /* lo que aparece o desaparece */

  --sidebar-w:236px;
  --sidebar-w-min:64px;

  /* La luz de color del fondo. Vacía salvo que la paleta traiga la suya. */
  --luz:none;

  --texto:'Hanken Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --cifra:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;

  /* ── compatibilidad ───────────────────────────────────────────────────────
     Los nombres viejos siguen resolviendo, para que las 51 pantallas no haya
     que tocarlas una por una. Se apoyan en los tokens de arriba. */
  --bg:var(--fondo); --surface:var(--fondo); --surface-lowest:var(--papel);
  --surface-container:var(--hueco); --surface-container-low:var(--hueco);
  --surface-high:var(--hueco); --surface-highest:var(--hueco); --surface-dim:var(--hueco);
  --surface-2:var(--hueco);
  --on-surface:var(--tinta); --on-surface-variant:var(--tinta-2);
  --outline:var(--tinta-3); --outline-variant:var(--filete); --line:var(--filete);
  --muted:var(--tinta-2);
  --primary-soft:var(--primary); --primary-fixed:var(--primary-suave); --primary-dim:var(--primary-suave);
  --on-primary:#fff;
  --secondary-fixed:var(--secondary-container);
  --tertiary:var(--gold); --tertiary-container:var(--gold); --tertiary-fixed:var(--gold-suave);
  --gold-dark:var(--gold);
  --error-container:var(--error-suave); --on-error-container:var(--error);
  --ok-container:var(--ok-suave); --warn-container:var(--warn-suave); --info-container:var(--info-suave);
  --inverse-surface:#22262c; --inverse-on-surface:#f2f4f6;
  --radius:var(--r); --radius-sm:var(--r); --radius-lg:var(--r);
  --shadow:none; --shadow-lg:var(--sombra);

  /* Lo que dibuja el navegador y no esta hoja: barras de desplazamiento, el
     botón de «Elegir archivo», el calendario de una fecha, la muestra de un
     selector de color. Nada de eso lee los tokens — lee esto. Sin declararlo,
     de noche salían trozos blancos sueltos en medio de pantallas oscuras. */
  color-scheme:light;
}

/* ── de noche ──────────────────────────────────────────────────────────────
   Quien revisa la cartera vencida a las once de la noche recibía una pantalla
   blanca entera. Sólo cambian los tokens: ningún componente sabe del tema. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    color-scheme:dark;
    --fondo:#111418; --papel:#181b20; --hueco:#212630;
    --tinta:#e9ecef; --tinta-2:#a6adb8; --tinta-3:#88909a;
    --filete:#2a2f38; --filete-fuerte:#3d4450;
    --primary:#a9c6ec; --primary-suave:#1c2634;
    --secondary:#5ecfc2; --secondary-container:#12332f;
    --gold:#d9b154; --gold-suave:#332a13; --on-gold:#231a04; --on-gold:#231a04;
    --ok:#6cc98a;    --ok-suave:#122a1a;
    --warn:#e0a955;  --warn-suave:#332612;
    --error:#f08a86; --error-suave:#331a1a;
    --info:#8fb2f0;  --info-suave:#161f33;
    --sombra:0 12px 32px -8px rgba(0,0,0,.6), 0 2px 8px -2px rgba(0,0,0,.4);
    --inverse-surface:#e9ecef; --inverse-on-surface:#15181c;
    --on-primary:#0d2440;
  }
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --fondo:#111418; --papel:#181b20; --hueco:#212630;
  --tinta:#e9ecef; --tinta-2:#a6adb8; --tinta-3:#88909a;
  --filete:#2a2f38; --filete-fuerte:#3d4450;
  --primary:#a9c6ec; --primary-suave:#1c2634;
  --secondary:#5ecfc2; --secondary-container:#12332f;
  --gold:#d9b154; --gold-suave:#332a13; --on-gold:#231a04;
  --ok:#6cc98a;    --ok-suave:#122a1a;
  --warn:#e0a955;  --warn-suave:#332612;
  --error:#f08a86; --error-suave:#331a1a;
  --info:#8fb2f0;  --info-suave:#161f33;
  --sombra:0 12px 32px -8px rgba(0,0,0,.6), 0 2px 8px -2px rgba(0,0,0,.4);
  --inverse-surface:#e9ecef; --inverse-on-surface:#15181c;
  --on-primary:#0d2440;
}

*{box-sizing:border-box;}
/* `clip` y no `hidden`. Los dos recortan lo que se sale de lado, pero
   `hidden` convierte el elemento en un CONTENEDOR DE DESPLAZAMIENTO, y a
   partir de ahí `position:sticky` se resuelve contra el body —que nunca se
   desplaza por dentro— en vez de contra la ventana. Resultado: ninguna barra
   pegajosa de contenido se pegaba. Se veía en la evaluación, donde el tiempo
   y el botón de entregar se iban con el desplazamiento justo cuando más falta
   hacían. `clip` recorta igual y no crea ese contenedor. */
html,body{overflow-x:clip;}
/* El rebote elástico de arriba, quitado.
   ------------------------------------------------------------------------
   Al llegar al principio de una pantalla larga, el navegador —en Mac sobre
   todo— sigue arrastrando el documento hacia abajo unos cincuenta píxeles y
   deja a la vista el lienzo que hay detrás. Como el fondo es una luz de color
   y la aplicación es papel, lo que aparece es una franja lila sobre el menú y
   la barra: el armazón entero parece despegado del borde de la ventana,
   flotando. No es un hueco de maquetación —a desplazamiento cero no existe—,
   es el rebote; por eso se va solo en cuanto uno vuelve a desplazar, y por eso
   se arregla aquí y no moviendo nada de sitio.
   De paso evita que el desplazamiento de un diálogo se contagie a la página
   que hay debajo cuando la lista de dentro llega a su final. */
html{overscroll-behavior-y:none;}
/* El fondo son DOS capas: un color plano y, encima, una luz de color.
   ------------------------------------------------------------------------
   `--fondo` tiene que seguir siendo un color y no un degradado, porque se usa
   también como relleno de un borde y de una sombra (`box-shadow:0 0 0 2px
   var(--fondo)`) y ahí un degradado no vale.
   La luz va aparte, en `--luz`, y por omisión no hay ninguna: sólo la paleta de
   la marca la trae. `background-attachment:fixed` la deja quieta al desplazar —
   si se moviera con el contenido se vería el corte al final de la página. */
body{
  margin:0;color:var(--tinta);font-family:var(--texto);
  background-color:var(--fondo);
  background-image:var(--luz, none);
  background-repeat:no-repeat;
  background-attachment:fixed;
  background-size:cover;
  font-size:var(--t-sm);line-height:1.55;-webkit-font-smoothing:antialiased;
}
.material-symbols-outlined{font-variation-settings:'FILL' 0,'wght' 400,'GRAD' 0,'opsz' 24;
  vertical-align:middle;font-size:20px;line-height:1;user-select:none;}
a{color:inherit;text-decoration:none;}
button,input,select,textarea{font-family:inherit;font-size:inherit;color:inherit;}
button{cursor:pointer;}
.hidden{display:none !important;}
[hidden]{display:none !important;}

/* ── un solo foco de teclado, igual en toda la plataforma ─────────────────
   Antes había cuatro reglas sueltas y el resto de la plataforma no se podía
   recorrer sin ratón sabiendo dónde estabas. */
:focus{outline:none;}
:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:4px;}

/* ── quien pidió menos movimiento ────────────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;}
}

/* ───────────────────────────── ENTRADA ─────────────────────────────────── */
.auth-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;
  padding:var(--e3);background:transparent;}
/* `margin:auto` y no sólo el centrado del contenedor: con align-items:center,
   una tarjeta más alta que la pantalla se recorta por arriba y la parte cortada
   no se puede alcanzar ni desplazando. Con margin auto el desbordamiento se
   reparte y todo queda accesible. Se nota en el registro, que es largo, y en un
   teléfono en horizontal. */
.auth-card{background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  max-width:400px;width:100%;padding:var(--e4) var(--e3);margin:auto;}
/* El registro pregunta bastante más que la entrada: en 400 px cada pareja de
   campos se parte en dos líneas y el formulario parece el doble de largo. */
.auth-card.ancha{max-width:580px;}

/* El medidor de la contraseña. El color dice lo mismo que el texto —nunca sólo
   el color— para quien no distingue rojo de verde. */
.medida-clave{font-size:var(--t-xs);margin:calc(var(--e1) * -1) 0 var(--e2);}
.medida-clave[data-nivel="vacia"]{color:var(--tinta-3);}
.medida-clave[data-nivel="corta"]{color:var(--error);}
.medida-clave[data-nivel="debil"]{color:var(--warn);}
.medida-clave[data-nivel="aceptable"],
.medida-clave[data-nivel="fuerte"]{color:var(--ok);}

/* Texto que sólo existe para quien navega escuchando. No se puede esconder con
   display:none ni con visibility:hidden —eso lo esconde también del lector— ni
   sacándolo con position:absolute a la izquierda, que en páginas en árabe o
   hebreo empuja la barra de desplazamiento. Este recorte es la forma que no
   tiene efectos secundarios. */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;}

/* Una lista que es una lista de verdad —para quien la escucha— pero sin la
   viñeta, porque cada línea ya empieza por su icono. */
.lista-limpia{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;
  gap:var(--e1);}

/* Una casilla con su explicación al lado. Con `.row` no vale: lleva
   flex-wrap, así que en un teléfono el texto salta de línea y la casilla se
   queda sola arriba, como si fuera de otra cosa. Aquí el texto ocupa lo que
   queda y envuelve dentro de su columna. */
.casilla{display:flex;align-items:flex-start;gap:var(--e1);cursor:pointer;}
.casilla > input{flex:0 0 auto;margin-top:3px;}
.casilla > span{flex:1;min-width:0;}

/* Un grupo de campos con su título, para que un formulario largo se lea como
   tres preguntas cortas y no como veinte casillas seguidas. */
.grupo-campos{border:0;padding:0;margin:0 0 var(--e3);}
.grupo-campos > legend{font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  text-transform:uppercase;letter-spacing:.04em;color:var(--tinta-2);
  padding:0;margin-bottom:var(--e1);}
.auth-card h1{font-size:var(--t-lg);font-weight:var(--peso-fuerte);margin:var(--e2) 0 var(--e0);}
.auth-card .sub{color:var(--tinta-2);font-size:var(--t-sm);margin-bottom:var(--e3);}
/* El cuadrado de la marca.
   ───────────────────────────────────────────────────────────────────────────
   `overflow:hidden` no es un detalle: dentro va un icono de la tipografía de
   ligaduras, y si esa fuente no llega lo que se dibuja es la PALABRA. Sin
   recorte, «account_balance» se salía del cuadrado de 44 px y cruzaba la
   pantalla de entrar en letras enormes. Se vio al renderizar sin la fuente.
   El `min-width` impide que el cuadrado se estire para dejarla salir. */
.brand-badge{width:44px;height:44px;min-width:44px;border-radius:var(--r);
  background:var(--primary);color:var(--on-primary);display:flex;
  align-items:center;justify-content:center;overflow:hidden;}
.brand-badge .material-symbols-outlined{font-size:24px;line-height:1;}
/* Cuando lo que va dentro es el logotipo de verdad y no un icono de estado.
   Un dibujo no depende de ninguna fuente: llega o no llega, pero nunca se
   convierte en una palabra suelta. */
.brand-badge img{width:26px;height:26px;object-fit:contain;display:block;}
.demo-list{margin-top:var(--e3);padding-top:var(--e2);border-top:1px solid var(--filete);
  font-size:var(--t-xs);color:var(--tinta-2);}
.demo-list button{margin:var(--e0) var(--e0) 0 0;}

/* ───────────────────────────── ESQUELETO ───────────────────────────────── */
.shell{display:flex;min-height:100vh;}

/* El menú era 248 px de azul marino sólido contra un contenido casi blanco:
   lo primero que se veía era la navegación, no el trabajo. Ahora es papel con
   tinta, y sólo la entrada activa lleva color. */
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--papel);color:var(--tinta);
  border-right:1px solid var(--filete);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow-y:auto;
  transition:width var(--entra);}
.sidebar .brand{display:flex;align-items:center;gap:var(--e1);padding:var(--e2) var(--e2) var(--e2);}
/* La marca es el birrete de plataforma/assets/favicon.svg, el mismo archivo
   que el icono de la pestaña: una sola forma para toda la casa. Antes aquí
   había un cuadrado con una «C» dibujada con CSS, que no se parecía a nada. */
.sidebar .brand .mark{width:30px;height:30px;flex-shrink:0;object-fit:contain;display:block;}
.pub-brand img{flex-shrink:0;object-fit:contain;}
.sidebar .brand b{font-size:var(--t-sm);display:block;line-height:1.2;font-weight:var(--peso-fuerte);}
.sidebar .brand span{font-size:var(--t-xs);color:var(--tinta-3);}
.nav-group{padding:var(--e0) var(--e1) 0;}
.nav-group .lbl{font-size:var(--t-xs);color:var(--tinta-3);padding:var(--e1) var(--e1) var(--e0);}
.sidebar a.nav-item{display:flex;align-items:center;gap:var(--e1);padding:var(--e1) var(--e1);
  border-radius:var(--r);font-size:var(--t-sm);color:var(--tinta-2);
  transition:background var(--rapido),color var(--rapido);min-height:36px;}
.sidebar a.nav-item:hover{background:var(--hueco);color:var(--tinta);}
.sidebar a.nav-item.active{background:var(--primary-suave);color:var(--primary);
  font-weight:var(--peso-fuerte);}
.sidebar a.nav-item .material-symbols-outlined{font-size:19px;flex:none;}
.sidebar .foot{margin-top:auto;padding:var(--e1);border-top:1px solid var(--filete);}
.sidebar .who{font-size:var(--t-xs);color:var(--tinta-2);padding:var(--e0) var(--e1) var(--e1);}
.sidebar .who b{display:block;font-size:var(--t-sm);color:var(--tinta);font-weight:var(--peso-fuerte);}

/* El menú plegado a sólo iconos: 236 px son el 16% de una pantalla dedicados
   a navegación. Se recuerda la elección. */
.sidebar-plegar{display:flex;align-items:center;justify-content:center;gap:var(--e0);
  width:100%;background:none;border:0;color:var(--tinta-3);font-size:var(--t-xs);
  padding:var(--e1);border-radius:var(--r);}
.sidebar-plegar:hover{background:var(--hueco);color:var(--tinta-2);}
.sidebar-plegar .material-symbols-outlined{font-size:18px;transition:transform var(--entra);}
.shell.plegado .sidebar{width:var(--sidebar-w-min);}
.shell.plegado .sidebar .brand b,
.shell.plegado .sidebar .brand span,
.shell.plegado .sidebar .nav-group .lbl,
.shell.plegado .sidebar .who{display:none;}
.shell.plegado .sidebar a.nav-item{justify-content:center;padding:var(--e1) 0;}
.shell.plegado .sidebar a.nav-item span:not(.material-symbols-outlined){display:none;}
.shell.plegado .sidebar-plegar .material-symbols-outlined{transform:rotate(180deg);}

/* Plegado, el pie también es una columna de iconos.
   ------------------------------------------------------------------------
   Antes no lo era y se veía roto: `Apariencia` y `Cerrar sesión` seguían
   llevando su rótulo dentro de un botón de 45 px, así que se leía «Aparienc»
   y «Cerrar ses»; y el botón de plegar se quedaba sin nada dentro —una
   píldora vacía— porque la regla que esconde el rótulo escondía también su
   flecha. De ahí el `:not(.material-symbols-outlined)`: lo que se esconde al
   plegar es la palabra, nunca el icono, que es justamente lo único que queda
   para saber qué hace el botón. El nombre no se pierde, pasa al tooltip. */
.shell.plegado .sidebar-plegar span:not(.material-symbols-outlined),
.shell.plegado .sidebar .foot .btn > span:not(.material-symbols-outlined){display:none;}
.shell.plegado .sidebar .foot{padding:var(--e0);}
.shell.plegado .sidebar .foot .btn{padding:var(--e0) 0;gap:0;}
.shell.plegado .sidebar-plegar{padding:var(--e1) 0;}

.main{flex:1;min-width:0;display:flex;flex-direction:column;}
.topbar{height:56px;background:var(--fondo);border-bottom:1px solid var(--filete);
  display:flex;align-items:center;gap:var(--e1);padding:0 var(--e3);position:sticky;top:0;z-index:30;}
.topbar .menu-btn{display:none;background:none;border:none;padding:var(--e0);border-radius:var(--r);}
.topbar .search{flex:1;max-width:380px;position:relative;}
.topbar .search input{width:100%;padding:var(--e1) var(--e1) var(--e1) 34px;
  border:1px solid var(--filete);border-radius:var(--r);background:var(--papel);}
.topbar .search .material-symbols-outlined{position:absolute;left:var(--e1);top:50%;
  transform:translateY(-50%);color:var(--tinta-3);font-size:18px;pointer-events:none;}
.topbar .spacer{flex:1;}
.icon-btn{background:none;border:none;width:36px;height:36px;border-radius:var(--r-full);
  display:inline-flex;align-items:center;justify-content:center;color:var(--tinta-2);
  transition:background var(--rapido);}
.icon-btn:hover{background:var(--hueco);color:var(--tinta);}
.content{padding:var(--aire) var(--aire) var(--e5);max-width:1400px;width:100%;}

/* Una aparición apenas perceptible: la plataforma deja de dar el golpe seco. */
#page{animation:entrar var(--entra) ease-out;}
@keyframes entrar{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}

/* Lo que mide la barra de abajo: 48 del enlace más su relleno arriba y abajo,
   más lo que el teléfono se reserva para su propia barra de gestos. Se declara
   una vez porque hay dos cosas que tienen que esquivarla —Cemi y su ventana— y
   con el número escrito a mano en tres sitios se despegan al primer cambio. */
:root{--alto-bottomnav:calc(48px + var(--e0) * 2 + env(safe-area-inset-bottom));}
.bottomnav{display:none;position:fixed;bottom:0;left:0;right:0;background:var(--papel);
  border-top:1px solid var(--filete);z-index:40;
  padding:var(--e0) var(--e0) calc(var(--e0) + env(safe-area-inset-bottom));}
.bottomnav a{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;
  font-size:var(--t-xs);padding:var(--e0);border-radius:var(--r);color:var(--tinta-2);
  white-space:nowrap;min-height:48px;justify-content:center;}
.bottomnav a.active{color:var(--primary);font-weight:var(--peso-fuerte);}
.bottomnav a.active .material-symbols-outlined{font-variation-settings:'FILL' 1;}

@media (max-width:900px){
  .sidebar{position:fixed;left:0;top:0;z-index:60;transform:translateX(-100%);
    transition:transform var(--entra);width:var(--sidebar-w);}
  .sidebar.open{transform:translateX(0);box-shadow:var(--sombra);}
  .shell.plegado .sidebar{width:var(--sidebar-w);}
  .sidebar-plegar{display:none;}
  .topbar .menu-btn{display:inline-flex;}
  .topbar{padding:0 var(--e2);}
  /* El margen lateral tiene que parecerse al hueco entre tarjetas. Con 16 a
     los lados y 40 entre cajas, las tarjetas se leían pegadas al borde de la
     pantalla y separadas entre ellas: dos aires distintos en la misma vista. */
  .content{padding:var(--e3) var(--e3) 96px;}
  .bottomnav{display:flex;}
  .scrim{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:55;}

  /* Cemi encima de la barra, no encima del menú.
     ═════════════════════════════════════════════════════════════════════════
     El botón estaba clavado a 16 px del fondo, y a partir de aquí abajo el
     menú ocupa justo ese sitio: la mascota se plantaba sobre «Mis
     evaluaciones» y tapaba una de las cuatro entradas.

     El `:has` es el que decide: la barra sólo existe en las pantallas de
     dentro. En las públicas Cemi también sale, y ahí no hay nada que esquivar,
     así que se queda donde estaba. */
  body:has(.bottomnav) .chat-boton{
    bottom:calc(var(--alto-bottomnav) + var(--e2));}
}

/* La ventana del chat, sólo en la franja donde sigue siendo una ventana. Por
   debajo de 640 se abre a pantalla completa y tapa la barra a propósito; si
   esta regla llegara hasta ahí le ganaría por especificidad y la dejaría
   flotando a media altura. */
@media (min-width:641px) and (max-width:900px){
  body:has(.bottomnav) .chat-caja{
    bottom:calc(var(--alto-bottomnav) + var(--e2) + 56px);
    height:min(560px, calc(100dvh - var(--alto-bottomnav) - 150px));}
}

/* ─────────────────────── TÍTULOS Y TEXTO ───────────────────────────────── */
.page-head{display:flex;align-items:flex-start;gap:var(--e2);flex-wrap:wrap;margin-bottom:var(--aire);}
.page-head h1{font-size:var(--t-lg);font-weight:var(--peso-fuerte);margin:0 0 var(--e0);
  letter-spacing:-.01em;line-height:1.25;}
.page-head p{margin:0;color:var(--tinta-2);font-size:var(--t-sm);max-width:68ch;}
/* `flex-end` y no `center`: en cuanto la barra de acciones lleva un campo con
   rótulo —«Mes», «Desde», «Curso»— el campo mide una etiqueta más que un
   botón, y centrar deja los botones once píxeles por encima de la casilla,
   como si estuvieran en otra fila. Alineados por abajo, todos los controles
   se apoyan en la misma línea. Es la misma decisión que ya tomaba `.filters`. */
.page-head .actions{margin-left:auto;display:flex;gap:var(--e1);flex-wrap:wrap;align-items:flex-end;}
h2.sec{font-size:var(--t-sm);margin:0 0 var(--e2);font-weight:var(--peso-fuerte);
  display:flex;align-items:center;gap:var(--e0);}
h3.sec{font-size:var(--t-sm);margin:0 0 var(--e1);font-weight:var(--peso-fuerte);color:var(--tinta-2);}
.muted{color:var(--tinta-2);}
.tiny{font-size:var(--t-xs);}
p{max-width:68ch;}

/* ───────────────────────────── BOTONES ─────────────────────────────────── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--e0);
  padding:var(--e1) var(--e2);border-radius:var(--r);border:1px solid var(--primary);
  background:var(--primary);color:var(--on-primary);font-size:var(--t-sm);
  font-weight:var(--peso-fuerte);transition:background var(--rapido),border-color var(--rapido);
  white-space:nowrap;min-height:36px;}
.btn:hover{background:var(--tinta);border-color:var(--tinta);}
.btn .material-symbols-outlined{font-size:18px;}
.btn.outline{background:transparent;color:var(--tinta);border-color:var(--filete);}
.btn.outline:hover{background:var(--hueco);border-color:var(--filete-fuerte);}
.btn.ghost{background:transparent;border-color:transparent;color:var(--tinta-2);}
.btn.ghost:hover{background:var(--hueco);color:var(--tinta);}
/* De noche el dorado se aclara para verse sobre fondo oscuro, y la letra
   blanca encima se quedaba en 2,03:1. Lo que va sobre el dorado tiene su
   propio token y cambia con el tema.
   Al teal le pasaba exactamente lo mismo y se quedó sin arreglar: con la
   paleta nocturna el secundario es un lila claro y la letra blanca encima
   daba 1,4:1 — el botón «Guardar plantilla» de los certificados no se leía. */
.btn.gold{background:var(--gold);border-color:var(--gold);color:var(--on-gold);}
.btn.teal{background:var(--secondary);border-color:var(--secondary);color:var(--on-secondary);}
.btn.danger{background:transparent;color:var(--error);border-color:var(--error);}
.btn.danger:hover{background:var(--error-suave);}
.btn.sm{padding:var(--e0) var(--e1);font-size:var(--t-xs);min-height:32px;}
.btn.block{width:100%;}
/* Un botón apagado al 45% se lee como un botón medio borrado: no se sabe si
   está deshabilitado o si la pantalla se quedó a medias. Se queda entero y
   dice que no se puede pulsar con el color y el cursor, no desapareciendo. */
.btn:disabled,.btn[aria-disabled=true]{cursor:not-allowed;opacity:1;
  background:var(--hueco);color:var(--tinta-3);border-color:var(--filete);
  box-shadow:none;}
.btn:disabled:hover,.btn[aria-disabled=true]:hover{background:var(--hueco);
  color:var(--tinta-3);border-color:var(--filete);}
/* Mientras trabaja: ocupado y diciéndolo, para que nadie lo pulse dos veces. */
.btn[aria-busy="true"]{opacity:.7;cursor:progress;}

/* Una acción que vive dentro de una frase —«…o ya no me interesa»— y no merece
   el peso de un botón. Es un <button> de verdad, con su foco y su teclado; sólo
   se viste de enlace. */
.enlace{background:none;border:0;padding:0;font:inherit;color:var(--primary);
  text-decoration:underline;text-underline-offset:2px;cursor:pointer;}
.enlace:hover{color:var(--tinta);}
.enlace:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:2px;}

/* Un valor largo en monoespaciada parte, no empuja la página.
   ------------------------------------------------------------------------
   Hay 106 `<code>` en la plataforma y sólo siete llevan clase propia: los
   demás no tenían ninguna regla, ni siquiera para partirse. Un código, una
   dirección de retorno o un identificador es UNA sola palabra para el
   navegador, así que en un teléfono no cabía y salía por el lado derecho
   arrastrando la página entera con él —se veía en la pantalla de YouTube, con
   la dirección de retorno—.

   `anywhere` y no `break-word`: sólo el primero deja partir cuando la palabra
   es más ancha que TODO el contenedor, que es justo este caso. */
code{overflow-wrap:anywhere;}

/* ───────────────────────── SUPERFICIES ─────────────────────────────────── */
/* Una tarjeta lleva fondo O filete, nunca las dos cosas más una sombra. */
.card{background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  padding:var(--e3);}

/* EL AIRE ENTRE BLOQUES LO PONE EL CONTENEDOR, NO CADA BLOQUE.
   ------------------------------------------------------------------------
   Antes cada pieza traía su propio margen: la tarjeta separaba de la tarjeta
   siguiente, la fila de cifras separaba de lo que viniera debajo. Funcionaba
   mientras la página fuera una lista de tarjetas; en cuanto aparecía una
   combinación que nadie había previsto —una cuadrícula después de una
   tarjeta, dos cuadrículas seguidas, una tabla suelta— no había regla que
   las separase y quedaban pegadas, que es exactamente lo que se veía en el
   teléfono. Ahora el hueco lo pone el contenedor de la pantalla, una sola
   vez y para todo lo que caiga dentro, así que no hay combinación que se
   escape. */
#page > * + *{margin-top:var(--aire);}
/* Los tres que ya traían su propio margen dejan de ponerlo aquí, para no
   sumarlo dos veces. Fuera de #page lo siguen necesitando. */
#page > .page-head, #page > .kpis, #page > .tabs{margin-bottom:0;}

/* Dentro de cualquier otra caja (un panel, una pestaña) siguen valiendo las
   reglas entre hermanas. `:where` no suma especificidad: la excepción de la
   cuadrícula, que va justo debajo, sigue ganando. */
:where(.card, .grid) + :where(.card, .grid){margin-top:var(--aire);}
/* Dentro de una cuadrícula el hueco lo pone `gap`. El margen entre hermanas
   sólo vale cuando van una debajo de otra; en una fila descolocaba a todas
   menos a la primera. */
.grid > .card + .card{margin-top:0;}
.card-pad0{padding:0;overflow:hidden;}
/* Una cuadrícula sin modificador declara UNA columna, y no se la deja al azar.
   ------------------------------------------------------------------------
   Antes era sólo `display:grid`: el navegador se inventaba una columna «auto»,
   que se dimensiona a partir del contenido. `minmax(0,1fr)` la declara, y el
   mínimo 0 importa —el mínimo automático de una pista es `auto`, así que una
   tabla ancha o una palabra larga dentro hinchaban la columna por encima del
   contenedor—. Con mínimo 0 nunca crece más que su sitio. `g2`, `g3` y `g4`
   la sobreescriben.

   (Esto NO era lo que encogía la tarjeta de la portada a 50 px: se probó una
   columna `1fr` explícita y seguía igual. Aquello era la contención de tamaño
   de `.card`; está contado donde se arregló, junto a la regla `@container`.) */
.grid{display:grid;gap:var(--aire);grid-template-columns:minmax(0,1fr);}
.g2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));}
.g3{grid-template-columns:repeat(auto-fit,minmax(230px,1fr));}
.g4{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));}
.row{display:flex;gap:var(--e1);align-items:center;flex-wrap:wrap;}
.row.between{justify-content:space-between;}
.stack{display:flex;flex-direction:column;gap:var(--e1);}

/* La tabla ya tiene estructura propia: encerrarla en una caja con borde
   redondeado añade un marco que no separa nada de nada. */
.card > .table-wrap:only-child,
.card.solo-tabla{border:0;background:none;padding:0;}

/* ── cifras ────────────────────────────────────────────────────────────────
   Cifra primero, etiqueta debajo: así se leen los números. Sin icono —la
   palabra ya dice de qué habla— y sin caja: basta el aire y un filete. */
.kpis{display:flex;gap:var(--aire);overflow-x:auto;padding-bottom:var(--e0);
  margin-bottom:var(--aire);border-bottom:1px solid var(--filete);}
.kpi{flex:0 0 auto;min-width:150px;padding:0 0 var(--e3);background:none;border:0;}
.kpi .k-top{display:flex;align-items:center;gap:var(--e0);color:var(--tinta-2);
  font-size:var(--t-xs);order:2;}
.kpi .k-top .material-symbols-outlined{display:none;}
.kpi .k-val{font-size:var(--t-xl);font-weight:var(--peso-fuerte);letter-spacing:-.025em;
  line-height:1.1;font-family:var(--cifra);font-variant-numeric:tabular-nums;
  order:1;margin-bottom:var(--e0);overflow-wrap:anywhere;}
/* Un cero grande y negro pesa lo mismo que un cuatro mil y dice lo contrario. */
.kpi .k-val.vacio{color:var(--tinta-3);}
.kpi{display:flex;flex-direction:column;}
.kpi .k-sub{font-size:var(--t-xs);color:var(--tinta-3);margin-top:2px;order:3;}
/* Un cero es la buena noticia: gris, sin adorno. El color, para lo que pasa. */
.kpi .k-sub.up{color:var(--ok);} .kpi .k-sub.down{color:var(--error);}
.kpi .k-sub.up.cero, .kpi .k-sub.down.cero{color:var(--tinta-3);}

/* ─────────────────────────── FORMULARIOS ───────────────────────────────── */
.field{display:flex;flex-direction:column;gap:var(--e0);margin-bottom:var(--e2);}
.field label{font-size:var(--t-xs);font-weight:var(--peso-fuerte);color:var(--tinta-2);}
/* Se seleccionaba por tipo —`input[type=text]`, `input[type=email]`…— y en el
   HTML hay 83 campos escritos sin `type`, que por omisión son de texto pero no
   casaban con ninguna de esas reglas. Esos quedaban con la pinta que trae el
   navegador de fábrica: borde blanco, fondo transparente, otra altura. Era la
   causa de que en una misma tarjeta unos campos se vieran bien y otros
   rotos —«CEM International Education» al lado de «100»—.

   Ahora se seleccionan todos y se excluyen sólo los que no son cajas de
   texto. La lista va dentro de un único `:not()` a propósito: así la regla
   pesa lo mismo que antes y las pantallas que afinan un campo suyo —el
   buscador de la barra de arriba, por ejemplo— siguen ganando. */
.inp,
input:not([type=checkbox],[type=radio],[type=range],[type=file],[type=color],
          [type=submit],[type=button],[type=reset],[type=image],[type=hidden]),
select,textarea{
  width:100%;padding:var(--e1) var(--e1);border:1px solid var(--filete);border-radius:var(--r);
  background:var(--papel);color:var(--tinta);min-height:36px;
  transition:border-color var(--rapido);}
.inp:hover,input:hover,select:hover,textarea:hover{border-color:var(--filete-fuerte);}
textarea{min-height:84px;resize:vertical;}
select{appearance:none;background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238b929c'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e");
  background-repeat:no-repeat;background-position:right var(--e1) center;background-size:18px;
  padding-right:30px;}
.filters{display:flex;gap:var(--e1);flex-wrap:wrap;align-items:flex-end;margin-bottom:var(--e2);}
.filters .field{margin-bottom:0;min-width:150px;}
.filters .field.grow{flex:1;min-width:200px;}
.check{display:flex;align-items:center;gap:var(--e1);font-size:var(--t-sm);cursor:pointer;}
.check input{width:16px;height:16px;accent-color:var(--primary);}

.sel-mejorado{position:relative;}
.sel-mejorado select{position:absolute;inset:0;width:100%;height:100%;opacity:0;pointer-events:none;}
.sel-trigger{width:100%;text-align:left;padding:var(--e1);border:1px solid var(--filete);
  border-radius:var(--r);background:var(--papel);color:var(--tinta);cursor:pointer;
  display:flex;align-items:center;justify-content:space-between;gap:var(--e1);font:inherit;
  min-height:36px;transition:border-color var(--rapido);}
.sel-trigger:hover{border-color:var(--filete-fuerte);}
.sel-trigger .material-symbols-outlined{font-size:18px;color:var(--tinta-3);}
.sel-panel{position:fixed;z-index:1000;background:var(--papel);border:1px solid var(--filete);
  border-radius:var(--r);box-shadow:var(--sombra);overflow-y:auto;display:none;}
.sel-panel.abierto{display:block;}
.sel-op{padding:var(--e1);font-size:var(--t-sm);cursor:pointer;}
.sel-op:hover{background:var(--hueco);}
.sel-op.on{background:var(--primary-suave);color:var(--primary);font-weight:var(--peso-fuerte);}

.dia-chip{padding:var(--e0) var(--e2);border-radius:var(--r-full);cursor:pointer;user-select:none;
  font-size:var(--t-xs);font-weight:var(--peso-fuerte);border:1px solid var(--filete);
  background:var(--papel);color:var(--tinta-2);min-height:32px;
  display:inline-flex;align-items:center;}
.dia-chip.on{background:var(--primary);color:var(--on-primary);border-color:var(--primary);}
.horario-panel{margin-top:var(--e1);padding:var(--e2);border:1px solid var(--filete);
  border-radius:var(--r);background:var(--hueco);}

/* ───────────────────────────── TABLAS ──────────────────────────────────── */
.table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:collapse;font-size:var(--t-sm);}
th,td{text-align:left;padding:var(--e1) var(--e2);border-bottom:1px solid var(--filete);
  white-space:nowrap;}
/* Los encabezados en versalitas espaciadas se leen más lento y gritan.
   El fondo es el mismo papel de la tarjeta: hace falta que sea opaco para que
   las filas pasen por debajo al desplazar, pero no debe verse como una franja
   —lo que separa el encabezado de los datos es la línea, no un color. */
th{font-size:var(--t-xs);color:var(--tinta-3);font-weight:var(--peso-fuerte);
  position:sticky;top:0;border-bottom:1px solid var(--filete-fuerte);
  background:var(--papel);}
/* Sin zebra: sólo la fila bajo el cursor. Se lee más limpio y no se pierde
   el renglón. */
tbody tr{transition:background var(--rapido);}
tbody tr:hover{background:var(--hueco);}
tr.clickable{cursor:pointer;}
td.wrap{white-space:normal;min-width:180px;}
.empty{padding:var(--e4) var(--e3);text-align:center;color:var(--tinta-2);font-size:var(--t-sm);
  display:flex;flex-direction:column;align-items:center;gap:var(--e0);}
/* El dibujo del hueco: grande para que se lea como ilustración, tenue para
   que no compita con el botón, que es lo que hay que pulsar. */
.empty .ico{font-size:44px;color:var(--filete-fuerte);margin-bottom:var(--e0);}
.empty b{color:var(--tinta);font-size:var(--t-md);font-weight:var(--peso-fuerte);}
.empty .btn{margin-top:var(--e2);}

/* ── el aviso de un formulario, siempre igual (item 53) ───────────────────
   Lo mismo se venía diciendo de tres maneras: una etiqueta roja tamaño chip,
   un párrafo gris y un mensaje flotante en la esquina de abajo. Un aviso que
   corrige lo que acabas de escribir se pone donde estabas escribiendo, ocupa
   el ancho del formulario y se distingue por una raya al margen —no por un
   fondo de color que compita con los datos. */
.nota{display:block;margin-top:var(--e1);padding:var(--e1) var(--e2);
  font-size:var(--t-sm);line-height:1.45;color:var(--tinta-2);
  background:var(--hueco);border-left:2px solid var(--filete-fuerte);
  border-radius:0 var(--r) var(--r) 0;}
.nota:empty{display:none;}
.nota.err{border-left-color:var(--error);color:var(--tinta);}
.nota.ok{border-left-color:var(--ok);}
.nota.warn{border-left-color:var(--warn);}

/* Un aviso que pide algo tiene que enseñar por dónde se hace. Como arriba
   dejamos `a{color:inherit;text-decoration:none}` para que los enlaces de
   tarjetas y filas no salieran azules y subrayados, dentro de un aviso el
   enlace quedaba del mismo color que la frase y sin subrayar: se leía como
   texto, nadie lo pulsaba. Aquí se le devuelve la pinta de enlace.
   El :not(.btn) no es adorno —`.nota a` pesa más que `.btn`, así que sin
   él este color le pisaría el suyo a cualquier botón puesto en un aviso. */
.nota a:not(.btn){color:var(--tinta);text-decoration:underline;
  text-underline-offset:2px;font-weight:var(--peso-fuerte);}
.nota a:not(.btn):hover{text-decoration-thickness:2px;}
/* sin-caja: la caja la pone .btn; esto sólo baja la acción a su propia línea. */
.nota .btn{margin-top:var(--e1);}

/* Aquí me equivoqué antes: escondía las acciones de fila y las sacaba al pasar
   el cursor, para que 51 iconos grises no compitieran con los datos. El efecto
   real es que los botones desaparecen, y un botón que a veces está y a veces
   no obliga a buscarlo cada vez. Un control se ve siempre; para que no griten,
   se bajan de tono y suben al acercarse. */
td .acciones{display:flex;gap:2px;justify-content:flex-end;}
td .acciones .btn{color:var(--tinta-3);transition:color var(--rapido), background var(--rapido);}
tr:hover td .acciones .btn, tr:focus-within td .acciones .btn{color:var(--tinta);}
td .acciones .btn:hover{color:var(--primary);}

/* ───────────────────────── CHIPS Y ESTADOS ─────────────────────────────── */
/* El chip es para el estado, no para todo. Sin fondo de color saturado: un
   punto y la palabra bastan para no llenar la tabla de píldoras. */
.chip{display:inline-flex;align-items:center;gap:var(--e0);padding:2px var(--e1);
  border-radius:var(--r-full);font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  white-space:nowrap;background:var(--hueco);color:var(--tinta-2);}
.chip .material-symbols-outlined{font-size:14px;}
.chip.ok{background:var(--ok-suave);color:var(--ok);}
.chip.warn{background:var(--warn-suave);color:var(--warn);}
.chip.err{background:var(--error-suave);color:var(--error);}
.chip.info{background:var(--info-suave);color:var(--info);}
.chip.gold{background:var(--gold-suave);color:var(--gold);}
/* `--secondary` es el color de marca y está calibrado para verse sobre el
   fondo de la página, no sobre su propio contenedor pálido: ahí daba 4,3:1 y
   la palabra dentro del chip se leía a duras penas. Una pizca de tinta lo
   asienta sin cambiar el tono ni tocar el color de marca. */
.chip.teal{background:var(--secondary-container);
  color:color-mix(in srgb, var(--secondary) 78%, var(--tinta));}
.chip.neutral{background:var(--hueco);color:var(--tinta-2);}
.chip-btn{cursor:pointer;border:1px solid var(--filete);background:var(--papel);min-height:32px;}
.chip-btn.on{background:var(--primary);color:var(--on-primary);border-color:var(--primary);}

/* ── el estado, dentro de una tabla (item 19) ─────────────────────────────
   Una columna con treinta píldoras de colores hace que la tabla parezca un
   semáforo averiado: el color deja de querer decir «mira aquí» justamente
   porque está en todas partes. Dentro de una tabla el estado es una palabra
   con un punto del color que le toca; fuera —en una ficha, en una tarjeta,
   donde hay uno solo y sí es la noticia— la píldora se queda como está.
   Lo único que conserva la tinta es el error: es lo que se busca. */
td .chip, td .chip.ok, td .chip.warn, td .chip.info,
td .chip.gold, td .chip.teal, td .chip.neutral,
.list-item > .chip, .list-item > .chip.ok, .list-item > .chip.warn, .list-item > .chip.info,
.list-item > .chip.gold, .list-item > .chip.teal, .list-item > .chip.neutral{
  background:none;padding:0;gap:6px;font-weight:var(--peso);color:var(--tinta-2);}
td .chip.err, .list-item > .chip.err{
  background:none;padding:0;gap:6px;font-weight:var(--peso);color:var(--error);}
td .chip::before, .list-item > .chip::before{content:'';width:6px;height:6px;
  border-radius:var(--r-full);flex:none;background:var(--filete-fuerte);}
td .chip.ok::before, .list-item > .chip.ok::before{background:var(--ok);}
td .chip.warn::before, .list-item > .chip.warn::before{background:var(--warn);}
td .chip.err::before, .list-item > .chip.err::before{background:var(--error);}
td .chip.info::before, .list-item > .chip.info::before{background:var(--info);}
td .chip.gold::before, .list-item > .chip.gold::before{background:var(--gold);}
td .chip.teal::before, .list-item > .chip.teal::before{background:var(--secondary);}
/* El icono dentro del chip repetía lo que ya dice la palabra. */
td .chip .material-symbols-outlined, .list-item > .chip .material-symbols-outlined{display:none;}
/* El icono que abre una fila de lista acompaña, no manda. */
.list-item > .material-symbols-outlined{color:var(--tinta-3);flex:none;}

/* La fecha al margen de una fila, como en un calendario de pared. */
.dia{text-align:center;min-width:40px;flex:none;}
.dia b{display:block;font-size:var(--t-md);font-weight:var(--peso-fuerte);
  font-family:var(--cifra);line-height:1.15;}
.dia span{display:block;font-size:var(--t-xs);color:var(--tinta-3);}

.avatar{width:32px;height:32px;border-radius:var(--r-full);background:var(--primary-suave);
  color:var(--primary);display:inline-flex;align-items:center;justify-content:center;
  font-weight:var(--peso-fuerte);font-size:var(--t-xs);flex-shrink:0;
  position:relative;overflow:hidden;}
.avatar.lg{width:56px;height:56px;font-size:var(--t-lg);}
/* El de la barra de arriba es un botón para quien no es estudiante —abre el
   diálogo de la foto—, así que hay que quitarle lo que el navegador le pone
   por ser botón y darle a entender que se puede pulsar. */
button.avatar{border:0;padding:0;cursor:pointer;font-family:inherit;}
button.avatar:hover{outline:2px solid var(--primary);outline-offset:1px;}
.mi-foto-caja{display:flex;gap:var(--e2);align-items:center;}
.mi-foto-caja b{display:block;margin-bottom:var(--e0);}
/* La foto va encima de las iniciales, tapándolas. Si no carga, `avatar()` la
   quita y debajo sigue estando el redondel con las iniciales de siempre. */
.avatar > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}

/* Una barra de 10 px con canal y borde es un objeto; a 3 px informa igual y
   deja de pedir atención. */
.bar{height:3px;border-radius:var(--r-full);background:var(--filete);overflow:hidden;}
.bar > i{display:block;height:100%;background:var(--secondary);border-radius:var(--r-full);
  transition:width var(--entra);}
.bar.gold > i{background:var(--gold);}

/* Pestañas: texto y un subrayado en la activa. Nada de cajas. */
.tabs{display:flex;gap:var(--e3);border-bottom:1px solid var(--filete);margin-bottom:var(--e3);
  overflow-x:auto;}
.tabs button{background:none;border:none;padding:var(--e1) 0;font-size:var(--t-sm);
  font-weight:var(--peso-fuerte);color:var(--tinta-3);border-bottom:2px solid transparent;
  white-space:nowrap;margin-bottom:-1px;transition:color var(--rapido);}
.tabs button:hover{color:var(--tinta-2);}
.tabs button.active{color:var(--tinta);border-bottom-color:var(--primary);}
/* En el teléfono, la pestaña es lo que más se pulsa del aula —Temario,
   Apuntes, Dudas, Material— y medía 34 píxeles de alto. Con el pulgar, eso es
   fallar una de cada tres veces y acabar tocando el vídeo de al lado.
   44 es el mínimo cómodo, y el ancho se reparte para que no haya que arrastrar
   la fila para ver la última. */
@media (max-width:640px){
  .tabs{gap:var(--e2);}
  .tabs button{min-height:44px;padding:var(--e1) var(--e0);}
}

/* ───────────────────────────── MODAL ───────────────────────────────────── */
.modal-bg{position:fixed;inset:0;background:rgba(10,14,20,.5);z-index:100;display:flex;
  align-items:flex-start;justify-content:center;padding:var(--e4) var(--e2);overflow-y:auto;
  animation:aparecer var(--entra);}
@keyframes aparecer{from{opacity:0;}to{opacity:1;}}
.modal{background:var(--papel);border-radius:var(--r);width:100%;max-width:600px;
  box-shadow:var(--sombra);animation:subir var(--entra) ease-out;}
@keyframes subir{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
.modal.wide{max-width:880px;}
.modal-h{display:flex;align-items:center;gap:var(--e2);padding:var(--e3);
  border-bottom:1px solid var(--filete);}
.modal-h h3{margin:0;font-size:var(--t-md);font-weight:var(--peso-fuerte);flex:1;}
.modal-b{padding:var(--e3);}
.modal-f{padding:var(--e2) var(--e3);border-top:1px solid var(--filete);
  display:flex;gap:var(--e1);justify-content:flex-end;flex-wrap:wrap;}

/* ───────────────────────────── AVISOS ─────────────────────────────────── */
#toasts{position:fixed;right:var(--e2);bottom:var(--e2);z-index:200;
  display:flex;flex-direction:column;gap:var(--e1);}
.toast{background:var(--inverse-surface);color:var(--inverse-on-surface);
  padding:var(--e1) var(--e2);border-radius:var(--r);font-size:var(--t-sm);
  box-shadow:var(--sombra);display:flex;align-items:center;gap:var(--e1);
  max-width:340px;animation:subir var(--entra);}
.toast .material-symbols-outlined{font-size:18px;flex:none;}
.toast.ok .material-symbols-outlined{color:var(--ok);}
.toast.err .material-symbols-outlined{color:var(--error);}
.toast .grow{flex:1;min-width:0;}
.toast .btn{color:inherit;border-color:currentColor;background:none;}

/* ───────────────────────────── LISTAS ─────────────────────────────────── */
.list-item{display:flex;align-items:center;gap:var(--e2);padding:var(--e2) 0;
  border-bottom:1px solid var(--filete);}
.list-item:last-child{border-bottom:none;}
.list-item .grow{flex:1;min-width:0;}
.list-item .t{font-weight:var(--peso-fuerte);font-size:var(--t-sm);}
.list-item .s{font-size:var(--t-xs);color:var(--tinta-2);}
.thumb{width:40px;height:40px;border-radius:var(--r);object-fit:cover;background:var(--hueco);
  flex-shrink:0;}

/* ───────────────────────────── PÚBLICAS ───────────────────────────────── */
.pub-header{background:var(--papel);border-bottom:1px solid var(--filete);
  position:sticky;top:0;z-index:30;}
.pub-inner{max-width:1200px;margin:0 auto;padding:0 var(--e3);min-height:60px;
  display:flex;align-items:center;gap:var(--e3);flex-wrap:wrap;}
.pub-inner nav{display:flex;gap:var(--e3);font-size:var(--t-sm);flex-wrap:wrap;min-width:0;}
.pub-brand{display:flex;align-items:center;gap:var(--e1);font-weight:var(--peso-fuerte);
  color:var(--tinta);white-space:nowrap;}
.pub-cta{margin-left:auto;display:flex;gap:var(--e1);align-items:center;}
/* El botón del menú: sólo existe en pantallas estrechas. */
.pub-menu-btn{display:none;border:1px solid var(--filete);background:var(--papel);
  color:var(--tinta);border-radius:var(--r);width:42px;height:42px;cursor:pointer;
  align-items:center;justify-content:center;flex:none;}
.pub-menu-btn:hover{border-color:var(--filete-fuerte);}

@media(max-width:720px){
  /* ── una sola fila, y lo demás cuando se pida ──────────────────────────
     Esto eran TRES filas apiladas —la marca, los dos botones y los enlaces—
     y los enlaces además se salían por la derecha: en un teléfono de 390 px
     «Verificar certificado» aparecía cortado por la mitad. Ciento veinte
     píxeles de los setecientos ochenta que hay, pegados arriba todo el rato
     mientras se lee.

     Ahora la fila mide lo que mide un botón. El menú se despliega debajo, se
     cierra al elegir, con Escape y tocando fuera. */
  .pub-inner{padding:var(--e1) var(--e2);gap:var(--e1);flex-wrap:wrap;min-height:0;}
  .pub-menu-btn{display:inline-flex;order:2;margin-left:auto;}
  .pub-brand{order:1;}

  .pub-inner nav,
  .pub-inner .pub-cta{display:none;order:3;width:100%;}
  .pub-header.abierto nav{display:flex;flex-direction:column;gap:0;
    border-top:1px solid var(--filete);padding-top:var(--e1);margin-top:var(--e1);}
  .pub-header.abierto nav a{padding:var(--e2) 0;font-size:var(--t-md);}
  .pub-header.abierto .pub-cta{display:flex;margin-left:0;gap:var(--e1);
    padding:var(--e1) 0 var(--e2);}
  /* Los dos botones, a mitad y mitad: en una columna estrecha, dos botones de
     ancho distinto se leen como si uno importara más que el otro. */
  .pub-header.abierto .pub-cta > .btn{flex:1;justify-content:center;}

  /* Desplegado puede pasar de la pantalla: que se desplace él, no la página
     de debajo, que si no se pierde el sitio donde se estaba leyendo. */
  .pub-header.abierto .pub-inner{max-height:100dvh;overflow-y:auto;}
}
.pub-inner nav a.on{color:var(--primary);font-weight:var(--peso-fuerte);}
.pub-main{max-width:1200px;margin:0 auto;padding:var(--e3) var(--e3) var(--e5);}

/* ── la portada pública ────────────────────────────────────────────────────
   Lo primero que ve alguien que no nos conoce. Una sola idea por franja, y la
   primera es el buscador: quien llega buscando «marketing» no quiere leer la
   misión, quiere escribir «marketing». */
.franja{padding:var(--e5) var(--e3);}
.franja.tenue{background:var(--hueco);}
.franja > .dentro{max-width:1200px;margin:0 auto;}
.franja h2{font-size:var(--t-xl);margin:0 0 var(--e1);}
.franja > .dentro > p.entrada{color:var(--tinta-2);max-width:62ch;margin:0 0 var(--e3);}

.portada-cem{padding:var(--e5) var(--e3) var(--e4);}
/* La caja de la portada, con el mismo ancho que las franjas de debajo.
   `.franja > .dentro` ya lo tenía; `.portada-cem > .dentro` no, así que la
   cabecera se pegaba al borde izquierdo mientras todo lo de abajo iba centrado
   a 1200. Se veía en las páginas generadas, donde la cabecera NO es la de una
   sola columna centrada. */
.portada-cem > .dentro{max-width:1200px;margin:0 auto;}
/* Dos columnas: lo que decimos y algo que se puede hacer.
   La derecha no baja de 340 px porque debajo lleva un formulario, y un campo
   de texto más estrecho que eso se vuelve incómodo de escribir. */
.portada-dos{display:grid;gap:var(--e4);align-items:center;
  grid-template-columns:minmax(0,1.15fr) minmax(340px,.85fr);}

/* La portada de una sola columna, centrada.
   ───────────────────────────────────────────────────────────────────────────
   Todo lo de dentro se centra —incluidos el buscador y los atajos, que llevan
   su propio `max-width` y sin el margen automático se quedarían pegados a la
   izquierda dentro de una caja centrada, que es peor que no centrar nada—.

   El texto se limita a 62ch: una línea que cruza mil quinientos píxeles hace
   que el ojo pierda el principio de la siguiente. */
.portada-una{display:flex;flex-direction:column;align-items:center;text-align:center;}
.portada-una .portada-dicho{display:flex;flex-direction:column;align-items:center;
  max-width:62ch;width:100%;}
.portada-una h1{max-width:none;}
.portada-una .lema{margin-left:auto;margin-right:auto;}
.portada-una .portada-manos{justify-content:center;}
.portada-una .buscador-grande{width:100%;max-width:620px;margin-left:auto;margin-right:auto;}
.portada-una .atajos{justify-content:center;}
/* El campo del buscador sí lleva el texto a la izquierda: centrar lo que se
   escribe hace que el cursor salte con cada letra. */
.portada-una .buscador-grande input{text-align:left;}
.portada-cem h1{font-size:clamp(28px, 4.4vw, 46px);line-height:1.08;margin:0 0 var(--e2);
  max-width:15ch;letter-spacing:-.02em;}
.portada-cem .lema{color:var(--tinta-2);font-size:var(--t-md);max-width:52ch;
  margin:0 0 var(--e3);}

/* La portada de una página que sólo tiene texto —«Quiénes somos»— va centrada.
   ───────────────────────────────────────────────────────────────────────────
   Sin esto el titular y el lema se quedaban pegados al borde izquierdo mientras
   todo lo que viene debajo —las tres tarjetas— está centrado, y la página se
   veía descuadrada de arriba.
   Los márgenes automáticos hacen falta porque el h1 y el lema llevan su propio
   `max-width`: centrar el texto sin centrar la caja deja la caja a la
   izquierda con el texto centrado dentro, que se nota aún más. */
.portada-cem.centrada{display:flex;flex-direction:column;align-items:center;
  text-align:center;}
.portada-cem.centrada h1{max-width:22ch;margin-left:auto;margin-right:auto;}
.portada-cem.centrada .lema{margin-left:auto;margin-right:auto;}
/* El ojal: quién habla y desde cuándo, antes del titular. Dos datos que una
   escuela sí puede decir y que ninguna plantilla trae puestos. */
.ojal{display:inline-block;font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  letter-spacing:.1em;text-transform:uppercase;color:var(--secondary);
  margin-bottom:var(--e2);}
.portada-manos{display:flex;gap:var(--e1);flex-wrap:wrap;margin-bottom:var(--e3);}

/* El comprobador de títulos. Es la única cosa de esta portada que la
   competencia no puede copiar pegando texto: funciona. */
.comprobador{align-self:start;}
.comprobador .sello{color:var(--ok);font-size:34px;}
.comprobador .buscador-grande,.comprobador input{margin:0;}

@media (max-width:900px){
  /* En una columna el orden manda: primero lo que se dice, y el comprobador
     debajo. Quien llega por el móvil viene a mirar qué hay, no a verificar. */
  .portada-dos{grid-template-columns:minmax(0,1fr);gap:var(--e3);}
  .portada-cem h1{max-width:none;}
}
/* El buscador grande. Es el elemento con más peso de la página a propósito. */
.buscador-grande{max-width:620px;position:relative;}
.buscador-grande .material-symbols-outlined{position:absolute;left:14px;top:50%;
  transform:translateY(-50%);color:var(--tinta-3);pointer-events:none;}
.buscador-grande input{padding-left:44px;min-height:52px;font-size:var(--t-md);
  border-radius:var(--r-full);}
.atajos{display:flex;flex-wrap:wrap;gap:var(--e1);margin-top:var(--e2);}

/* ── escribir para encontrar a alguien ────────────────────────────────────
   Sustituye al desplegable de quinientos nombres. La lista flota sobre lo
   que haya debajo —dentro de un modal, sobre el resto del formulario— y no
   empuja nada al abrirse: si empujara, el ratón perdería la fila que iba a
   pulsar justo al pulsarla. */
.field.buscador{position:relative;}
.buscador-lista{position:absolute;left:0;right:0;top:100%;z-index:30;margin:4px 0 0;padding:4px;
  list-style:none;max-height:260px;overflow-y:auto;
  background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  box-shadow:var(--sombra);}
.buscador-lista li{display:flex;flex-direction:column;gap:1px;padding:8px 10px;
  border-radius:var(--r);cursor:pointer;}
.buscador-lista li.marcada,.buscador-lista li:hover{background:var(--hueco);}
.buscador-lista li.vacia{cursor:default;color:var(--tinta-3);font-size:var(--t-xs);}
.buscador-lista li.vacia:hover{background:none;}
.buscador-elegido{display:flex;align-items:center;gap:var(--e1);
  padding:8px 8px 8px 12px;border:1px solid var(--filete);border-radius:var(--r);
  background:var(--hueco);}

/* ── un descuento en porcentaje o en dinero ───────────────────────────────
   El número y la unidad van pegados: separados, se lee «10» y se olvida el
   «%». Debajo, siempre, el precio que queda — que es lo que de verdad se
   está decidiendo. */
.descuento-fila{gap:var(--e1);align-items:stretch;flex-wrap:nowrap;}
.descuento-fila input{flex:1;min-width:80px;}
.segmentado{display:flex;gap:2px;flex:none;}
.segmentado .btn{min-width:44px;justify-content:center;}
.segmentado .btn.activo{background:var(--primary);color:var(--on-primary);
  border-color:var(--primary);}

/* Una invitación no es una tarjeta más del panel: es lo único de la pantalla
   que espera una respuesta. Se marca con el filete del color de la casa, sin
   fondo de color — el panel ya tiene una tarjeta llena de color arriba. */
.card.invitacion{border-left:3px solid var(--secondary);}

/* ── el deslizador ────────────────────────────────────────────────────────
   El de serie de cada navegador es distinto en los tres, y en el oscuro se
   queda gris sobre gris. Con `appearance:none` se pinta la barra y el tirador
   a mano, iguales en todos, y el foco se marca — se maneja con las flechas del
   teclado tanto como con el ratón. */
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;
  background:transparent;cursor:pointer;padding:6px 0;}
input[type=range]::-webkit-slider-runnable-track{height:5px;border-radius:var(--r-full);
  background:var(--hueco);border:1px solid var(--filete);}
input[type=range]::-moz-range-track{height:5px;border-radius:var(--r-full);
  background:var(--hueco);border:1px solid var(--filete);}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:17px;height:17px;margin-top:-7px;border-radius:var(--r-full);
  background:var(--primary);border:2px solid var(--papel);
  box-shadow:0 1px 3px rgba(0,0,0,.28);}
input[type=range]::-moz-range-thumb{width:17px;height:17px;border:2px solid var(--papel);
  border-radius:var(--r-full);background:var(--primary);box-shadow:0 1px 3px rgba(0,0,0,.28);}
input[type=range]:focus-visible::-webkit-slider-thumb{outline:2px solid var(--primary);outline-offset:2px;}
input[type=range]:focus-visible::-moz-range-thumb{outline:2px solid var(--primary);outline-offset:2px;}
/* El número va pegado a su rótulo, no debajo: leyendo «Velocidad 31 s por
   vuelta» de un tirón se entiende qué se está moviendo. */
.field label b[data-eco]{margin-left:var(--e0);color:var(--tinta-2);font-variant-numeric:tabular-nums;}

/* ── la columna de marcar, para repartir en lote ──────────────────────────
   Estrecha y centrada; no lleva rótulo porque no hay nada que rotular. */
th.col-marca,td.col-marca{width:36px;text-align:center;padding-left:var(--e1);padding-right:0;}
.col-marca input{width:17px;height:17px;accent-color:var(--primary);cursor:pointer;}

/* Las cifras de la casa: 2.500 estudiantes, 9 países. Van juntas y grandes. */
.cifras-casa{display:grid;gap:var(--e3);grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  text-align:center;}
.cifras-casa b{display:block;font-size:var(--t-xl);font-family:var(--cifra);
  font-variant-numeric:tabular-nums;color:var(--primary);line-height:1.1;}
.cifras-casa span{font-size:var(--t-xs);color:var(--tinta-2);}

/* Las mismas cifras, dentro del héroe. Antes vivían en una franja aparte, tan
   lejos del titular que no probaban nada de lo que el titular afirmaba: 178 px
   de página para decir ocho palabras. Aquí van pegadas debajo, más juntas y
   sobre una línea que las separa del texto sin partir la sección en dos. */
.cifras-heroe{width:100%;margin-top:var(--e3);padding-top:var(--e3);
  border-top:1px solid var(--filete);gap:var(--e2);
  /* Tres columnas fijas, no `auto-fit`. Siempre son tres cifras exactamente,
     así que aquí `auto-fit` no adapta nada: sólo decide en qué ancho se rompe
     la fila y deja una cifra sola. A 390 px partía 2+1. Con tres columnas la
     fila aguanta entera en cualquier pantalla; lo que se estrecha es cada
     columna, y el rótulo pasa a dos renglones, que es simétrico. */
  grid-template-columns:repeat(3,minmax(0,1fr));}
.cifras-heroe b{font-size:var(--t-lg);}
@media (max-width:420px){
  .cifras-heroe{gap:var(--e1);}
  .cifras-heroe b{font-size:var(--t-md);}
}

/* Los tres hechos del héroe: cómo son las clases, dónde y qué te llevas.
   ───────────────────────────────────────────────────────────────────────────
   Apilados, no en fila. Lo intenté en fila y se partía 2+1: la columna del
   héroe mide 596 px y los tres renglones suman 818, así que en una fila no
   caben en NINGUNA pantalla —no es cosa del móvil—. Y un 2+1 centrado se lee
   como un descuadre, no como una lista.

   Apilados, además, se leen de verdad en vez de barrerse de un vistazo. Van
   alineados a la izquierda dentro de una caja centrada: los iconos forman
   columna y el ojo baja por ella. Centrar cada renglón por su cuenta dejaría
   los iconos en zigzag. */
.portada-hechos{list-style:none;margin:var(--e2) auto 0;padding:0;
  display:grid;gap:var(--e1);justify-content:center;justify-items:start;
  text-align:left;width:max-content;max-width:100%;}
.portada-hechos li{display:flex;align-items:center;gap:8px;
  font-size:var(--t-sm);color:var(--tinta-2);}
.portada-hechos .material-symbols-outlined{font-size:19px;color:var(--secondary);
  flex:none;}

/* La próxima convocatoria: la única línea de los hechos que cambia, y la única
   con fecha. Va en tinta entera y con su icono en dorado, para que se
   distinga de las tres de arriba, que son permanentes. La nota («Cupos
   limitados») baja de tono porque es un matiz, no el dato. */
.portada-hechos li.conv{color:var(--tinta);flex-wrap:wrap;}
.portada-hechos li.conv .material-symbols-outlined{color:var(--gold);}
.conv-nota{color:var(--tinta-2);font-size:var(--t-xs);}
/* En la ficha del diplomado, como párrafo sobre los botones. */
.convocatoria{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:var(--e2) 0 0;font-size:var(--t-sm);color:var(--tinta);}
.convocatoria .material-symbols-outlined{font-size:19px;color:var(--gold);flex:none;}

/* El pie de las pantallas públicas montadas, igual que el de las generadas:
   la marca a la izquierda, los enlaces a la derecha, y en el móvil apilados. */
.pub-pie{display:flex;flex-wrap:wrap;gap:var(--e3);justify-content:space-between;}
.pub-pie-nav{display:flex;flex-direction:column;gap:6px;}

/* El verificador en la portada. Es lo único de la página que funciona en vez
   de contar, así que se le da el tamaño de un buscador y no el de un campo
   más: quien llega aquí con un código en la mano tiene que verlo. */
.verificar-aqui{width:100%;max-width:520px;margin:var(--e3) auto 0;text-align:left;}
.verificar-fila{display:flex;gap:var(--e1);flex-wrap:wrap;}
.verificar-fila input{flex:1;min-width:min(100%,220px);min-height:48px;
  border-radius:var(--r-full);padding-inline:var(--e2);}
.verificar-fila .btn{min-height:48px;white-space:nowrap;}

/* Misión, visión y valores: tarjetas iguales, ninguna manda sobre otra. */
.tarjetas-idea{display:grid;gap:var(--aire);grid-template-columns:repeat(auto-fit,minmax(260px,1fr));}
.tarjeta-idea{background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  padding:var(--e3);}
/* El icono de cada tarjeta, en su color. En el brand board los cinco valores
   son cinco estrellas de cinco colores distintos; una rejilla de cinco iconos
   todos del mismo azul no se parece a eso. El fondo es el mismo color al 14 %,
   que es lo que el board llama «efectos de luz». */
.tarjeta-idea .ico{width:44px;height:44px;border-radius:var(--r);display:flex;
  align-items:center;justify-content:center;background:var(--primary-suave);
  color:var(--primary);margin-bottom:var(--e2);}
.tarjeta-idea .ico[data-serie]{
  color:var(--tono);
  background:color-mix(in oklab, var(--tono) 14%, var(--papel));
}
.tarjeta-idea .ico[data-serie="1"]{--tono:var(--serie-1);}
.tarjeta-idea .ico[data-serie="2"]{--tono:var(--serie-2);}
.tarjeta-idea .ico[data-serie="3"]{--tono:var(--serie-3);}
.tarjeta-idea .ico[data-serie="4"]{--tono:var(--serie-4);}
.tarjeta-idea .ico[data-serie="5"]{--tono:var(--serie-5);}
.tarjeta-idea .ico[data-serie="6"]{--tono:var(--serie-6);}
/* El verde y el amarillo del board son demasiado luminosos para ser tinta sobre
   blanco; se oscurecen sólo cuando hacen de icono, no en las barras. */
.tarjeta-idea .ico[data-serie="2"],
.tarjeta-idea .ico[data-serie="6"]{
  color:color-mix(in oklab, var(--tono) 62%, var(--tinta));
}

/* ── Dónde damos clase ─────────────────────────────────────────────────────
   Una fila de países que se arrastra de lado, con la idea en la primera
   tarjeta y cada país después: bandera, contorno y nombre.

   Va de lado y no en rejilla a propósito. Una rejilla de nueve países ocupa
   media portada y obliga a mirarlos todos; una fila que se desliza cabe en
   una franja, se entiende de un vistazo y deja claro que hay más a la
   derecha. En un teléfono es además el gesto natural. */
.paises-fila{display:flex;gap:var(--aire);overflow-x:auto;padding-bottom:var(--e2);
  /* Cada tarjeta se para en su sitio: arrastrar no deja medio país cortado. */
  scroll-snap-type:x mandatory;scroll-padding-left:var(--e3);
  /* La barra de desplazamiento sólo cuando se está usando, para que no le
     cruce una línea gris por debajo a la franja. */
  scrollbar-width:thin;}
.paises-fila > *{scroll-snap-align:start;flex:none;}

/* La primera tarjeta lleva el texto y va en color: es el enunciado, no un
   país, y si fuera igual que las demás se leería como si el CEM estuviera en
   un sitio llamado «Ensambla programas». */
.pais-intro{width:min(300px,72vw);border-radius:var(--r-g);padding:var(--e3);
  /* `primary` y no `secondary` porque de este color SÍ existe la pareja para
     escribir encima. `--on-secondary` no está definido en ninguna paleta: al
     usarlo, el texto se quedaba en la tinta heredada —casi negra— sobre un
     morado fuerte, y no se leía. */
  background:var(--primary);color:var(--on-primary);
  display:flex;flex-direction:column;justify-content:center;gap:var(--e1);}
/* Los títulos y los párrafos traen su propio color de más arriba; aquí manda
   el de la tarjeta o vuelve la tinta oscura sobre el color. */
.pais-intro h3,.pais-intro p{margin:0;color:inherit;}
.pais-intro h3{font-size:var(--t-lg);line-height:1.2;}
.pais-intro p{font-size:var(--t-sm);opacity:.92;line-height:1.6;}

.pais-tarjeta{width:200px;border-radius:var(--r-g);padding:var(--e2);
  background:var(--papel);border:1px solid var(--filete);
  display:flex;flex-direction:column;align-items:center;gap:var(--e1);}
.pais-tarjeta .bandera{align-self:flex-start;font-size:26px;line-height:1;
  /* La bandera es un emoji, y en un sistema sin emojis de color salen las dos
     letras del país: se sigue entendiendo, así que se le da sitio fijo para
     que la tarjeta no cambie de alto según lo que sepa dibujar cada uno. */
  min-height:30px;}
.pais-tarjeta svg{width:100%;height:150px;display:block;}
/* El contorno es un trazo, no una mancha: relleno no, y la línea lo bastante
   fina para que un país pequeño no se convierta en un borrón. */
.pais-tarjeta svg path{fill:none;stroke:var(--primary);stroke-width:.7;
  stroke-linejoin:round;vector-effect:non-scaling-stroke;}
.pais-tarjeta .nombre{font-weight:var(--peso-fuerte);font-size:var(--t-sm);
  text-align:center;margin-top:auto;}

@media (max-width:640px){
  .pais-tarjeta{width:170px;}
  .pais-tarjeta svg{height:120px;}
}

/* Un chip que va del color de su serie. Los ocho de «qué te llevas» del board
   salen cada uno de un color, como las etiquetas de sus redes sociales. */
.chip-marca{display:inline-flex;align-items:center;gap:var(--e0);
  font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  padding:.35em .75em;border-radius:var(--r-full);
  color:color-mix(in oklab, var(--tono) 55%, var(--tinta));
  background:color-mix(in oklab, var(--tono) 16%, var(--papel));
  border:1px solid color-mix(in oklab, var(--tono) 32%, var(--papel));}
.chip-marca[data-serie="1"]{--tono:var(--serie-1);}
.chip-marca[data-serie="2"]{--tono:var(--serie-2);}
.chip-marca[data-serie="3"]{--tono:var(--serie-3);}
.chip-marca[data-serie="4"]{--tono:var(--serie-4);}
.chip-marca[data-serie="5"]{--tono:var(--serie-5);}
.chip-marca[data-serie="6"]{--tono:var(--serie-6);}
.tarjeta-idea h3{margin:0 0 var(--e1);font-size:var(--t-md);}
.tarjeta-idea p{margin:0;color:var(--tinta-2);font-size:var(--t-sm);}

/* La historia, como una línea de tiempo. Un año a la izquierda y qué pasó. */
.hilo{list-style:none;margin:0;padding:0;position:relative;}
.hilo::before{content:'';position:absolute;left:7px;top:6px;bottom:6px;width:2px;
  background:var(--filete);}
.hilo > li{position:relative;padding:0 0 var(--e3) var(--e4);}
.hilo > li::before{content:'';position:absolute;left:0;top:5px;width:16px;height:16px;
  border-radius:var(--r-full);background:var(--primary);border:3px solid var(--fondo);}
.hilo b{display:block;font-family:var(--cifra);font-size:var(--t-xs);color:var(--primary);
  letter-spacing:.03em;}
.hilo span{color:var(--tinta-2);font-size:var(--t-sm);}

/* La franja de contacto: el formulario a un lado y por qué escribir al otro. */
.contacto{display:grid;gap:var(--aire);grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  align-items:start;}
@media(max-width:820px){ .contacto{grid-template-columns:1fr;} }
/* ── ver el material antes de abrirlo ─────────────────────────────────────
   El marco de una previsualización. Es siempre del mismo alto aunque dentro
   quepa una imagen apaisada, la primera página de un PDF o sólo un icono: una
   cuadrícula de fichas de alturas distintas se lee como una avería.

   `pointer-events:none` sobre lo de dentro es lo que hace que el conjunto se
   comporte como una imagen y no como una ventana: el clic lo recibe la ficha,
   que es la que sabe qué abrir. */
.previa{position:relative;overflow:hidden;background:var(--hueco);
  border-bottom:1px solid var(--filete);display:flex;align-items:center;justify-content:center;}
.previa > *{pointer-events:none;}
.previa img{width:100%;height:100%;object-fit:cover;display:block;}
.previa.sin-previa{background:var(--primary-suave);}
.previa.sin-previa .material-symbols-outlined{font-size:var(--t-2xl);color:var(--primary);opacity:.62;}
/* El triángulo de reproducir, encima de la miniatura: sin él una miniatura de
   vídeo se lee como una foto cualquiera. */
.previa-play{position:absolute;width:42px;height:42px;border-radius:var(--r-full);
  background:rgba(0,0,0,.6);color:#fff;display:flex;align-items:center;justify-content:center;}
.previa-play .material-symbols-outlined{font-size:26px;color:#fff;}
/* Dentro de una tarjeta con relleno, la previa lo desborda para tocar los
   bordes: media pulgada de papel alrededor de una foto la convierte en sello. */
.card > .previa:first-child{margin:calc(var(--e2) * -1) calc(var(--e2) * -1) var(--e2);
  border-radius:var(--r) var(--r) 0 0;}

/* ── el mes, en cuadrícula ────────────────────────────────────────────────
   Vivía dentro de la pantalla del equipo. Cuando el estudiante pidió su propio
   calendario, copiarlo habría dejado dos rejillas que se separan a la primera
   corrección: la misma semana se vería distinta según quién la mire, y eso es
   justo lo que hace dudar de si son los mismos datos. */
.cal{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:1px;background:var(--filete);
  border:1px solid var(--filete);border-radius:var(--r);overflow:hidden;}
.cal .dow{background:var(--hueco);padding:8px 6px;text-align:center;font-size:var(--t-xs);font-weight:700;
  text-transform:uppercase;color:var(--tinta-2);}
.cal .day{background:var(--surface-lowest);min-height:104px;padding:6px;display:flex;flex-direction:column;gap:4px;}
.cal .day.out{background:var(--papel);opacity:.55;}
.cal .day.today .num{background:var(--primary);color:var(--on-primary);border-radius:var(--r-full);}
.cal .num{font-size:var(--t-xs);font-weight:700;width:22px;height:22px;display:flex;align-items:center;justify-content:center;}
.ev{font-size:var(--t-xs);padding:3px 6px;border-radius:var(--r);background:var(--secondary-container);color:var(--secondary);
  cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;
  border:0;width:100%;text-align:left;display:block;}
.ev.presencial{background:var(--gold-suave);color:var(--gold);}
.ev.dictada{background:var(--hueco);color:var(--tinta-2);}
@media(max-width:760px){ .cal .day{min-height:78px;} .ev{font-size:var(--t-xs);padding:3px 5px;} }

/* Una cuadrícula de material que se estrecha sola: en el aula convive con el
   panel de dudas y con la lista de lecciones, así que no puede pedir un ancho
   fijo. */
.rejilla-material{display:grid;gap:var(--e2);
  grid-template-columns:repeat(auto-fill,minmax(190px,1fr));}

.course-card{background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  overflow:hidden;display:flex;flex-direction:column;transition:border-color var(--rapido);}
.course-card:hover{border-color:var(--filete-fuerte);}
.course-card img{width:100%;height:148px;object-fit:cover;background:var(--hueco);}
/* ── la portada dibujada, cuando el curso no tiene foto ───────────────────
   Seis de los ocho cursos publicados no tienen imagen, y un recuadro en blanco
   de 148 px no se lee como «falta la foto»: se lee como «esto no está
   terminado». Cada categoría trae su motivo —lo dibuja `portadaDeCurso()` en
   app.js— y el color sale del tema elegido, así que sigue la paleta como todo
   lo demás en vez de ser un morado fijo.

   `currentColor` es lo que deja que el motivo se pinte solo: el SVG hereda el
   color del contenedor y no hay que repetirlo dentro de cada trazo. */
.portada-curso{width:100%;display:block;object-fit:cover;background:var(--hueco);}
.portada-tema{position:relative;overflow:hidden;color:var(--on-primary);
  background:linear-gradient(135deg,
    color-mix(in oklab, var(--primary) calc(var(--mezcla, 50) * 1%), var(--secondary)),
    color-mix(in oklab, var(--primary) 78%, #000));}
.portada-tema svg{position:absolute;inset:0;width:100%;height:100%;opacity:.72;}
/* El nombre de la categoría, abajo a la izquierda: es el único texto, porque el
   título del curso ya va ocho píxeles más abajo en la propia ficha. */
.portada-tema .etiqueta{position:absolute;left:var(--e2);bottom:var(--e1);
  font-size:var(--t-xs);font-weight:var(--peso-fuerte);letter-spacing:.08em;
  text-transform:uppercase;opacity:.9;}
@media (prefers-reduced-transparency:reduce){ .portada-tema svg{opacity:1;} }
.course-card .body{padding:var(--e2);flex:1;display:flex;flex-direction:column;gap:var(--e1);}
.course-card h3{margin:0;font-size:var(--t-md);line-height:1.3;font-weight:var(--peso-fuerte);}
/* El título de una tarjeta dentro del portal, con la misma voz. */
.tit-tarjeta{margin:var(--e1) 0 var(--e0);font-size:var(--t-md);line-height:1.3;
  font-weight:var(--peso-fuerte);}
/* La nota de un curso en el catálogo. El número va además de la estrella: una
   fila de cinco estrellas obliga a contarlas para saber si son cuatro o cinco, y
   «4,6» se lee de un golpe. La estrella se queda como señal de qué es. */
.nota-curso{display:flex;align-items:baseline;gap:var(--e0);}
.nota-curso .material-symbols-outlined{font-size:15px;color:var(--gold);
  align-self:center;font-variation-settings:'FILL' 1;}
.nota-curso b{font-family:var(--cifra);font-variant-numeric:tabular-nums;
  font-size:var(--t-sm);}

.price{font-size:var(--t-lg);font-weight:var(--peso-fuerte);color:var(--tinta);
  font-family:var(--cifra);font-variant-numeric:tabular-nums;}

/* ───────────────────────────── CAMPANA ────────────────────────────────── */
.icon-btn.campana{position:relative;}
.icon-btn.campana .punto{position:absolute;top:6px;right:6px;width:7px;height:7px;
  border-radius:var(--r-full);background:var(--error);box-shadow:0 0 0 2px var(--fondo);}
/* ─────────────── CONTACTO EN LAS PANTALLAS PÚBLICAS ────────────────────
   Abajo a la izquierda, no a la derecha: en las públicas la derecha es de
   Cemi, y dos burbujas peleándose por la misma esquina es lo que hace que no
   se pulse ninguna. En el teléfono se queda sólo el icono. */
/* 3.8 · Dos separaciones, no una.
   Todas las secciones se separaban exactamente igual, y cuando todo se separa
   igual nada se agrupa: los dos diplomados —que son hermanos y deberían leerse
   como un par— quedaban tan lejos entre sí como del bloque de certificados.
   `franja + franja` del mismo tono son hermanas y se juntan; el cambio de tono
   es lo que marca que empieza otro bloque. */
.franja + .franja{padding-top:var(--e3);}
.franja.tenue + .franja.tenue{padding-top:var(--e3);}

/* La barra de garantías, pegada al hero.
   Va en su propia franja estrecha y sin separación arriba: es la continuación
   del hero, no una sección nueva. */
.franja-garantias{padding:var(--e3) 0;border-bottom:1px solid var(--filete);}
.garantias{list-style:none;margin:0;padding:0;display:grid;gap:var(--e3);
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));}
.garantias li{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;
  column-gap:10px;align-items:center;}
.garantias .material-symbols-outlined{grid-row:1 / span 2;font-size:26px;color:var(--secondary);}
.garantias b{font-size:var(--t-sm);}
.garantias span:not(.material-symbols-outlined){font-size:var(--t-xs);color:var(--tinta-2);}

/* Para quién es: tres perfiles, no adjetivos. */
/* `align-items:stretch` explícito y altura completa: sin esto las tres
   tarjetas quedaban a distinta altura —220, 180 y 180— y con los iconos a
   distinta línea, que es lo que hace que una fila de tarjetas parezca
   desalineada aunque el texto esté bien. */
.perfiles{display:grid;gap:var(--e2);grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  margin-top:var(--e3);align-items:stretch;}
.perfil{height:100%;}
.perfil h3{margin:var(--e1) 0 6px;font-size:var(--t-md);}
.perfil p{margin:0;font-size:var(--t-sm);color:var(--tinta-2);}
.perfil-ico{font-size:28px;color:var(--secondary);}

/* Qué vas a lograr: seis resultados con su marca. Dos columnas en pantalla
   ancha porque son frases cortas y en una sola quedarían seis renglones
   sueltos con medio ancho vacío al lado. */
.logros{list-style:none;margin:var(--e3) 0 0;padding:0;display:grid;gap:var(--e2);
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr));}
.logros li{display:flex;align-items:flex-start;gap:10px;font-size:var(--t-sm);}
.logros .material-symbols-outlined{font-size:20px;color:var(--secondary);flex:none;margin-top:1px;}

/* Quién enseña. Vacío hoy: no hay ni un profesor en el temario y no se
   inventa a una persona. Cuando los haya, cae aquí. */
.profes{display:grid;gap:var(--e2);grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  margin-top:var(--e3);align-items:stretch;}
.profe{height:100%;}
.profe{display:flex;gap:var(--e2);align-items:flex-start;}
.profe img{border-radius:var(--r-full);flex:none;object-fit:cover;}
.profe h3{margin:0 0 4px;font-size:var(--t-md);}
.profe-credencial{margin:0;font-size:var(--t-sm);color:var(--tinta-2);}

/* El CTA repetido, tras el temario. Centrado y con aire, para que se lea como
   una parada y no como el pie de la rejilla. */
.cta-medio{margin-top:var(--e4);text-align:center;}
.cta-medio p{margin:var(--e1) 0 0;}

/* El hero del diplomado, en dos columnas.
   ═══════════════════════════════════════════════════════════════════════════
   Medía 473 px con TODO el contenido en el 45 % izquierdo y ~700 px de
   degradado a la derecha. Ahí va ahora la tarjeta de compra, que es lo que
   ponen Domestika, Hotmart, Whop y Coursera en ese mismo sitio.

   `minmax(0,1fr)` en la columna del texto y no `1fr` a secas: sin el mínimo en
   cero, una palabra larga del titular ensancha la columna y estruja la tarjeta.
   La tarjeta se pide 340 px y no crece: es un panel, no una columna de texto. */
.hero-dos{display:grid;gap:var(--e4);align-items:start;
  grid-template-columns:minmax(0,1fr) 340px;}
.hero-dicho{min-width:0;}

.compra{background:var(--papel);border:1px solid var(--filete);
  border-radius:var(--r-lg,14px);padding:var(--e3);
  box-shadow:var(--sombra-2,0 6px 24px rgba(13,36,64,.10));
  position:sticky;top:88px;}
.compra .ojal{display:block;color:var(--gold);}
.compra-titulo{margin:var(--e0) 0 0;font-weight:var(--peso-fuerte);}

/* El bolívar manda porque es lo que se paga; el resto es contexto. */
.compra-precio{margin:var(--e1) 0 2px;font-family:var(--cifra);
  font-size:var(--t-2xl,32px);font-weight:var(--peso-fuerte);line-height:1.1;
  color:var(--primary);font-variant-numeric:tabular-nums;}
.compra-precio .compra-moneda{font-family:var(--fuente,inherit);font-size:var(--t-lg);
  font-weight:var(--peso-fuerte);margin-right:6px;letter-spacing:0;}
.compra-equivale{margin:0;font-size:var(--t-sm);color:var(--tinta-2);}
.compra-tasa{display:block;font-size:var(--t-xs);color:var(--tinta-3);margin-top:2px;}
.compra-precio-pendiente{margin:var(--e1) 0;font-size:var(--t-sm);color:var(--tinta-2);}

.compra-datos{list-style:none;margin:var(--e2) 0 0;padding:0;display:grid;gap:4px;
  font-size:var(--t-sm);color:var(--tinta-2);}
.compra-cupos{display:flex;align-items:center;gap:6px;margin:var(--e1) 0 0;
  font-size:var(--t-sm);font-weight:var(--peso-fuerte);color:var(--gold);}
.compra-cupos .material-symbols-outlined{font-size:18px;}
.compra-cta{margin-top:var(--e2);}
.compra-nota{margin-top:var(--e1);}

.compra-garantias{list-style:none;margin:var(--e2) 0 0;padding:var(--e2) 0 0;
  border-top:1px solid var(--filete);display:grid;gap:8px;
  font-size:var(--t-sm);color:var(--tinta-2);}
.compra-garantias li{display:flex;align-items:flex-start;gap:8px;}
.compra-garantias .material-symbols-outlined{font-size:18px;color:var(--secondary);
  flex:none;margin-top:1px;}

/* Sin fecha todavía: se dice, y se recoge un correo. Nunca un hueco en blanco. */
.compra-sinfecha{margin:var(--e0) 0 var(--e1);font-weight:var(--peso-fuerte);
  font-size:var(--t-lg);}
.compra-avisame{margin-top:var(--e2);}
.compra-avisame-fila{display:flex;gap:8px;}
.compra-avisame-fila input{flex:1;min-width:0;}
.compra-aviso:empty{display:none;}

@media (max-width:900px){
  /* En el teléfono la tarjeta se apila DEBAJO del título, que es donde la
     ponen las referencias: primero qué es, después cuánto cuesta. */
  .hero-dos{grid-template-columns:minmax(0,1fr);gap:var(--e3);}
  .compra{position:static;}
}

/* La página de contacto, que hasta hoy era un 404. */
.contacto-form{margin-top:var(--e3);}
/* `card` ya trae su relleno; aquí sólo la separación. */
.contacto-otros{margin-top:var(--e3);}
.contacto-otros h2{font-size:var(--t-lg);}
.contacto-lista{list-style:none;margin:0;padding:0;display:grid;gap:var(--e2);}
.contacto-lista li{display:flex;align-items:flex-start;gap:10px;font-size:var(--t-sm);
  color:var(--tinta-2);}
.contacto-lista .material-symbols-outlined{font-size:20px;color:var(--secondary);flex:none;
  margin-top:1px;}

/* El botón de dudas, a la DERECHA.
   ═══════════════════════════════════════════════════════════════════════════
   Vivía abajo a la izquierda, que es donde no lo busca nadie: la convención en
   prácticamente toda la web es la esquina inferior derecha. Y a 1440 px se
   montaba encima de la fila de cifras del hero.

   Cemi ya ocupa la derecha en las pantallas donde está —sólo en las públicas
   que cargan app.js—, así que este botón se sube por encima de ella en vez de
   pelearse por el mismo sitio: 92 px deja libre el redondel de Cemi, que mide
   unos 56 más su margen. En las páginas generadas, donde no hay Cemi, queda
   simplemente un poco más arriba del borde, que no molesta. */
.contacto-flotante{position:fixed;right:18px;bottom:92px;z-index:55;}
.contacto-flotante .contacto-btn{box-shadow:var(--sombra-2,0 6px 20px rgba(0,0,0,.18));
  border-radius:999px;padding:12px 18px;gap:8px;}
.contacto-flotante .contacto-btn:hover{transform:translateY(-1px);}
@media (max-width:640px){
  .contacto-flotante{right:14px;bottom:82px;}
  .contacto-flotante .contacto-txt{display:none;}
  .contacto-flotante .contacto-btn{padding:13px;}
}
@media print{ .contacto-flotante{display:none;} }

/* WhatsApp, con su verde: es el color que la gente reconoce antes de leer. */
.wa-btn, .whatsapp-flotante .contacto-btn{background:#25d366;border-color:#25d366;color:#fff;}
.wa-btn:hover, .whatsapp-flotante .contacto-btn:hover{background:#1ebe5b;border-color:#1ebe5b;color:#fff;}
.whatsapp-flotante .contacto-btn{text-decoration:none;display:inline-flex;align-items:center;}

/* Que los flotantes no se pisen entre ellos ni pisen el pie.
   Con la ventana de Cemi abierta, el «¿Tienes dudas?» se aparta: Cemi ya
   contesta dudas y ya recoge datos, y dos burbujas a la vez sobre una ventana
   de chat es lo que se vio en las capturas de la auditoría. (La promoción ya
   no flota: es una franja debajo de la cabecera, y no estorba a nadie.)
   Y las páginas públicas dejan sitio abajo, para que la última fila de cifras
   y el pie no queden debajo de las burbujas. */
body:has(#cemChat:not([hidden])) .contacto-flotante{display:none;}
body.cem-publico:has(.chat-boton, .contacto-flotante){padding-bottom:88px;}
body.cem-publico:has(.chat-boton, .contacto-flotante) > footer{
  padding-bottom:calc(var(--e4) + 24px);}

/* ─────────────── LAS PREGUNTAS DE LA PORTADA ───────────────────────────
   Un acordeón y no una rejilla de tarjetas: nadie lee cinco respuestas, cada
   uno abre la suya. El tirador es toda la línea, no una flechita de doce
   píxeles, porque esto se usa sobre todo con el pulgar.

   El signo de la derecha se gira al abrir en vez de cambiar de carácter: así
   no salta el ancho de la línea al pulsar. */
/* La columna estrecha empieza donde empiezan las demás.
   ═══════════════════════════════════════════════════════════════════════════
   `.estrecho` recorta la medida a 760 px para que un párrafo largo se lea sin
   barrer la pantalla con la vista. Eso está bien. Lo que estaba mal es que
   heredaba el `margin:0 auto` de `.dentro` y se CENTRABA: en la portada eso
   dejaba tres bordes izquierdos distintos, uno detrás de otro —medidos a
   1440 px de ancho—:

       «Un centro de estudios de Caracas» ... x = 340   (estrecho, centrado)
       «Diplomado en Marketing Digital» ..... x = 120   (dentro, 1200 de ancho)
       las tarjetas de módulo ............... x = 121

   Doscientos veinte píxeles de diferencia entre una sección y la siguiente. La
   página se leía descuadrada, y con razón: no había una línea vertical común
   de la que colgara todo.

   Se estrecha el CONTENIDO, no el contenedor, y esa es la clave. Recortar el
   contenedor a 760 daba una de dos, y las dos se probaron y se midieron:
   centrado, se despegaba del borde común (x=340 contra x=120); pegado a la
   izquierda con `margin-inline:0`, se iba al borde de la franja (x=24) y
   descuadraba igual, sólo que hacia el otro lado. Dejando el contenedor donde
   estaba —1200 px centrados, o sea x=120— y capando lo que lleva dentro, el
   bloque arranca en la misma vertical que todos los demás y el párrafo sigue
   sin pasar de 760.

   `centrado` es para el bloque que SÍ quiere ir en el eje —el del certificado,
   el de «cuándo abre la próxima»—: ahí la columna de 760 se centra dentro de
   los 1200. Lo que ya no pasa es que un texto alineado a la izquierda flote en
   mitad de la franja sin nada con qué alinearse. */
.dentro.estrecho > *{max-width:760px;}
.dentro.estrecho.centrado > *{margin-inline:auto;}
.pregunta{border-bottom:1px solid var(--filete);}
/* La última no lleva raya: dentro de una caja quedaba una línea a dos píxeles
   del borde de abajo, que se lee como un renglón que falta. La raya separa
   preguntas, y después de la última no hay ninguna que separar. */
.pregunta:last-child{border-bottom:0;}
.pregunta > summary{list-style:none;cursor:pointer;padding:var(--e2) 0;
  font-weight:600;display:flex;align-items:center;justify-content:space-between;
  gap:var(--e2);}
.pregunta > summary::-webkit-details-marker{display:none;}
/* 3.4 · Lo contrario de «+» es «−», no «×».
   Se giraba el «+» 45 grados, que da una equis. Y la equis significa cerrar o
   borrar —otra cosa—, en la página que existe justamente para resolver dudas.
   Ahora se cambia el signo en vez de girarlo. */
.pregunta > summary::after{content:'+';font-size:1.35rem;font-weight:400;line-height:1;
  color:var(--tinta-3);}
.pregunta[open] > summary::after{content:'−';}
.pregunta > summary:hover{color:var(--primary);}
.pregunta > summary:focus-visible{outline:2px solid var(--primary);outline-offset:3px;
  border-radius:6px;}
.pregunta > p{margin:0 0 var(--e2);color:var(--tinta-2);max-width:62ch;}

/* El siguiente paso, en la tarjeta de cada curso del panel.
   Se apoya en un fondo tenue y no en un recuadro con borde: es una pista
   dentro de una tarjeta que ya tiene borde, y un borde dentro de otro borde
   hace que la tarjeta parezca dos cosas. */
.paso-siguiente{background:var(--hueco);border-radius:var(--r);
  padding:var(--e1) var(--e2);display:flex;flex-direction:column;gap:2px;}
.paso-siguiente b{font-size:.95rem;line-height:1.3;}

/* El tic de «recibido». Se usa al confirmar un contacto y al terminar una
   compra: la misma señal para la misma idea. */
.tic-ok{width:56px;height:56px;border-radius:50%;display:grid;place-items:center;
  margin:0 auto;background:var(--ok-suave);color:var(--ok);}
.tic-ok .material-symbols-outlined{font-size:32px;}

/* Las pestañas de la campana. Se envuelven en varias líneas en vez de rodar
   de lado: en un diálogo estrecho, una pestaña que hay que arrastrar para ver
   es una pestaña que nadie pulsa. */
/* Se quedan pegadas arriba. Con treinta avisos, unas pestañas que se van con
   el desplazamiento obligan a subir del todo para cambiar de filtro — y con
   ellas fuera de vista, la lista vuelve a parecer un montón sin separar. */
.avisos-tabs{position:sticky;top:calc(var(--e2) * -1);z-index:1;
  display:flex;flex-wrap:wrap;gap:var(--e0);margin-bottom:var(--e2);
  padding:var(--e2) 0;background:var(--papel);border-bottom:1px solid var(--filete);}
.avisos-tabs .btn{gap:6px;}
.avisos-tabs .btn .material-symbols-outlined{font-size:17px;}
/* El número de sin leer, dentro de su pestaña. El fondo se saca del color del
   propio texto rebajado, no de un color fijo: así se lee tanto sobre la
   pestaña activa (sólida) como sobre las demás (de contorno), sin tener que
   escribir dos versiones que acabarían discrepando. */
.pastilla-aviso{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;
  border-radius:9px;background:color-mix(in srgb, currentColor 22%, transparent);
  color:inherit;font-size:var(--t-xs);
  font-weight:var(--peso-fuerte);font-variant-numeric:tabular-nums;}
.avisos{display:flex;flex-direction:column;gap:2px;margin:calc(var(--e0) * -1) calc(var(--e0) * -1) 0;}
.avisos .aviso{display:block;padding:var(--e2);border-radius:var(--r);text-decoration:none;
  color:inherit;border-left:2px solid transparent;}
.avisos .aviso:hover{background:var(--hueco);}
.avisos .aviso.nuevo{border-left-color:var(--primary);background:var(--hueco);}
.avisos .aviso b{display:block;font-size:var(--t-sm);margin-bottom:2px;font-weight:var(--peso-fuerte);}
.avisos .aviso span{display:block;font-size:var(--t-sm);line-height:1.45;color:var(--tinta-2);}
.avisos .aviso em{display:block;font-size:var(--t-xs);font-style:normal;margin-top:var(--e0);
  color:var(--tinta-3);}

/* ─────────────────── DOCUMENTOS IMPRIMIBLES ───────────────────────────── */
.doc-print{max-width:760px;margin:0 auto;}
.doc-print .doc-h{display:flex;justify-content:space-between;align-items:flex-start;
  gap:var(--e2);padding-bottom:var(--e2);border-bottom:1px solid var(--filete-fuerte);
  margin-bottom:var(--e3);}
.doc-print .doc-h .marca{font-weight:var(--peso-fuerte);letter-spacing:.04em;color:var(--tinta);}
.doc-print .doc-tot{display:flex;justify-content:space-between;padding:var(--e1) 0;
  font-size:var(--t-md);}
.doc-print .doc-tot.fuerte{border-top:1px solid var(--filete-fuerte);
  font-weight:var(--peso-fuerte);}

/* La hoja impresa se lleva a una reunión: merece márgenes, cabecera y
   numeración, no sólo esconder el menú. */
@media print{
  @page{margin:18mm 15mm;}
  .sidebar,.topbar,.bottomnav,.no-print,.kpis-mas>summary,.ayuda-btn{display:none !important;}
  .content{padding:0;max-width:none;}
  body{background:#fff;color:#000;font-size:11pt;}
  .card{border:0;padding:0;}
  .card + .card{margin-top:12mm;}
  h2.sec{font-size:12pt;border-bottom:1px solid #999;padding-bottom:3mm;}
  table{font-size:9.5pt;}
  th{background:none;color:#000;border-bottom:1px solid #000;}
  td,th{padding:2mm 3mm;}
  tr,.card,.kpi{break-inside:avoid;}
  a[href]::after{content:'';}
  .doc-print{max-width:none;}
}

/* ═══════════════════════════════════════════════════════════════════════════
   COMPONENTES DE LA PLATAFORMA
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── dinero y cifras ──────────────────────────────────────────────────────
   Una columna de números sólo se compara de un vistazo si están alineados a la
   derecha, todos los dígitos ocupan lo mismo y el encabezado sigue a su
   columna. */
td.num, th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;}
td.num{font-family:var(--cifra);}
/* La misma cifra fuera de una tabla: una lista, un resumen, un contador. */
.cifra{font-family:var(--cifra);font-variant-numeric:tabular-nums;}
/* La moneda de la columna, dicha una vez y en voz baja. */
th .unidad{font-weight:var(--peso);color:var(--tinta-3);text-transform:none;}

/* ── el ancho de las columnas no debe bailar (item 39) ────────────────────
   Al cambiar el filtro, la tabla se repintaba con otros datos y cada columna
   se reacomodaba: la vista entera saltaba aunque no hubiera pasado nada. Las
   columnas de forma conocida —marcar, cifras, acciones— llevan su ancho
   escrito, así el reparto sólo depende del texto libre y las cifras siguen
   cayendo en el mismo sitio de una carga a la otra. */
th.sel, td.sel{width:34px;padding-right:0;}
th.num, td.num{width:9ch;}
thead th:last-child:empty{width:1%;}
td.nowrap, th.nowrap{white-space:nowrap;width:1%;}
/* La fila que se busca no puede pesar lo mismo que las demás. */
tfoot tr.totales td{border-top:2px solid var(--filete-fuerte);border-bottom:0;
  font-weight:var(--peso-fuerte);background:none;padding-top:var(--e2);}
tfoot tr.totales td.num{font-family:var(--cifra);font-variant-numeric:tabular-nums;}
tfoot tr.totales:hover{background:none;}

tr.vencida td{background:color-mix(in srgb, var(--error) 5%, transparent);}
tr.vencida .dias-mora{color:var(--error);font-weight:var(--peso-fuerte);}

/* ── elegir un archivo ────────────────────────────────────────────────── */
.subida{border:1px dashed var(--filete-fuerte);border-radius:var(--r);padding:var(--e3) var(--e2);
  display:flex;flex-direction:column;gap:var(--e1);align-items:center;justify-content:center;
  text-align:center;cursor:pointer;background:var(--hueco);
  transition:border-color var(--rapido), background var(--rapido);min-height:104px;}
.subida:hover{border-color:var(--primary);}
.subida.encima{border-color:var(--primary);background:var(--primary-suave);}
.subida.subiendo{opacity:.7;pointer-events:none;}
.subida-vacia{display:flex;flex-direction:column;gap:var(--e0);align-items:center;}
.subida-vacia .material-symbols-outlined{font-size:28px;color:var(--tinta-3);}
.subida-hecha{display:flex;gap:var(--e2);align-items:center;width:100%;text-align:left;}
.subida-hecha img{width:64px;height:64px;object-fit:cover;border-radius:var(--r);flex:none;
  border:1px solid var(--filete);background:var(--hueco);}
.subida-hecha .t{font-weight:var(--peso-fuerte);font-size:var(--t-sm);word-break:break-word;}
.subida progress{width:100%;height:3px;}
.subida-enlace{margin-top:var(--e0);}
.subida-enlace summary{color:var(--tinta-2);cursor:pointer;padding:var(--e0) 0;font-size:var(--t-xs);}
.subida-enlace input{margin-top:var(--e0);}

/* ── el menú, plegable ───────────────────────────────────────────────── */
.nav-group .lbl{display:flex;align-items:center;justify-content:space-between;width:100%;
  background:none;border:0;cursor:pointer;font-family:inherit;color:var(--tinta-3);
  text-align:left;transition:color var(--rapido);}
.nav-group .lbl:hover{color:var(--tinta-2);}
.nav-group .lbl .flecha{font-size:18px;transition:transform var(--rapido);}
.nav-group:not(.abierto) .lbl .flecha{transform:rotate(-90deg);}
.nav-group:not(.abierto) .nav-items{display:none;}

/* ── dónde estás ─────────────────────────────────────────────────────── */
.migas{display:flex;flex-wrap:wrap;align-items:center;gap:2px;font-size:var(--t-xs);
  color:var(--tinta-3);margin-bottom:var(--e1);}
.migas a{color:var(--tinta-2);text-decoration:none;}
.migas a:hover{color:var(--primary);text-decoration:underline;}
.migas [aria-current]{color:var(--tinta);font-weight:var(--peso-fuerte);}
.migas .sep{font-size:16px;opacity:.5;}

/* ── ordenar pulsando la columna ─────────────────────────────────────── */
th.ordenable{cursor:pointer;user-select:none;white-space:nowrap;transition:color var(--rapido);}
th.ordenable:hover{color:var(--tinta);}
th.ordenable::after{content:'unfold_more';font-family:'Material Symbols Outlined';
  font-size:15px;vertical-align:-3px;margin-left:2px;opacity:.3;}
th.ordenable.asc::after{content:'arrow_upward';opacity:.9;}
th.ordenable.desc::after{content:'arrow_downward';opacity:.9;}
th.num.ordenable::after{margin-left:2px;}

/* ── el «?» de ayuda ─────────────────────────────────────────────────── */
/* Medía 16 px, la mitad que el control más pequeño de al lado, y aun con el
   ratón costaba acertarle. 22 px sigue siendo discreto y ya es un objetivo. */
.ayuda-btn{width:22px;height:22px;min-height:0;border-radius:var(--r-full);
  border:1px solid var(--filete-fuerte);background:none;color:var(--tinta-3);
  font-size:var(--t-xs);line-height:1;cursor:pointer;padding:0;margin-left:var(--e0);
  vertical-align:middle;transition:color var(--rapido),border-color var(--rapido);}
.ayuda-btn:hover{border-color:var(--primary);color:var(--primary);}
.ayuda-globo{position:absolute;z-index:80;max-width:290px;background:var(--inverse-surface);
  color:var(--inverse-on-surface);padding:var(--e1) var(--e2);border-radius:var(--r);
  font-size:var(--t-xs);line-height:1.5;box-shadow:var(--sombra);animation:subir var(--rapido);
  /* El globo puede llevar dos párrafos —el subtítulo de la pantalla y la
     explicación que ya tenía—: los saltos de línea tienen que verse. */
  white-space:pre-line;}

/* ── filtros plegados ────────────────────────────────────────────────── */
.filtros-plegables summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;
  gap:var(--e0);padding:var(--e1) var(--e2);border:1px solid var(--filete);
  border-radius:var(--r);font-size:var(--t-sm);font-weight:var(--peso-fuerte);
  color:var(--tinta-2);min-height:36px;transition:border-color var(--rapido),color var(--rapido);}
.filtros-plegables summary::-webkit-details-marker{display:none;}
.filtros-plegables summary:hover{border-color:var(--filete-fuerte);color:var(--tinta);}
.filtros-plegables summary .material-symbols-outlined{font-size:18px;}
.filtros-plegables .cuantos, .filtros-plegables .chip{background:var(--primary);
  color:var(--on-primary);border-radius:var(--r-full);padding:0 var(--e0);font-size:var(--t-xs);}
.filtros-plegables{flex:none;}
.filtros-plegables[open]{flex-basis:100%;}
/* Cuando lo que se pliega es un bloque entero —tarjetas, gráficos, una tabla—
   y no una fila de filtros, hace falta aire entre el resumen y el contenido.
   Las reglas de abajo sólo cubren `.campos` y `.filters`, que es lo que había. */
.filtros-plegables.bloque[open] > summary{margin-bottom:var(--e3);}
.filtros-plegables[open] .campos,
.filtros-plegables[open] > .filters{margin-top:var(--e2);padding-top:var(--e2);
  border-top:1px solid var(--filete);}

/* ── acciones sobre varias filas ─────────────────────────────────────── */
.barra-seleccion{display:flex;align-items:center;gap:var(--e1);flex-wrap:wrap;
  background:var(--primary-suave);border-radius:var(--r);padding:var(--e1) var(--e2);
  margin-bottom:var(--e2);animation:subir var(--rapido);}
.barra-seleccion .cuenta{font-weight:var(--peso-fuerte);}
td.sel,th.sel{width:32px;text-align:center;padding-left:var(--e1);padding-right:0;}
tr.marcada{background:var(--primary-suave);}

/* ── indicadores secundarios plegados ────────────────────────────────── */
.kpis-mas{margin:calc(var(--e2) * -1) 0 var(--e3);}
.kpis-mas>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;
  gap:var(--e0);font-size:var(--t-xs);font-weight:var(--peso-fuerte);color:var(--tinta-2);
  padding:var(--e0) var(--e1);border-radius:var(--r);transition:background var(--rapido);}
.kpis-mas>summary::-webkit-details-marker{display:none;}
.kpis-mas>summary:hover{background:var(--hueco);color:var(--tinta);}
.kpis-mas>summary .material-symbols-outlined{font-size:18px;transition:transform var(--rapido);}
.kpis-mas[open]>summary .material-symbols-outlined{transform:rotate(180deg);}
.kpis-mas .kpis{margin-top:var(--e2);border-bottom:0;}

/* ── el buscador de arriba, en el teléfono ───────────────────────────── */
.gsearch-cerrar{display:none;position:absolute;right:var(--e0);top:50%;transform:translateY(-50%);
  background:none;border:none;width:32px;height:32px;border-radius:var(--r-full);
  align-items:center;justify-content:center;color:var(--tinta-2);}

/* ── esqueletos mientras cargan los datos ────────────────────────────────
   Antes había una palabra en gris y la pantalla saltaba al llegar la tabla.
   Un bloque del tamaño de lo que viene no salta. */
.hueso{background:var(--hueco);border-radius:var(--r);
  animation:latir 1.4s ease-in-out infinite;}
.hueso.linea{height:12px;margin:var(--e0) 0;}
.hueso.corta{width:40%;} .hueso.media{width:65%;} .hueso.larga{width:88%;}
@keyframes latir{0%,100%{opacity:1;}50%{opacity:.45;}}
.esqueleto-fila td{border-bottom:1px solid var(--filete);}
.esqueleto-fila td > span{display:block;height:12px;background:var(--hueco);border-radius:var(--r);
  animation:latir 1.4s ease-in-out infinite;}
/* Un solo elemento vacío en el HTML dibuja el hueco, así que aparece antes de
   que el navegador haya descargado siquiera el JavaScript. `.una` para cuando
   lo que va a llegar cabe en un renglón. */
.cargando{display:block;}
.cargando::before,.cargando::after{content:'';display:block;height:12px;border-radius:var(--r);
  margin:var(--e1) 0;background:var(--hueco);animation:latir 1.4s ease-in-out infinite;}
.cargando::before{width:88%;} .cargando::after{width:55%;}
.cargando.una::after{display:none;}
/* La tira de cifras reserva su alto desde el principio: nada se desplaza. */
.kpis:empty{min-height:76px;}

/* ── utilidades del sistema ──────────────────────────────────────────────
   Antes eran 660 `style="…"` sueltos dentro del HTML. */
.field.corto{flex:1;min-width:110px;}
.field.medio{flex:1;min-width:150px;}
.field.largo{flex:2;min-width:220px;}
.sep-poco{margin-top:var(--e1);}
.sep{margin-top:var(--e2);}
.sep-mucho{margin-top:var(--e3);}
.sin-margen{margin:0;}
.ok-text{color:var(--ok);}
.err-text{color:var(--error);}
.aviso-text{color:var(--warn);}
.nowrap{white-space:nowrap;}
.negrita{font-weight:var(--peso-fuerte);}
.crece{flex:1;min-width:0;}
/* Un nombre largo se recorta con puntos suspensivos en vez de partir la fila. */
.recorta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:34ch;display:block;}

/* ═══════════════════════ EN EL TELÉFONO ═══════════════════════════════════
   Va al final a propósito: estas reglas deben ganar sobre las de arriba. */
@media (max-width:900px){
  /* iOS hace zoom al enfocar un campo con letra menor de 16px. */
  input,select,textarea{font-size:var(--t-md);}
  /* Área táctil cómoda: 44 px en todo lo que se toca con el pulgar. */
  .btn,.icon-btn,select,input,textarea,.sel-trigger,.chip-btn,
  .filtros-plegables summary{min-height:44px;}
  .btn.sm{min-height:40px;font-size:var(--t-sm);padding:var(--e1) var(--e2);}
  td .acciones .btn.sm{min-width:40px;}
  /* Con el ratón un «?» de 16 px se acierta; con el pulgar, no. En el
     teléfono crece hasta tener tamaño de dedo. */
  .ayuda-btn{width:32px;height:32px;font-size:var(--t-sm);vertical-align:middle;}
  .page-head .actions{width:100%;}
  .card{padding:var(--e2);}
  .kpis{gap:var(--e3);}
}

/* En 390 puntos, cada fila de tabla es una tarjeta: una tabla de diez columnas
   se leía de lado. La caja de fuera sobra —ya no hay dos bordes concéntricos. */
@media (max-width:720px){
  .card:has(> .table-wrap > table.tarjetas){border:0;background:none;padding:0;}
  table.tarjetas thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);}
  table.tarjetas, table.tarjetas tbody, table.tarjetas tr, table.tarjetas td{display:block;width:100%;}
  table.tarjetas tr{border:1px solid var(--filete);border-radius:var(--r);
    margin-bottom:var(--e1);padding:var(--e2);background:var(--papel);}
  table.tarjetas tr:hover{background:var(--papel);}
  table.tarjetas td{border:0;padding:3px 0;display:flex;justify-content:space-between;
    align-items:center;gap:var(--e2);text-align:right;
    /* Las celdas de una tabla llevan `white-space:nowrap` para que las columnas
       no se deshagan al estrecharlas. Apiladas como tarjeta eso es justo lo
       contrario de lo que hace falta: cada fila ocupa el ancho de la pantalla y
       el texto tiene que partirse. Sin esto, una frase larga en una celda
       ensanchaba la tarjeta entera y había que arrastrar de lado —que es
       exactamente lo que apilar venía a evitar. */
    white-space:normal;overflow-wrap:anywhere;min-width:0;}
  table.tarjetas td::before{content:attr(data-col);font-size:var(--t-xs);
    font-weight:var(--peso-fuerte);color:var(--tinta-3);text-align:left;flex:none;}
  table.tarjetas td:first-child{display:block;text-align:left;
    font-weight:var(--peso-fuerte);font-size:var(--t-md);margin-bottom:var(--e0);}
  table.tarjetas td:first-child::before{display:none;}
  table.tarjetas td.sel{display:none;}
  table.tarjetas td .acciones{opacity:1;justify-content:flex-end;width:100%;}
  /* La columna de acciones lleva `nowrap` para que en la tabla ancha los
     botones no se partan de línea. Apilada como tarjeta eso es al revés: cinco
     botones en una sola línea son 340 px dentro de una tarjeta de 342, y la
     pantalla entera se arrastra de lado por catorce píxeles. Aquí se dejan
     fluir a la línea siguiente, que es lo que hace un móvil. */
  table.tarjetas td.nowrap{white-space:normal;flex-wrap:wrap;justify-content:flex-end;}
  /* Con columna de marcar, la casilla no puede quedarse de título de la
     tarjeta: se pone arriba en su propia línea, con su rótulo, y el título
     vuelve a ser la celda siguiente, que es la que identifica la fila. */
  table.tarjetas td.col-marca{display:flex;justify-content:flex-start;gap:var(--e1);
    width:auto;text-align:left;padding:0 0 var(--e1);}
  table.tarjetas td.col-marca::before{display:block;content:attr(data-col);}
  table.tarjetas td.col-marca + td{display:block;text-align:left;
    font-weight:var(--peso-fuerte);font-size:var(--t-md);margin-bottom:var(--e0);}
  table.tarjetas td.col-marca + td::before{display:none;}
  table.tarjetas tfoot tr.totales{background:var(--hueco);}
  .table-wrap{overflow-x:visible;}
  .recorta{max-width:none;white-space:normal;}

  /* El buscador se abre sobre toda la barra; en reposo es sólo la lupa. */
  .topbar .search{flex:0 0 auto;max-width:44px;}
  .topbar .search input{padding-left:34px;padding-right:0;color:transparent;}
  .topbar .search.buscando{position:fixed;inset:0 0 auto 0;height:56px;max-width:none;
    z-index:60;background:var(--papel);display:flex;align-items:center;padding:0 var(--e2);
    border-bottom:1px solid var(--filete);flex:1;}
  .topbar .search.buscando input{padding-left:34px;padding-right:40px;color:var(--tinta);}
  .topbar .search.buscando .gsearch-cerrar{display:inline-flex;right:var(--e2);}
}

/* ═══════════════════════════════════════════════════════════════════════════
   ASPECTO  ·  cada quien configura el suyo desde Configuración
   ═══════════════════════════════════════════════════════════════════════════

   Cuatro cosas se eligen por separado y se combinan sin estorbarse:

     data-paleta   los colores          (los define temas.js)
     data-theme    claro u oscuro
     data-estilo   cómo son los recuadros: siete tratamientos
     data-forma    cuánto se redondean las esquinas
     data-densidad cuánto aire hay entre las cosas

   Dos cosas que conviene tener claras antes de leer los siete estilos:

   1. El vidrio no existe sin algo detrás. Difuminar un fondo liso da un gris
      y ya: para que se lea como cristal hace falta color debajo, y bastante.
      Por eso lo primero que enciende cualquier estilo que no sea `plano` no
      es el desenfoque sino un ambiente —cuatro manchas sacadas de los
      colores de la paleta— sobre el que apoyarse. Cambia con la paleta sin
      escribir un color más.

   2. `backdrop-filter` cuesta caro, y lo que cuesta es repintarlo. En algo
      fijo —el menú, la barra de arriba, un diálogo— se calcula una vez y no
      se vuelve a tocar. En una tabla de trescientas filas se recalcularía en
      cada desplazamiento y la pantalla se arrastraría.

   De ahí que dos de los siete estilos —`canto` y `halo`— estén hechos sin
   desenfoque en las tarjetas: son los únicos que se pueden llevar hasta la
   última fila de una tabla larga sin que se note al desplazar.           */

/* ── lo que todo estilo con vidrio comparte ───────────────────────────── */
:root:not([data-estilo="plano"]){
  --vidrio-papel:color-mix(in srgb, var(--papel) 72%, transparent);
  --vidrio-borde:color-mix(in srgb, var(--papel) 45%, var(--filete-fuerte));
  --vidrio-brillo:rgba(255,255,255,.65);
  --desenfoque:saturate(190%) blur(26px);
  /* Cada estilo rellena estos cuatro; el resto de las reglas los leen. */
  --caja-fondo:color-mix(in srgb, var(--papel) 74%, transparent);
  --caja-sombra:none;
  --caja-filtro:none;
  --caja-borde:0;
}
@media (prefers-color-scheme:dark){
  :root:not([data-estilo="plano"],[data-theme="light"]){
    --vidrio-papel:color-mix(in srgb, var(--papel) 58%, transparent);
    --vidrio-brillo:rgba(255,255,255,.12);
  }
}
:root:not([data-estilo="plano"])[data-theme="dark"]{
  --vidrio-papel:color-mix(in srgb, var(--papel) 58%, transparent);
  --vidrio-brillo:rgba(255,255,255,.12);
}

/* ── la tinta más tenue, sobre vidrio ─────────────────────────────────────
   `--tinta-3` está calibrada para leerse sobre el papel de la paleta. Con
   vidrio el papel deja de ser papel: la tarjeta se tiñe con lo que tiene
   detrás, y ese mismo gris pasaba de 4,6:1 a 3,9. Medido en 77 pantallas, era
   la causa de casi todos los textos que no se leían. Sobre vidrio, entonces,
   el tercer nivel de tinta sube un escalón: sigue siendo más claro que la
   explicación, pero ya no vive al borde del mínimo.

   Va con `html:root` a propósito. La hoja de la paleta la genera temas.js y
   se añade al final del <head>, así que con la misma especificidad ganaría
   ella. Añadir el tipo de elemento la sube lo justo para ganar sin recurrir a
   `!important`. */
html:root:not([data-estilo="plano"]){
  --tinta-3:var(--tinta-2);
}

/* De noche el ambiente es lo contrario de lo que parece: las manchas son
   CLARAS sobre un fondo oscuro, así que una tarjeta translúcida encima se
   aclara y la letra clara pierde el suelo. Medido, la fecha de una tabla
   bajaba a 4,1:1. Bajarle la intensidad al ambiente devuelve el contraste sin
   apagar el efecto. */
/* ── cuánta luz, y quién decide cuánta ───────────────────────────────────
   Dos cosas distintas multiplicándose, y por eso son dos variables:

   · `--ambiente-base` la decide el tema. De noche baja a la mitad, y no por
     gusto: las manchas claras sobre fondo oscuro aclaran las tarjetas
     translúcidas y la letra clara pierde el suelo. Medido en su día, la fecha
     de una tabla bajaba a 4,1:1.
   · `--ambiente-fuerza` la elige cada quien en el panel, entre 0,3 y 1,6.

   Juntarlas en una sola declaración de `opacity` evita el enredo de reglas
   pisándose que había antes, donde «plano» y «oscuro» a la vez dejaban un
   valor u otro según cuál se hubiera escrito después. */
:root{ --ambiente-base:1; }
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){ --ambiente-base:.5; }
}
:root[data-theme="dark"]{ --ambiente-base:.5; }

/* ── el ambiente ────────────────────────────────────────────────────────
   Fijo detrás de todo y sin capturar el ratón. Es lo único que da al vidrio
   algo que difuminar, así que tiene que verse: por debajo de un 30% el
   efecto entero se convierte en un gris caro.

   Llevaba `inset:-25vmax` para que las manchas sangraran por los bordes, y
   eso era un error de bulto: en una ventana de 1400 px, 25vmax son 350 px…
   por cada lado, así que la capa medía 2100 px de ancho y el centro de la
   primera mancha —al 8% de ESA anchura— caía a 182 px de su borde
   izquierdo, es decir 168 px fuera de la pantalla por la izquierda. Las
   cuatro manchas quedaban casi todas fuera y el vidrio se difuminaba contra
   nada: gris caro, exactamente lo que este comentario decía que había que
   evitar. Con `inset:0` los porcentajes son porcentajes de la ventana, que
   es lo que siempre quisieron decir, y el sangrado lo dan los radios. */
:root:not([data-estilo="plano"]) body::before,
:root[data-animacion="si"] body::before{
  content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(60vmax 55vmax at 8% -8%,
      color-mix(in srgb, var(--primary) 58%, transparent), transparent 66%),
    radial-gradient(52vmax 48vmax at 100% 4%,
      color-mix(in srgb, var(--secondary) 54%, transparent), transparent 64%),
    radial-gradient(65vmax 60vmax at 58% 108%,
      color-mix(in srgb, var(--primary) 40%, transparent), transparent 68%),
    radial-gradient(40vmax 38vmax at 28% 54%,
      color-mix(in srgb, var(--secondary) 26%, transparent), transparent 64%);
}
/* `background` a secas aquí borraba la capa de luz, porque la forma corta
   reinicia background-image. Se toca sólo el color. */
:root:not([data-estilo="plano"]) body{background-color:var(--fondo);}
:root:not([data-estilo="plano"]) .content{background:none;}

/* ── lo que está quieto: desenfoque de verdad, igual en los seis ──────────
   El menú y la barra de arriba no se desplazan, así que el desenfoque se
   calcula una vez. Aquí sale gratis y es donde más se nota. */
:root:not([data-estilo="plano"]) .sidebar,
:root:not([data-estilo="plano"]) .topbar,
:root:not([data-estilo="plano"]) .bottomnav,
:root:not([data-estilo="plano"]) .modal{
  background:var(--vidrio-papel);
  -webkit-backdrop-filter:var(--desenfoque);
  backdrop-filter:var(--desenfoque);
}

/* El aviso y el globo de ayuda van al revés que todo lo demás: fondo oscuro,
   letra clara. Al meterlos en la lista de arriba se llevaban el papel
   translúcido —claro— y se quedaban con su letra clara encima: texto casi
   invisible, que es exactamente lo que se veía al aplicar una paleta. Llevan
   vidrio, pero del suyo. */
:root:not([data-estilo="plano"]) .toast,
:root:not([data-estilo="plano"]) .ayuda-globo{
  background:color-mix(in srgb, var(--inverse-surface) 88%, transparent);
  color:var(--inverse-on-surface);
  -webkit-backdrop-filter:var(--desenfoque);
  backdrop-filter:var(--desenfoque);
}
:root:not([data-estilo="plano"]) .sidebar{border-right-color:var(--vidrio-borde);}
:root:not([data-estilo="plano"]) .topbar{border-bottom-color:var(--vidrio-borde);}
:root:not([data-estilo="plano"]) .bottomnav{border-top-color:var(--vidrio-borde);}

/* El canto superior encendido es lo que convierte una caja translúcida en una
   lámina de cristal. Es el brillo del borde, no el desenfoque. */
:root:not([data-estilo="plano"]) .modal{
  border:1px solid var(--vidrio-borde);
  box-shadow:inset 0 1px 0 var(--vidrio-brillo), var(--sombra);
}
:root:not([data-estilo="plano"]) .modal-bg{
  background:color-mix(in srgb, var(--tinta) 26%, transparent);
  -webkit-backdrop-filter:blur(4px);
  backdrop-filter:blur(4px);
}

/* ── `.caja`: el nombre público del contrato ──────────────────────────────
   Los ocho estilos no son ocho hojas de CSS: son ocho juegos de estas cuatro
   variables —`--caja-fondo`, `--caja-filtro`, `--caja-sombra`, `--r-caja`—
   puestas en `:root`. Lo que decide si algo cambia de aspecto no es la
   pantalla en la que esté, sino si CONSUME esas variables.

   Durante mucho tiempo sólo las consumían `.card` y `.kpi`. Cualquier pantalla
   que se dibujara su propio recuadro —y hay una veintena que lo hacen, con su
   `<style>` local y su clase propia— quedaba fuera del sistema sin que nada lo
   dijera: se cambiaba de estilo y esa pantalla no se enteraba. En Formas de
   pago, que no usa `.card` ni una sola vez, no cambiaba absolutamente nada.

   `.caja` es ese contrato con nombre. Un recuadro propio añade `caja` a su
   clase y ya está dentro: no tiene que repetir fondo, filete ni esquinas, y
   los ocho estilos le llegan solos. La comprobación estática nº 10 avisa de
   los recuadros que se dibujan por su cuenta sin declararlo. */
.caja{background:var(--papel);border:1px solid var(--filete);
  border-radius:var(--r);}

/* ── la caja: una sola regla, siete rellenos ──────────────────────────────
   `.card` y `.kpi` se pintan igual porque son lo mismo: un dato dentro de un
   recuadro. Los indicadores no tenían caja —una cifra grande no la
   necesita— y con vidrio la recuperan, que es justo lo que se pidió. */
:root:not([data-estilo="plano"]) :is(.card,.caja),
:root:not([data-estilo="plano"]) .kpi{
  position:relative;
  border:var(--caja-borde);
  border-radius:var(--r-caja, var(--r));
  background:var(--caja-fondo);
  box-shadow:var(--caja-sombra);
  -webkit-backdrop-filter:var(--caja-filtro);
  backdrop-filter:var(--caja-filtro);
}
:root:not([data-estilo="plano"]) .kpi{
  padding:var(--e2);
  min-width:170px;
}
/* Con caja, la fila de indicadores ya no necesita el filete que la cerraba:
   dos separadores para lo mismo es uno de más. */
:root:not([data-estilo="plano"]) .kpis{border-bottom:0;padding-bottom:0;}
/* Una tarjeta dentro de otra no se desenfoca dos veces: se vería un halo
   turbio en el borde interior. */
:root:not([data-estilo="plano"]) :is(.card,.caja) :is(.card,.caja){
  -webkit-backdrop-filter:none;backdrop-filter:none;
  background:color-mix(in srgb, var(--papel) 40%, transparent);
}
/* La tabla suelta dentro de una tarjeta ya se quita el marco; con vidrio hay
   que quitarle también el fondo, o queda un rectángulo opaco flotando. */
:root:not([data-estilo="plano"]) :is(.card,.caja) > .table-wrap:only-child,
:root:not([data-estilo="plano"]) :is(.card,.caja).solo-tabla{
  background:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;}

:root:not([data-estilo="plano"]) .inp,
:root:not([data-estilo="plano"]) input:not([type=checkbox],[type=radio],[type=range],[type=file],
  [type=color],[type=submit],[type=button],[type=reset],[type=image],[type=hidden]),
:root:not([data-estilo="plano"]) select,
:root:not([data-estilo="plano"]) textarea,
:root:not([data-estilo="plano"]) .btn.outline{
  background:color-mix(in srgb, var(--papel) 48%, transparent);
  border-color:var(--vidrio-borde);
}

/* La tabla se queda opaca: encima van los datos, y un dato sobre un fondo que
   cambia se lee peor. El encabezado está fijo, así que además necesita tapar
   lo que le pasa por debajo. */
:root:not([data-estilo="plano"]) th{background:color-mix(in srgb, var(--papel) 90%, transparent);}
:root:not([data-estilo="plano"]) tbody tr:hover{background:color-mix(in srgb, var(--hueco) 70%, transparent);}

/* ── 1 · escarcha ─────────────────────────────────────────────────────────
   El vidrio de manual: translúcido, desenfoque suave y un filete de un
   píxel. Discreto y correcto. */
:root[data-estilo="escarcha"]{
  --caja-fondo:color-mix(in srgb, var(--papel) 74%, transparent);
  --caja-filtro:saturate(150%) blur(14px);
  --caja-sombra:inset 0 0 0 1px var(--vidrio-borde), inset 0 1px 0 var(--vidrio-brillo);
}

/* ── 2 · marco de vidrio ──────────────────────────────────────────────────
   Un aro grueso translúcido y, dentro, un panel algo más claro con el canto
   difuminado: acrílico esmerilado sobre acrílico. El relleno interior es un
   ::before desenfocado, así que el contenido tiene que ir por encima. */
:root[data-estilo="marco"]{
  --r-caja:calc(var(--r) * 2);
  --caja-fondo:color-mix(in srgb, var(--papel) 62%, transparent);
  --caja-filtro:saturate(165%) blur(17px);
  --caja-sombra:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
                inset 0 0 0 1px color-mix(in srgb, #fff 22%, transparent),
                0 20px 44px -26px rgba(13,36,64,.55);
}
:root[data-estilo="marco"] :is(.card,.caja),
:root[data-estilo="marco"] .kpi{padding:calc(var(--e2) + 7px);}
:root[data-estilo="marco"] :is(.card,.caja)::before,
:root[data-estilo="marco"] .kpi::before{
  content:'';position:absolute;inset:7px;border-radius:var(--r);z-index:0;
  background:color-mix(in srgb, #fff 26%, transparent);
  filter:blur(7px);pointer-events:none;}
:root[data-estilo="marco"] :is(.card,.caja) > *,
:root[data-estilo="marco"] .kpi > *{position:relative;z-index:1;}
@media (prefers-color-scheme:dark){
  :root[data-estilo="marco"]:not([data-theme="light"]){
    --caja-fondo:color-mix(in srgb, var(--papel) 42%, transparent);
    --caja-sombra:inset 0 1px 0 color-mix(in srgb, #fff 16%, transparent),
                  inset 0 0 0 1px color-mix(in srgb, #fff 8%, transparent),
                  0 20px 44px -26px rgba(0,0,0,.7);}
  :root[data-estilo="marco"]:not([data-theme="light"]) :is(.card,.caja)::before,
  :root[data-estilo="marco"]:not([data-theme="light"]) .kpi::before{
    background:color-mix(in srgb, #fff 9%, transparent);}
}
:root[data-estilo="marco"][data-theme="dark"]{
  --caja-fondo:color-mix(in srgb, var(--papel) 42%, transparent);
  --caja-sombra:inset 0 1px 0 color-mix(in srgb, #fff 16%, transparent),
                inset 0 0 0 1px color-mix(in srgb, #fff 8%, transparent),
                0 20px 44px -26px rgba(0,0,0,.7);}
:root[data-estilo="marco"][data-theme="dark"] :is(.card,.caja)::before,
:root[data-estilo="marco"][data-theme="dark"] .kpi::before{
  background:color-mix(in srgb, #fff 9%, transparent);}

/* ── 3 · bisel ────────────────────────────────────────────────────────────
   Luz arriba a la izquierda, sombra abajo a la derecha: el canto se comporta
   como una pieza con grosor. Es el único de los siete que hace que el
   recuadro parezca tocable. */
:root[data-estilo="bisel"]{
  --r-caja:calc(var(--r) * 1.6);
  --caja-fondo:linear-gradient(158deg,
    color-mix(in srgb, var(--papel) 86%, transparent),
    color-mix(in srgb, var(--papel) 62%, transparent));
  --caja-filtro:saturate(180%) blur(20px);
  --caja-sombra:inset 1.5px 1.5px 0 color-mix(in srgb, #fff 65%, transparent),
                inset -1px -1px 0 color-mix(in srgb, var(--tinta) 14%, transparent),
                inset 0 -16px 22px -20px color-mix(in srgb, var(--tinta) 55%, transparent),
                0 12px 32px -20px rgba(13,36,64,.5);
}
/* De noche la tarjeta del bisel se aclaraba hasta (68,58,92) y la tinta más
   tenue se quedaba en 4,1:1. Con más cuerpo el degradado sigue viéndose y el
   texto recupera su suelo. */
@media (prefers-color-scheme:dark){
  :root[data-estilo="bisel"]:not([data-theme="light"]){
    --caja-fondo:linear-gradient(158deg,
      color-mix(in srgb, var(--papel) 94%, transparent),
      color-mix(in srgb, var(--papel) 78%, transparent));
    --caja-sombra:inset 1.5px 1.5px 0 color-mix(in srgb, #fff 18%, transparent),
                  inset -1px -1px 0 rgba(0,0,0,.35),
                  inset 0 -16px 22px -20px rgba(0,0,0,.6),
                  0 12px 32px -20px rgba(0,0,0,.7);}
}
:root[data-estilo="bisel"][data-theme="dark"]{
  --caja-fondo:linear-gradient(158deg,
    color-mix(in srgb, var(--papel) 94%, transparent),
    color-mix(in srgb, var(--papel) 78%, transparent));
  --caja-sombra:inset 1.5px 1.5px 0 color-mix(in srgb, #fff 18%, transparent),
                inset -1px -1px 0 rgba(0,0,0,.35),
                inset 0 -16px 22px -20px rgba(0,0,0,.6),
                0 12px 32px -20px rgba(0,0,0,.7);}

/* ── 4 · canto tallado ────────────────────────────────────────────────────
   El marco es un degradado de píxel y medio recortado con máscara: brilla
   arriba y se tiñe de la paleta abajo. Ni una gota de backdrop-filter, así
   que éste sí puede llegar hasta la tabla. */
:root[data-estilo="canto"]{
  --r-caja:calc(var(--r) * 1.4);
  --caja-fondo:linear-gradient(152deg,
    color-mix(in srgb, var(--papel) 94%, transparent),
    color-mix(in srgb, var(--papel) 78%, transparent));
  --caja-sombra:0 14px 32px -22px rgba(13,36,64,.45);
}
:root[data-estilo="canto"] :is(.card,.caja)::after,
:root[data-estilo="canto"] .kpi::after,
:root[data-estilo="canto"] .table-wrap::after{
  content:'';position:absolute;inset:0;border-radius:inherit;padding:1.5px;
  background:linear-gradient(142deg,
    color-mix(in srgb, #fff 88%, transparent),
    color-mix(in srgb, #fff 4%, transparent) 46%,
    color-mix(in srgb, var(--primary) 50%, transparent));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none;}
/* La tabla suelta se lleva su propio canto, que es lo que este estilo puede
   permitirse y los demás no. */
:root[data-estilo="canto"] :is(.card,.caja) > .table-wrap:only-child,
:root[data-estilo="canto"] :is(.card,.caja).solo-tabla{
  position:relative;border-radius:var(--r-caja);overflow:hidden;
  background:var(--caja-fondo);}

/* ── 5 · halo ─────────────────────────────────────────────────────────────
   Sin marco visible: un resplandor del color de la paleta se escapa por
   detrás y el recuadro flota. El más callado, y el que menos estorba cuando
   hay quince cajas en la misma pantalla. */
:root[data-estilo="halo"]{
  --r-caja:calc(var(--r) * 1.4);
  --caja-fondo:color-mix(in srgb, var(--papel) 84%, transparent);
  --caja-filtro:blur(10px);
  --caja-sombra:0 0 0 1px color-mix(in srgb, var(--papel) 40%, transparent),
                0 0 38px -6px color-mix(in srgb, var(--primary) 32%, transparent),
                0 16px 36px -26px rgba(13,36,64,.4);
}

/* ── 6 · vidrio tintado ───────────────────────────────────────────────────
   Cada cifra se lleva su color. Lo que sube va en verde y lo que baja en
   rojo —eso lo dice el dato, no la decoración—; el resto rota por la paleta
   para que dos indicadores seguidos no se confundan. Las tarjetas se quedan
   en el color de la paleta: tintarlas todas sería una feria. */
:root[data-estilo="tintado"]{
  --tinte:var(--primary);
  --r-caja:calc(var(--r) * 1.4);
  /* El tinte se mezcla contra un papel que ya lleva transparencia, así que
     cada punto porcentual pesa menos de lo que parece: con un 20% sobre
     blanco el color no llegaba a verse y las cuatro cifras salían grises. */
  --caja-fondo:linear-gradient(155deg,
    color-mix(in srgb, var(--tinte) 22%, color-mix(in srgb, var(--papel) 80%, transparent)),
    color-mix(in srgb, var(--tinte) 9%,  color-mix(in srgb, var(--papel) 70%, transparent)));
  --caja-filtro:saturate(170%) blur(16px);
  --caja-sombra:inset 0 0 0 1px color-mix(in srgb, var(--tinte) 55%, transparent),
                inset 0 1px 0 color-mix(in srgb, #fff 45%, transparent),
                0 14px 32px -22px color-mix(in srgb, var(--tinte) 75%, rgba(13,36,64,1));
}
:root[data-estilo="tintado"] .kpis .kpi:nth-child(4n+2){--tinte:var(--secondary);}
:root[data-estilo="tintado"] .kpis .kpi:nth-child(4n+3){--tinte:var(--info);}
:root[data-estilo="tintado"] .kpis .kpi:nth-child(4n+4){--tinte:var(--gold);}
/* El color que sale del dato manda sobre el que sale de la posición. */
:root[data-estilo="tintado"] .kpi:has(.k-sub.up:not(.cero)){--tinte:var(--ok);}
:root[data-estilo="tintado"] .kpi:has(.k-sub.down:not(.cero)){--tinte:var(--error);}

/* ── forma: cuánto se redondea todo ───────────────────────────────────────
   Un solo token gobierna las esquinas de la plataforma entera, así que aquí
   sólo hay que reescribirlo. */
:root[data-forma="recta"]{--r:3px;}
:root[data-forma="suave"]{--r:10px;}
:root[data-forma="redonda"]{--r:18px;}

/* ── densidad: cuánto aire hay entre las cosas ────────────────────────────
   No toca la escala de espaciado entera —eso descuadraría cuadrículas y
   menús—, sólo el relleno de lo que se repite cientos de veces: las celdas
   de la tabla, las tarjetas y las filas de lista. Quien revisa pagos todo el
   día quiere ver diez filas más; quien entra dos veces por semana prefiere
   respirar. */
:root[data-densidad="compacta"]{--aire:var(--e3);}
:root[data-densidad="compacta"] th,
:root[data-densidad="compacta"] td{padding:calc(var(--e1) * .55) var(--e2);}
:root[data-densidad="compacta"] .card{padding:var(--e2);}
:root[data-densidad="compacta"] .list-item{padding:var(--e1) 0;}
:root[data-densidad="compacta"] .kpi{padding:var(--e1) var(--e2);}

:root[data-densidad="amplia"]{--aire:var(--e5);}
:root[data-densidad="amplia"] th,
:root[data-densidad="amplia"] td{padding:var(--e2);}
:root[data-densidad="amplia"] .card{padding:var(--e4);}
:root[data-densidad="amplia"] .list-item{padding:var(--e2) 0;}
:root[data-densidad="amplia"] .kpi{padding:var(--e3);}

/* ── el ambiente, moviéndose ──────────────────────────────────────────────
   Las cuatro manchas de color estaban quietas. Girándolas y respirándolas muy
   despacio, las de los bordes se desplazan bastante y las del centro casi
   nada, y el fondo deja de parecer una imagen para parecer luz.

   POR QUÉ SÓLO `transform`. Es lo único que el navegador puede componer sin
   volver a pintar. Animar `background-position` sobre cuatro degradados a
   pantalla completa repinta toda la ventana en cada fotograma, y eso sí se
   nota en un portátil con una tabla de trescientas filas abierta. Con una
   transformación sobre una capa fija y aislada, el coste es de la tarjeta
   gráfica y no se nota en ninguno.

   POR QUÉ SE AMPLÍA. Al girar un rectángulo, sus esquinas dejan de tapar las
   de la ventana: hace falta ampliarlo al menos `cos θ + sen θ`. Con la vuelta
   entera se pasa por 45° cuatro veces por ciclo, donde eso vale √2 ≈ 1,414; la
   escala no baja de 1,70, así que cubre con holgura en el peor momento. Sin
   esa cuenta asoma una cuña del fondo pelado por una esquina cada pocos
   segundos, que es el error clásico de esta animación. La prueba ya no mira un
   instante suelto: vigila el ciclo completo y se queda con el peor.

   POR QUÉ ESTAS CANTIDADES Y NO MENOS. Costó cuatro intentos, y los tres
   primeros fallaron por el mismo motivo: medían que la PROPIEDAD cambiaba.
   Pasaban en verde mientras la pantalla no se movía. Restando píxeles de dos
   fotos, el vaivén de ±14° daba 8 de 765 en cinco segundos con todo al máximo
   —menos de tres niveles de 255 por canal—, o sea invisible. La vuelta entera
   da 143, y el recorrido pasó de 13,9 a 137 px/s.

   La lección, que vale para más cosas: «la propiedad cambió» no es «se ve», y
   la única forma de saberlo es fotografiar la pantalla y restar. */
/* La vuelta entera, no un temblor.
   ───────────────────────────────────────────────────────────────────────────
   Antes esto iba y venía entre −14° y +14°. Con las manchas colocadas fuera del
   centro, girar la capa entera 360° las hace ORBITAR: cada punto de la pantalla
   pasa por todas ellas a lo largo del ciclo, en vez de ver siempre la misma
   temblando un poco. Es la diferencia entre que algo vibre y que algo se mueva.

   Dos cosas que cambian con la vuelta entera y no son opcionales:

   1 · La escala. Al girar un rectángulo, sus esquinas dejan de tapar las de la
       ventana y asoma una cuña de fondo pelado. Hace falta `cos θ + sen θ`,
       que a 45° vale √2 ≈ 1,414 — y con la vuelta entera se pasa por 45° sí o
       sí, cuatro veces por ciclo. Por eso la escala mínima es 1,70: cubre el
       peor caso con margen de sobra. El vaivén de antes no llegaba nunca a 45°
       y podía permitirse 1,30.

   2 · El desplazamiento se va. Estaba ahí para compensar que el giro era
       pequeño; con la vuelta entera sobra, y sumado al giro obligaría a
       ampliar todavía más para no descubrir los bordes.

   Y el reparto del tiempo tiene que ser `linear`. Con `ease-in-out`, una vuelta
   completa acelera a mitad de ciclo y frena al llegar al final, así que en vez
   de girar parecería que da tirones. */
@keyframes cem-ambiente {
  0%   { transform:rotate(0turn)    scale(1.70); }
  50%  { transform:rotate(.5turn)   scale(1.88); }
  100% { transform:rotate(1turn)    scale(1.70); }
}
/* Con «Plano» la capa existe SÓLO para esto, así que va más tenue: el estilo
   plano tiene que seguir siendo plano. Lo que se busca ahí es un rastro de luz
   moviéndose, no una tarjeta de vidrio. */
:root[data-animacion="si"][data-estilo="plano"]{ --ambiente-base:.55; }
@media (prefers-color-scheme:dark){
  :root[data-animacion="si"][data-estilo="plano"]:not([data-theme="light"]){ --ambiente-base:.3; }
}
:root[data-animacion="si"][data-estilo="plano"][data-theme="dark"]{ --ambiente-base:.3; }

:root:not([data-estilo="plano"]) body::before,
:root[data-animacion="si"] body::before{
  /* `opacity` se recorta sola a [0,1], así que no hace falta un `clamp`. */
  opacity:calc(var(--ambiente-base, 1) * var(--ambiente-fuerza, 1));
}

/* ── la capa que se mueve, aparte de la que está quieta ───────────────────
   Durante tres intentos esto animó la MISMA capa que hace de vidrio detrás de
   las tarjetas, y por eso no se podía subir: tocarla cambiaba el aspecto de
   los seis estilos de vidrio. El resultado fue un movimiento que existía y no
   se veía. Medido, no supuesto: se fotografió la franja de fondo que asoma al
   lado de las tarjetas y se restaron los píxeles. Con todo al máximo, la
   pantalla cambiaba 8,26 de 765 en cinco segundos — menos de tres niveles de
   255 por canal, repartidos en cinco segundos. Invisible, literalmente.

   La causa no era la velocidad ni la intensidad: era QUÉ se movía. Manchas de
   60vmax difuminadas hasta el 66% no tienen borde, y el ojo detecta movimiento
   por bordes. Multiplicar por dos algo sin bordes sigue sin dar bordes.

   Así que el movimiento se va a su propia capa, con manchas más pequeñas,
   más apretadas y con más color, y con un recorrido mucho mayor. La capa
   quieta —el vidrio— se queda exactamente como estaba. */
:root[data-animacion="si"] body::after{
  content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(26vmax 24vmax at 12% 18%,
      color-mix(in srgb, var(--primary) 85%, transparent), transparent 52%),
    radial-gradient(22vmax 20vmax at 88% 12%,
      color-mix(in srgb, var(--secondary) 80%, transparent), transparent 50%),
    radial-gradient(28vmax 25vmax at 72% 88%,
      color-mix(in srgb, var(--primary) 70%, transparent), transparent 54%),
    radial-gradient(20vmax 18vmax at 30% 82%,
      color-mix(in srgb, var(--secondary) 62%, transparent), transparent 50%);
  opacity:calc(var(--ambiente-movil, .42) * var(--ambiente-fuerza, 1));
  /* En propiedades sueltas y no en el atajo `animation`. El atajo con una
     variable dentro para la duración lo interpretan mal algunos navegadores, y
     ahí el fondo se queda quieto sin que nada lo explique. */
  animation-name:cem-ambiente;
  animation-duration:var(--ambiente-ciclo, 62s);
  /* `linear` y no `ease-in-out`: ver la nota de los fotogramas. Una vuelta
     completa con aceleración se ve dando tirones, no girando. */
  animation-timing-function:linear;
  animation-iteration-count:infinite;
  will-change:transform;
}
/* Con «Plano» esta capa es lo único que hay, así que puede permitirse algo más
   sin dejar de ser plano: no hay vidrio debajo con el que competir. */
:root[data-animacion="si"][data-estilo="plano"]{ --ambiente-movil:.5; }
@media (prefers-color-scheme:dark){
  :root[data-animacion="si"]:not([data-theme="light"]){ --ambiente-movil:.3; }
}
:root[data-animacion="si"][data-theme="dark"]{ --ambiente-movil:.3; }

/* Quien pidió menos movimiento en su sistema lo pidió por algo, y por eso el
   fondo le viene APAGADO: esa decisión la toma `animacionActual()` al arrancar,
   leyendo la preferencia del sistema.

   Pero si esa persona entra al panel y lo enciende, ya dijo lo que quiere. Sin
   estas dos líneas no habría forma: arriba del todo hay un reinicio global con
   `!important` sobre `*, *::before, *::after` que deja toda animación en
   0,01 ms y una sola vuelta. Encendido y quieto, y encima con el mando de la
   intensidad funcionando —porque la opacidad no es una animación— que es la
   peor combinación posible: parece que el ajuste hace algo a medias. */
@media (prefers-reduced-motion:reduce){
  :root[data-animacion="si"] body::after{
    animation-duration:var(--ambiente-ciclo, 62s) !important;
    animation-iteration-count:infinite !important;
  }
}

/* ── cuándo NO ponerlo, aunque esté elegido ───────────────────────────────
   Quien pidió menos transparencia en su sistema lo pidió por algo, y en el
   papel un fondo difuminado es tinta gastada. */
@media (prefers-reduced-transparency:reduce), print{
  :root:not([data-estilo="plano"]) body::before,
  :root[data-animacion="si"] body::before,
  :root[data-animacion="si"] body::after{display:none;}
  :root:not([data-estilo="plano"]) .sidebar,
  :root:not([data-estilo="plano"]) .topbar,
  :root:not([data-estilo="plano"]) .bottomnav,
  :root:not([data-estilo="plano"]) .modal,
  :root:not([data-estilo="plano"]) .toast,
  :root:not([data-estilo="plano"]) .ayuda-globo,
  :root:not([data-estilo="plano"]) :is(.card,.caja),
  :root:not([data-estilo="plano"]) .kpi,
  :root:not([data-estilo="plano"]) th{
    background:var(--papel);-webkit-backdrop-filter:none;backdrop-filter:none;
    box-shadow:none;}



  :root:not([data-estilo="plano"]) :is(.card,.caja)::before,
  :root:not([data-estilo="plano"]) :is(.card,.caja)::after,
  :root:not([data-estilo="plano"]) .kpi::before,
  :root:not([data-estilo="plano"]) .kpi::after{display:none;}
}

/* Este bloque va DESPUÉS de todo lo del ambiente a propósito.
   ─────────────────────────────────────────────────────────────────────────
   La primera versión estaba más arriba y no se aplicaba NADA: misma
   especificidad que la regla base, así que ganaba la que venía después. Se
   veía exactamente igual que antes y costó dos intentos entender que el CSS
   ni siquiera estaba llegando. Si alguien mueve esto hacia arriba, el
   escaparate se apaga sin dar ningún error. */
/* ── el escaparate: blanco, y la marca dando vueltas encima ───────────────
   Sólo en las pantallas públicas. Dentro de la plataforma el fondo tiene que
   dejar trabajar; aquí tiene que llamar la atención, y además puede: no hay
   barra lateral ni una rejilla de tarjetas tapando la pantalla entera, así que
   por primera vez el movimiento se ve de verdad.

   Blanco de base y las manchas ENCIMA, no mezcladas: sobre blanco, un color
   saturado se ve saturado. Si el blanco se pusiera encima con transparencia,
   todo acabaría en el mismo pastel de siempre.

   Y responde al ratón. Es la única parte de la página que se mueve por lo que
   hace la persona, así que es donde ese gesto se nota. Muy poco recorrido —un
   3%— porque un fondo que persigue el cursor marea; lo justo para que se
   perciba que la página está viva. */
/* Los mismos tonos de la marca, a plena intensidad.
   ───────────────────────────────────────────────────────────────────────
   El azul del CEM es #0d2440 y el verde #0b7268: colores OSCUROS, elegidos
   para llevar texto encima. Sobre blanco y a media opacidad no dan color,
   dan gris — se probó dos veces, y las dos veces la franja parecía una nube
   sucia. Subirles la saturación con un filtro tampoco arregla nada: no se
   puede sacar color de donde no lo hay.

   Estos son los MISMOS tonos —el mismo azul, el mismo verde, el mismo
   dorado— con la luminosidad y el croma que hacen falta para que se vean
   como color sobre blanco. No es otra paleta: es la de la casa encendida.
   Sólo viven aquí, en el escaparate; dentro de la plataforma no se usan. */
/* Dos atributos y no uno: `[data-animacion][data-estilo="plano"]` pone
   `--ambiente-movil:.5` y con un solo atributo esta regla perdía por
   especificidad. El síntoma era que la intensidad salía a la mitad sin que
   nada lo dijera: la variable decía 1,6 y la opacidad salía como si fuera 0,8. */
:root[data-publico="si"][data-animacion="si"],
:root[data-publico="si"]{
  --ambiente-movil:1;
  --vivo-azul:#1f5fd8;
  --vivo-verde:#06b6a4;
  --vivo-oro:#f0a913;
  --vivo-indigo:#6b4ce0;
}
:root[data-publico="si"] body{ background:#fff; }

/* …y con el blanco, la tinta oscura. Las dos cosas o ninguna.
   ═══════════════════════════════════════════════════════════════════════════
   Esta era la pantalla rota. El escaparate fija el fondo en blanco —la regla
   de arriba, sin condiciones— pero los TOKENS seguían obedeciendo al sistema
   operativo: quien tiene el Mac en modo oscuro se encontraba `--tinta:#e9ecef`
   y `--primary:#a9c6ec` —letra clara, pensada para fondo negro— escritas sobre
   ese blanco. Titulares que no se leen, tarjetas oscuras flotando en una
   página clara, botones lavados. De día no se notaba nunca, porque #fff y
   #f7f8fa se parecen: por eso llevaba tanto tiempo ahí.

   El escaparate ya se pintaba claro a la fuerza, pero lo hacía desde temas.js,
   que pone `data-theme="light"` al arrancar. Eso deja fuera dos casos, y los
   dos son visibles: la portada del dominio y las páginas generadas no cargan
   app.js —ni existe ese `data-theme`—, y en las que sí lo cargan hay un
   instante entre que llega esta hoja y que corre el módulo. Un escaparate no
   puede depender de que un módulo llegue para no verse roto.

   Así que se dice aquí, en CSS, junto a la regla del fondo blanco que lo
   provocaba. `color-scheme:light` va con ellas porque las barras de
   desplazamiento y los controles del navegador no leen tokens: sin eso salían
   negras al borde de una página blanca. */
@media (prefers-color-scheme:dark){
  :root[data-publico="si"]:not([data-theme="dark"]){
    color-scheme:light;
    --fondo:#f7f8fa; --papel:#ffffff; --hueco:#eef0f3;
    --tinta:#15181c; --tinta-2:#5a616b; --tinta-3:#6c7179;
    --filete:#dfe3e8; --filete-fuerte:#c2c8d0;
    --primary:#0d2440; --primary-suave:#e8edf5; --on-primary:#fff;
    --secondary:#0b7268; --secondary-container:#d6f2ee;
    --gold:#856517; --gold-suave:#f6ecd2; --on-gold:#ffffff;
    --ok:#166534;    --ok-suave:#e3f2e7;
    --warn:#9a5b06;  --warn-suave:#fbeed8;
    --error:#a51d1d; --error-suave:#fbe4e3;
    --info:#1f4fa8;  --info-suave:#e4ebf9;
    --sombra:0 1px 2px rgba(16,24,40,.06), 0 8px 24px -12px rgba(16,24,40,.18);
    --inverse-surface:#22262c; --inverse-on-surface:#f2f4f6;
  }
}

/* En el escaparate pinta UNA capa, no dos. La otra era el barro.
   ═══════════════════════════════════════════════════════════════════════════
   El fondo de la casa son dos capas: `::before`, la de la plataforma, hecha
   con `--primary` y `--secondary` —el azul y el verde institucionales, que son
   oscuros—; y `::after`, la del escaparate, hecha con los `--vivo-*`, que son
   esos mismos tonos encendidos para que se lean como color sobre blanco.

   En lo público se pintaban las dos, una encima de la otra, y el resultado era
   la nube sucia que este archivo lleva advirtiendo desde hace páginas. Medido
   en la esquina superior izquierda de la portada, que es donde peor se veía:

     las dos juntas ............ (155,172,197)  saturación 21 %, y OSCURO
     sólo `::before` ........... (177,185,194)  saturación  9 %  ← el gris
     sólo `::after` ............ (210,223,247)  saturación 15 %, claro y azul

   O sea: la capa institucional no aportaba color —nueve por ciento es gris—,
   sólo aportaba suciedad y peso, y arrastraba hacia el gris a la que sí tenía
   color. Quitarla deja el blanco limpio con la luz de la marca encima, que es
   justo lo que el escaparate quería ser.

   Va aquí y no en temas.js a propósito: la portada del dominio y las páginas
   de programa no cargan app.js, y eran las que peor se veían. */
:root[data-publico="si"] body::before{ display:none; }

:root[data-publico="si"] body::after{
  /* La capa se declara ENTERA aquí, y esto era el otro medio fallo.
     ─────────────────────────────────────────────────────────────────────
     Antes esta regla sólo ponía el fondo y la opacidad, y heredaba `content`,
     `position` e `inset` de `:root[data-animacion="si"] body::after`. Sin
     `content` no hay pseudoelemento: en las 24 páginas que genera el SEO —la
     portada del dominio incluida— `data-animacion` no existe, porque no cargan
     temas.js, así que la capa de color NUNCA se dibujó ahí. Lo único que se
     veía en ellas era la capa gris institucional. Por eso eran las más feas de
     todo el sitio, y por eso quitar la gris las dejaba en blanco liso.

     El movimiento sigue atado a `data-animacion` más abajo, que es lo
     correcto: quien tiene activado «reducir movimiento» ve el color quieto,
     no se queda sin color. */
  content:'';
  position:fixed; inset:0; z-index:-1; pointer-events:none;

  /* Cada mancha lleva un NÚCLEO sólido antes de empezar a desvanecerse, y esa
     es la diferencia entre que se vea el color y que no.

     Con dos paradas —color en el centro, transparente en el borde— el color
     puro existe en un solo punto: el resto de la mancha es alfa cada vez más
     baja, y encima se le aplica la opacidad de la capa. El resultado sobre
     blanco era un pastel grisáceo que no se parecía a la marca. Con la parada
     intermedia, el 22 % interior de cada mancha es color de verdad y el
     desvanecido empieza desde ahí. Se ve el azul y se ve el verde.

     El desvanecido termina más tarde (68-72 % en vez de 54-60 %) para que las
     manchas se solapen y se mezclen en los bordes en vez de dejar el blanco
     asomando entre ellas como cinco pegotes sueltos. */
  background:
    radial-gradient(32vmax 30vmax at 14% 16%,
      var(--vivo-azul) 0%, var(--vivo-azul) 22%, transparent 70%),
    radial-gradient(28vmax 26vmax at 86% 10%,
      var(--vivo-verde) 0%, var(--vivo-verde) 20%, transparent 68%),
    radial-gradient(34vmax 31vmax at 76% 86%,
      var(--vivo-indigo) 0%, var(--vivo-indigo) 22%, transparent 72%),
    radial-gradient(26vmax 24vmax at 24% 84%,
      var(--vivo-oro) 0%, var(--vivo-oro) 18%, transparent 68%),
    radial-gradient(22vmax 21vmax at 50% 50%,
      var(--vivo-verde) 0%, var(--vivo-verde) 18%, transparent 66%);
  /* La opacidad la lleva la capa entera y no cada mancha: así el mando de la
     intensidad sigue funcionando igual que dentro de la plataforma. */
  /* Saturación de verdad.
     ─────────────────────────────────────────────────────────────────────
     La paleta de la casa es sobria a propósito —azul marino y verde apagados,
     «más peso que color»— y eso, mezclado sobre blanco a media opacidad, da
     GRIS. Se probó: la primera versión de esta franja parecía una nube sucia.

     Así que aquí, y sólo aquí, se les sube la saturación. La identidad no
     cambia: siguen siendo el azul y el verde del CEM, sólo que enseñando el
     color que ya tenían en vez de dejarlo apagado contra el blanco. Dentro de
     la plataforma no se toca nada. */
  /* Sin filtro: el color ya viene vivo de origen. Un `saturate()` encima de
     algo casi transparente sólo ensucia los bordes. */
  opacity:calc(var(--ambiente-movil, 1) * var(--ambiente-fuerza, 1) * .40);
  /* El desplazamiento del ratón se suma al giro. Va en variables para que la
     animación siga siendo de CSS: mover esto desde JavaScript en cada
     movimiento del ratón repintaría la pantalla sesenta veces por segundo. */
  translate:calc(var(--raton-x, 0) * 3%) calc(var(--raton-y, 0) * 3%);
  transition:translate 1.2s cubic-bezier(.22,.61,.36,1);
}

/* ── que el texto se siga leyendo con las manchas debajo ─────────────────
   Esto no es un detalle de acabado, es la parte que hace que lo de arriba se
   pueda usar. Medido sobre los píxeles de la captura, no sobre la hoja: con
   las manchas saturadas el párrafo del hero daba 3,34 de contraste y el ojal
   3,20, cuando hace falta 4,5. Se leían mal.

   Y no se arregla oscureciendo el texto, porque EL FONDO SE MUEVE: dentro de
   medio minuto ese mismo párrafo tiene otro color detrás. Cualquier color fijo
   que se elija estará bien en un momento del giro y mal en otro. Lo único que
   funciona con un fondo que cambia es poner algo entre el fondo y el texto.

   Así que la columna de texto lleva su propio velo: casi blanco donde están
   las letras, desvaneciéndose hacia fuera para que no se vea el borde y el
   color siga llegando al resto de la pantalla. El velo va DETRÁS del texto
   (z-index) y no le come los clics a los botones (pointer-events). */
:root[data-publico="si"] .portada-cem{position:relative;}
:root[data-publico="si"] .portada-dicho{position:relative;}
/* La caja del velo es MUCHO más grande que el texto y el degradado se apaga
   muy antes de llegar a su borde. Con la caja justa se veía lo contrario de
   lo que se busca: un rectángulo blanco con las esquinas rectas recortado
   sobre el color, que llamaba más la atención que las manchas. Un degradado
   radial se corta en seco en el borde de su caja si todavía le queda opacidad,
   y con `120% 100%` en el borde derecho aún iba por la mitad del recorrido.
   Ahora la elipse mide el 70 % de una caja un tercio más ancha, así que llega
   a transparente del todo dentro de ella y no hay borde que ver. */
:root[data-publico="si"] .portada-dicho::before{
  content:'';position:absolute;z-index:0;pointer-events:none;
  inset:-38% -34% -34% -30%;
  background:radial-gradient(62% 56% at 34% 48%,
    rgba(255,255,255,.95) 0%, rgba(255,255,255,.92) 34%,
    rgba(255,255,255,.62) 58%, rgba(255,255,255,.22) 74%, transparent 86%);
}
/* Con la portada centrada, el velo también: estaba puesto al 34 % porque el
   texto vivía en la columna izquierda. Dejarlo ahí iluminaría el hueco de la
   izquierda y dejaría el final de cada línea sobre el color. */
:root[data-publico="si"] .portada-una .portada-dicho::before{
  inset:-40% -26%;
  background:radial-gradient(58% 54% at 50% 48%,
    rgba(255,255,255,.95) 0%, rgba(255,255,255,.92) 36%,
    rgba(255,255,255,.62) 60%, rgba(255,255,255,.22) 76%, transparent 88%);
}
:root[data-publico="si"] .portada-dicho > *{position:relative;z-index:1;}
/* El ojal es el texto más pequeño de la portada y va en el verde de la marca,
   que sobre claro es el color con menos contraste que tenemos. Aquí se le baja
   un punto para que llegue al mínimo sin dejar de ser el verde del CEM. */
:root[data-publico="si"] .ojal{color:#0a5f57;}
/* El párrafo del hero va en el gris secundario, y con el velo puesto seguía
   dando 3,96. El número no se elige a ojo: con el fondo medido detrás
   —rgb(203,206,209)— este gris da 5,4, o sea que aguanta también cuando el
   giro traiga debajo el tono más oscuro del recorrido y no sólo el de la
   captura. Sigue siendo gris de texto secundario, no negro. */
:root[data-publico="si"] .portada-cem .lema{color:#464d56;}
:root[data-publico="si"] .franja.tenue{background:rgba(255,255,255,.72);}

@media (prefers-reduced-motion:reduce){
  :root[data-publico="si"] body::after{transition:none;}
}


/* ── la muestra de un estilo, en Configuración ────────────────────────────
   Elegir a ciegas entre siete nombres no es elegir. Cada botón lleva dentro
   un recuadro pintado con su propio estilo, así que se ve antes de aplicar.
   `data-muestra` repite las mismas recetas en pequeño; no hereda las de
   arriba porque aquéllas cuelgan de :root y aquí conviven las siete. */
.estilos{display:grid;gap:var(--e2);
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));}
/* `display:block` no es decorativo: dentro de un <button> los <span> son
   inline, y a un elemento inline la altura no le hace nada. Sin esto el
   lienzo medía cero y la muestra se montaba encima del nombre. */
.estilo{display:block;position:relative;text-align:left;cursor:pointer;padding:var(--e1);
  border:1px solid var(--filete);border-radius:var(--r);background:var(--papel);
  transition:border-color var(--rapido);font:inherit;color:inherit;}
.estilo:hover{border-color:var(--filete-fuerte);}
.estilo[aria-pressed="true"]{border-color:var(--primary);
  box-shadow:0 0 0 1px var(--primary);}
.estilo .lienzo{display:block;position:relative;height:64px;border-radius:var(--r);
  overflow:hidden;margin-bottom:var(--e1);
  background:
    radial-gradient(60% 80% at 12% 0%, color-mix(in srgb,var(--primary) 55%,transparent),transparent 70%),
    radial-gradient(60% 80% at 96% 20%, color-mix(in srgb,var(--secondary) 48%,transparent),transparent 70%),
    var(--fondo);}
.estilo .pieza{position:absolute;inset:12px;border-radius:calc(var(--r) * .8);
  display:flex;align-items:center;justify-content:center;
  font-family:var(--cifra);font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  color:var(--tinta);}
.estilo .nom{display:block;font-size:var(--t-sm);font-weight:var(--peso-fuerte);}
.estilo .des{display:block;font-size:var(--t-xs);color:var(--tinta-2);line-height:1.4;}
.estilo .coste{display:inline-block;margin-top:var(--e0);font-size:var(--t-xs);
  padding:1px 8px;border-radius:var(--r-full);}
.estilo .coste.barato{background:var(--ok-suave);color:var(--ok);}
.estilo .coste.caro{background:var(--warn-suave);color:var(--warn);}

[data-muestra="plano"] .pieza{background:none;border-bottom:1px solid var(--filete-fuerte);
  border-radius:0;align-items:flex-end;}
[data-muestra="escarcha"] .pieza{background:color-mix(in srgb,var(--papel) 58%,transparent);
  -webkit-backdrop-filter:saturate(150%) blur(8px);backdrop-filter:saturate(150%) blur(8px);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--papel) 45%,var(--filete-fuerte));}
[data-muestra="marco"] .pieza{background:color-mix(in srgb,#fff 15%,transparent);
  -webkit-backdrop-filter:saturate(165%) blur(9px);backdrop-filter:saturate(165%) blur(9px);
  box-shadow:inset 0 1px 0 color-mix(in srgb,#fff 55%,transparent),
             inset 0 0 0 1px color-mix(in srgb,#fff 22%,transparent);}
[data-muestra="marco"] .pieza::before{content:'';position:absolute;inset:5px;
  border-radius:6px;background:color-mix(in srgb,#fff 26%,transparent);filter:blur(4px);}
[data-muestra="bisel"] .pieza{background:linear-gradient(158deg,
    color-mix(in srgb,var(--papel) 74%,transparent),
    color-mix(in srgb,var(--papel) 42%,transparent));
  -webkit-backdrop-filter:saturate(180%) blur(10px);backdrop-filter:saturate(180%) blur(10px);
  box-shadow:inset 1.5px 1.5px 0 color-mix(in srgb,#fff 65%,transparent),
             inset -1px -1px 0 color-mix(in srgb,var(--tinta) 14%,transparent),
             inset 0 -10px 14px -12px color-mix(in srgb,var(--tinta) 55%,transparent);}
[data-muestra="canto"] .pieza{background:linear-gradient(152deg,
    color-mix(in srgb,var(--papel) 92%,transparent),
    color-mix(in srgb,var(--papel) 68%,transparent));}
[data-muestra="canto"] .pieza::after{content:'';position:absolute;inset:0;
  border-radius:inherit;padding:1.5px;
  background:linear-gradient(142deg,color-mix(in srgb,#fff 88%,transparent),
    color-mix(in srgb,#fff 4%,transparent) 46%,
    color-mix(in srgb,var(--primary) 50%,transparent));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;}
[data-muestra="halo"] .pieza{background:color-mix(in srgb,var(--papel) 72%,transparent);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  box-shadow:0 0 0 1px color-mix(in srgb,var(--papel) 40%,transparent),
             0 0 22px -4px color-mix(in srgb,var(--primary) 40%,transparent);}
[data-muestra="tintado"] .pieza{background:linear-gradient(155deg,
    color-mix(in srgb,var(--primary) 20%,color-mix(in srgb,var(--papel) 62%,transparent)),
    color-mix(in srgb,var(--primary) 7%,color-mix(in srgb,var(--papel) 48%,transparent)));
  -webkit-backdrop-filter:saturate(170%) blur(9px);backdrop-filter:saturate(170%) blur(9px);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--primary) 42%,transparent),
             inset 0 1px 0 color-mix(in srgb,#fff 40%,transparent);}

/* Las formas y la densidad también se eligen viéndolas. */
.formas{display:flex;gap:var(--e1);flex-wrap:wrap;}
.forma{cursor:pointer;font:inherit;color:inherit;padding:var(--e1) var(--e2);
  border:1px solid var(--filete);background:var(--papel);
  display:flex;flex-direction:column;align-items:center;gap:var(--e0);
  font-size:var(--t-xs);min-width:88px;}
.forma[aria-pressed="true"]{border-color:var(--primary);box-shadow:0 0 0 1px var(--primary);}
.forma i{display:block;width:34px;height:22px;background:var(--primary-suave);
  border:1px solid var(--primary);}
.forma[data-forma="recta"], .forma[data-forma="recta"] i{border-radius:3px;}
.forma[data-forma="suave"], .forma[data-forma="suave"] i{border-radius:10px;}
.forma[data-forma="redonda"], .forma[data-forma="redonda"] i{border-radius:14px;}
.forma[data-forma="redonda"]{border-radius:18px;}

/* ── la historia de una persona, en orden ────────────────────────────────
   Cinco tablas separadas obligan a reconstruir mentalmente lo que pasó. En
   una columna con su fecha se lee sola: se inscribió, pagó, entregó, aprobó,
   se graduó. La línea vertical es lo que la convierte en un relato. */
.linea{position:relative;padding-left:var(--e3);margin:0;list-style:none;}
.linea::before{content:'';position:absolute;left:5px;top:6px;bottom:6px;
  width:1px;background:var(--filete);}
.linea li{position:relative;padding:var(--e1) 0 var(--e1) var(--e2);}
.linea li::before{content:'';position:absolute;left:-19px;top:15px;width:11px;height:11px;
  border-radius:var(--r-full);background:var(--papel);
  box-shadow:0 0 0 2px var(--filete-fuerte);}
.linea li.ok::before{box-shadow:0 0 0 2px var(--ok);background:var(--ok);}
.linea li.mal::before{box-shadow:0 0 0 2px var(--error);background:var(--error);}
.linea li.hito::before{box-shadow:0 0 0 2px var(--gold);background:var(--gold);}
.linea .cuando{font-size:var(--t-xs);color:var(--tinta-3);font-family:var(--cifra);}
.linea .que{font-weight:var(--peso-fuerte);line-height:1.35;}
.linea .detalle{font-size:var(--t-sm);color:var(--tinta-2);}

/* El aviso de que un certificado ya no vale: tiene que verse antes que el
   propio certificado. */
.anulado{opacity:.6;}
.anulado .t{text-decoration:line-through;}

/* El eco de una fecha con el mes en letras. Discreto, pero suficiente para que
   nadie confunda el 1 de septiembre con el 9 de enero. */
.fecha-clara{display:block;font-size:var(--t-xs);color:var(--tinta-3);
  margin-top:var(--e0);line-height:1.4;}
.fecha-clara:empty{display:none;}

/* Cuántas filas se están viendo y de cuántas. Va junto a la tabla, en voz baja,
   y sólo levanta la voz cuando la lista se quedó recortada. */
.cuenta-filas{font-size:var(--t-xs);color:var(--tinta-3);font-variant-numeric:tabular-nums;}
.cuenta-filas b{color:var(--warn);font-weight:var(--peso-fuerte);}

/* ── un botón mide siempre lo mismo en el mismo sitio (item 10) ──────────
   Había `.btn`, `.btn.sm` y controles que no usaban ninguna de las dos y se
   quedaban con la altura que les tocara. Dentro de una barra de acciones o de
   una fila de tabla, todos los controles se alinean por el mismo alto: es lo
   que hace que la fila se lea como una fila y no como una repisa. */
.page-head .actions > *,
.filters > *,
.row.between > .row > .btn{min-height:36px;}
td .acciones .btn,
td .btn{min-height:32px;}
/* Un campo metido entre botones tiene que medir como ellos. */
.page-head .actions .field,
.filters .field{margin-bottom:0;}
.page-head .actions .field select,
.page-head .actions .field input{min-height:36px;}

/* ═══════════════════════════════════════════════════════════════════════════
   EL AULA  ·  tablón, comentarios y cuadrícula de notas
   ═══════════════════════════════════════════════════════════════════════════
   Lo que hace que un aula se sienta un aula no es tener el contenido —eso ya
   estaba— sino que haya un sitio donde el profesor diga «mañana no hay clase»
   y alguien pueda preguntar. */

.muro-nuevo{padding:var(--e2);}
.muro-abrir{display:flex;align-items:center;gap:var(--e2);width:100%;
  padding:var(--e2);border:0;border-radius:var(--r);background:var(--hueco);
  color:var(--tinta-2);font:inherit;font-size:var(--t-sm);cursor:pointer;
  text-align:left;transition:background var(--rapido);}
.muro-abrir:hover{background:var(--primary-suave);color:var(--tinta);}

.post{padding:var(--e3);}
.post + .post{margin-top:var(--aire);}
.post-cab{display:flex;align-items:flex-start;gap:var(--e2);margin-bottom:var(--e2);}
.post-quien{font-weight:var(--peso-fuerte);font-size:var(--t-sm);line-height:1.3;}
.post-cuando{font-size:var(--t-xs);color:var(--tinta-3);}
.post-cab .fijado{font-size:15px;color:var(--gold);vertical-align:-2px;}
.post-cuerpo{font-size:var(--t-sm);line-height:1.6;overflow-wrap:anywhere;}
.post-adjuntos{display:flex;flex-wrap:wrap;gap:var(--e1);margin-top:var(--e2);}
.adjunto{display:inline-flex;align-items:center;gap:var(--e0);font-size:var(--t-xs);
  padding:var(--e0) var(--e1);border:1px solid var(--filete);border-radius:var(--r);
  color:var(--tinta-2);text-decoration:none;}
.adjunto:hover{border-color:var(--primary);color:var(--primary);}

/* Los comentarios cuelgan del aviso con una línea, como una conversación. */
.post-comentarios{margin-top:var(--e3);padding-top:var(--e2);
  border-top:1px solid var(--filete);}
.comentario{display:flex;align-items:flex-start;gap:var(--e1);
  font-size:var(--t-sm);line-height:1.5;padding:var(--e0) 0;}
.comentario .avatar.sm{width:26px;height:26px;font-size:11px;flex:none;}
.comentario > .btn{flex:none;}
.comentar{display:flex;gap:var(--e1);margin-top:var(--e1);}
.comentar input{flex:1;min-width:0;}

/* ── las dudas de la lección ───────────────────────────────────────────────
   Se parecen a los avisos del tablón porque son la misma conversación vista
   desde otro sitio, pero con dos diferencias que sí importan: una duda tiene
   estado —espera respuesta, la tiene, está cerrada— y la respuesta del
   profesor no es un comentario más. Las dos cosas se ven de un vistazo. */
.duda-nueva textarea{width:100%;}
.duda{padding:var(--e3);}
.duda + .duda{margin-top:var(--aire);}
.duda.resuelta{opacity:.72;}
.duda.resuelta:hover, .duda.resuelta:focus-within{opacity:1;}
.comentario.del-profe{background:var(--primary-suave);border-radius:var(--r);
  padding:var(--e1) var(--e1) var(--e1) var(--e0);}
.comentario .chip.tiny{font-size:var(--t-xs);padding:0 6px;margin-left:var(--e0);
  vertical-align:1px;}

/* ── la cuadrícula de notas ────────────────────────────────────────────────
   La columna del nombre se queda quieta al desplazar de lado: sin ella, a la
   tercera evaluación ya no se sabe de quién es la fila que se está mirando. */
table.notas th.pegada, table.notas td.pegada{position:sticky;left:0;z-index:2;
  background:var(--papel);max-width:190px;}
table.notas th{white-space:normal;vertical-align:bottom;}
table.notas th .recorta{display:block;max-width:120px;}
table.notas .celda-nota{padding:2px var(--e0);}
table.notas .celda-nota input{width:72px;text-align:right;min-height:34px;
  padding:2px var(--e0);font-family:var(--cifra);font-variant-numeric:tabular-nums;}
/* Una nota por debajo de la aprobatoria se marca, pero con el borde: pintar la
   casilla entera de rojo convierte la tabla en un semáforo. */
table.notas .celda-nota.suspende input{border-color:var(--error);color:var(--error);}
table.notas .celda-nota.guardada input{border-color:var(--ok);}
@media (prefers-reduced-motion:no-preference){
  table.notas .celda-nota input{transition:border-color var(--rapido);}
}

/* El código de clase: es lo que se dicta por teléfono o se copia a un chat,
   así que va con la letra de las cifras y bien separado. */
.codigo-clase{font-family:var(--cifra);font-size:var(--t-lg);letter-spacing:.12em;
  font-weight:var(--peso-fuerte);text-transform:lowercase;}

/* Encabezado de grupo dentro de una tabla: dice en qué módulo o carpeta estás
   sin sacarte de la tabla. Es una fila, no una caja: meter cada grupo en su
   propia tarjeta rompería la alineación de las columnas, que es lo único que
   una tabla sabe hacer bien. */
tr.grupo-banco td{background:var(--hueco);font-size:var(--t-xs);
  padding:var(--e1) var(--e2);border-bottom:1px solid var(--filete-fuerte);}
tr.grupo-banco .material-symbols-outlined{font-size:16px;vertical-align:-3px;
  color:var(--tinta-3);margin-right:var(--e0);}
tr.grupo-banco .tiny{margin-left:var(--e1);}

/* La escala de 1 a 5: cinco botones, no un desplegable ni un deslizador. Se
   contesta de un toque, que es la única forma de que alguien conteste. */
.escala{display:flex;gap:var(--e0);}
.escala button{flex:1;min-height:44px;font:inherit;font-family:var(--cifra);
  font-size:var(--t-md);cursor:pointer;border:1px solid var(--filete);
  border-radius:var(--r);background:var(--papel);color:var(--tinta-2);
  transition:background var(--rapido),border-color var(--rapido),color var(--rapido);}
.escala button:hover{border-color:var(--primary);color:var(--tinta);}
.escala button[aria-pressed="true"]{background:var(--primary);color:var(--on-primary);
  border-color:var(--primary);font-weight:var(--peso-fuerte);}

/* Un comentario anónimo: se lee como una cita, no como una fila de tabla. La
   barra de la izquierda es lo que dice «esto lo escribió una persona». */
.comentario-anon{margin:0 0 var(--e2);padding:var(--e1) 0 var(--e1) var(--e2);
  border-left:2px solid var(--filete-fuerte);font-size:var(--t-sm);line-height:1.6;}
.comentario-anon footer{margin-top:var(--e0);}
.sub-ap{font-size:var(--t-xs);color:var(--tinta-3);font-weight:var(--peso-fuerte);
  margin:var(--e3) 0 var(--e1);}

/* ── el editor de evaluaciones ─────────────────────────────────────────────
   Una pregunta a la vez abierta y el resto plegadas. Con veinte preguntas
   todas abiertas no se ve dónde estás, y con todas cerradas no se ve qué
   estás escribiendo: abrir sólo la que se toca es lo único que funciona. */
.q-tarjeta{background:var(--papel);border:1px solid var(--filete);
  border-radius:var(--r);padding:var(--e2);margin-bottom:var(--e1);
  border-left:4px solid transparent;cursor:pointer;}
.q-tarjeta:hover{border-color:var(--filete-fuerte);}
.q-tarjeta.activa{border-left-color:var(--primary);cursor:default;
  border-color:var(--filete-fuerte);}
.q-tarjeta .q-cabeza{display:flex;gap:var(--e1);align-items:flex-start;}
.q-tarjeta .q-num{font-family:var(--cifra);font-weight:var(--peso-fuerte);
  color:var(--tinta-3);min-width:22px;flex:none;padding-top:2px;}
.q-tarjeta .q-vale{font-family:var(--cifra);font-variant-numeric:tabular-nums;}
.q-tarjeta .q-vale input{width:80px;text-align:right;}
/* Una pregunta sin puntos dentro de un examen puntuado casi siempre es un
   descuido, así que se ve; pero no es un error, porque a veces se quiere. */
.q-tarjeta.sin-puntos .q-vale input{border-color:var(--warn);}
.q-seccion{margin:var(--e3) 0 var(--e1);padding:var(--e1) var(--e2);
  background:var(--hueco);border-radius:var(--r);
  border-left:4px solid var(--filete-fuerte);}
.q-seccion h3{margin:0;font-size:var(--t-md);}

/* Las opciones de una pregunta: el control de «cuál es la correcta» va pegado
   a la opción, no en un desplegable aparte, porque marcar la correcta mirando
   a otro sitio es cómo se cuelan las claves equivocadas. */
.opt-fila{display:flex;gap:var(--e1);align-items:center;margin-bottom:var(--e0);}
.opt-fila input[type=text]{flex:1;min-width:0;}
.opt-fila input[type=radio],.opt-fila input[type=checkbox]{flex:none;
  width:20px;height:20px;accent-color:var(--ok);}
.opt-fila .btn{flex:none;}
.opt-fila.correcta input[type=text]{border-color:var(--ok);}

/* El marcador de puntos. Es la pieza central del editor: la regla es que la
   suma de las preguntas dé el total declarado, y si no se ve todo el rato,
   se descubre al publicar y ya es tarde. */
.marcador{display:flex;align-items:center;gap:var(--e2);flex-wrap:wrap;
  padding:var(--e1) var(--e2);border-radius:var(--r);
  background:var(--hueco);border:1px solid var(--filete);}
.marcador .cifra{font-family:var(--cifra);font-size:var(--t-lg);
  font-weight:var(--peso-fuerte);font-variant-numeric:tabular-nums;}
.marcador.cuadra{border-color:var(--ok);}
.marcador.cuadra .cifra{color:var(--ok);}
.marcador.falta{border-color:var(--warn);}
.marcador.falta .cifra{color:var(--warn);}
.marcador.pasa{border-color:var(--error);}
.marcador.pasa .cifra{color:var(--error);}

/* ── métricas de respuestas ────────────────────────────────────────────────
   Barras horizontales, no tartas: comparar longitudes es fácil y comparar
   ángulos no lo es. */
.barra-dato{display:grid;grid-template-columns:minmax(110px,1fr) 3fr auto;
  gap:var(--e1);align-items:center;margin-bottom:var(--e0);font-size:var(--t-sm);}
.barra-dato .pista{height:22px;border-radius:var(--r);background:var(--hueco);
  overflow:hidden;}
.barra-dato .relleno{height:100%;background:var(--primary);min-width:2px;}
.barra-dato.acertada .relleno{background:var(--ok);}
.barra-dato .cuenta{font-family:var(--cifra);font-variant-numeric:tabular-nums;
  color:var(--tinta-2);min-width:56px;text-align:right;}
.barra-dato .etq{overflow-wrap:anywhere;}

/* El histograma de notas: columnas, porque el eje es una escala continua de 0
   a 100 y leerla de izquierda a derecha es cómo se lee una nota. */
.histograma{display:flex;align-items:flex-end;gap:3px;height:120px;
  margin:var(--e2) 0 var(--e1);}
.histograma .col{flex:1;display:flex;flex-direction:column;justify-content:flex-end;
  height:100%;}
.histograma .palo{background:var(--primary);border-radius:var(--r) var(--r) 0 0;
  min-height:2px;}
.histograma .col.reprueba .palo{background:var(--error);}
.histograma .pie{font-size:10px;color:var(--tinta-3);text-align:center;
  font-family:var(--cifra);margin-top:var(--e0);}

/* Una pregunta que falló casi todo el mundo casi nunca es culpa de la clase:
   o está mal redactada o el tema se explicó mal. Por eso va destacada. */
.fallada{border-left:3px solid var(--error);padding-left:var(--e2);}

/* ── responder: cuadrícula de opciones ─────────────────────────────────────
   Con una sola columna por fila en el móvil la cuadrícula deja de ser una
   cuadrícula, así que se desplaza de lado dentro de su caja. */
.rejilla-resp{width:100%;border-collapse:collapse;font-size:var(--t-sm);}
.rejilla-resp th,.rejilla-resp td{padding:var(--e1);text-align:center;
  border-bottom:1px solid var(--filete);}
.rejilla-resp th:first-child,.rejilla-resp td:first-child{text-align:left;
  font-weight:var(--peso-fuerte);}
.rejilla-resp input{width:20px;height:20px;accent-color:var(--primary);}

/* La escala lineal al responder: los números en fila con su etiqueta en cada
   extremo, que es lo que dice si 1 es «malo» o «poco». */
.escala-lineal{display:flex;align-items:center;gap:var(--e1);flex-wrap:wrap;}
.escala-lineal .punto{display:flex;flex-direction:column;align-items:center;
  gap:var(--e0);cursor:pointer;min-width:44px;}
.escala-lineal .punto input{width:20px;height:20px;accent-color:var(--primary);}
.escala-lineal .tope{font-size:var(--t-xs);color:var(--tinta-3);max-width:120px;}

/* Dos columnas con una barra lateral: el contenido manda y al lado va la ficha
   de apoyo. En el teléfono no hay «al lado», así que se ponen una debajo de la
   otra. Sin este corte la columna estrecha se comía la ancha y el contenido
   principal salía en tiras de cuatro palabras. */
.grid.dos-columnas{grid-template-columns:minmax(0,2fr) minmax(260px,1fr);}
@media (max-width:900px){
  .grid.dos-columnas{grid-template-columns:minmax(0,1fr);}
}

/* ── 8 · panel de datos ───────────────────────────────────────────────────
   El aspecto de un panel que se mira de noche y durante horas: la caja apenas
   se separa del fondo —un tono más clara y un filete de un píxel— y todo el
   peso visual se lo lleva la cifra. El rótulo se convierte en una etiqueta
   pequeña en versalitas, encima; la cifra crece y toma el color de lo que está
   diciendo.

   No difumina, así que aguanta una tabla de cientos de filas sin coste.

   Va al final del bloque de estilos a propósito: sus reglas de `.kpi` tienen
   que ganarle a las genéricas, y aquí lo hacen por orden de cascada sin tener
   que inflar la especificidad. */
:root[data-estilo="panel"]{
  --r-caja:calc(var(--r) * 1.3);
  --caja-fondo:var(--papel);
  --caja-filtro:none;
  --caja-sombra:inset 0 0 0 1px var(--filete);
}
/* El rótulo, en versalitas y separado: deja de competir con la cifra. */
:root[data-estilo="panel"] .kpi .k-top{
  font-size:11px;letter-spacing:.11em;text-transform:uppercase;
  font-weight:var(--peso-fuerte);color:var(--tinta-3);
}
:root[data-estilo="panel"] .kpi .k-val{
  font-size:calc(var(--t-xl) * 1.12);letter-spacing:-.03em;
}
/* Y la cifra toma el color de lo que dice. El estado va en el subtítulo
   —«250,00 € vencido»— así que se lee desde ahí con :has(): la cifra sola no
   sabe si es una buena noticia. */
:root[data-estilo="panel"] .kpi:has(.k-sub.up:not(.cero)) .k-val{color:var(--ok);}
:root[data-estilo="panel"] .kpi:has(.k-sub.down:not(.cero)) .k-val{color:var(--error);}
:root[data-estilo="panel"] .kpi.entra .k-val{color:var(--ok);}
:root[data-estilo="panel"] .kpi.debe .k-val{color:var(--error);}
:root[data-estilo="panel"] .kpi.warn .k-val{color:var(--warn);}
/* El cero sigue siendo gris: es la buena noticia y no necesita color. */
:root[data-estilo="panel"] .kpi .k-val.vacio{color:var(--tinta-3);}
/* Los títulos de sección, con la misma voz de etiqueta. */
:root[data-estilo="panel"] :is(.card,.caja) > .sec:first-child{
  font-size:11px;letter-spacing:.11em;text-transform:uppercase;color:var(--tinta-3);
}

/* ═══════════════════════ GRÁFICOS ═════════════════════════════════════════
   Ni un color escrito aquí que no venga de un token. Los tokens los pone la
   paleta que cada quien eligió en Configuración, así que los gráficos cambian
   con ella —también en modo noche— sin tener una tabla de colores propia que
   se quede desincronizada.

   Las series salen de mezclar los dos colores de la paleta entre ellos y con
   la tinta suave. No se eligen a mano doce juegos de seis colores: se
   derivan, y así una paleta nueva trae sus series hechas. La mayoría de los
   gráficos de la plataforma son de UNA serie —ingresos por mes, matrícula por
   programa, asistencia— y usan --primary a secas; las de varias series son
   pocas, y las que hablan de estados usan los colores de estado, que es lo
   que ya significa «vencido» en el resto de la aplicación. */
:root{
  --serie-1:var(--primary);
  --serie-2:var(--secondary);
  --serie-3:color-mix(in oklab, var(--primary) 55%, var(--secondary));
  --serie-4:color-mix(in oklab, var(--primary) 45%, var(--tinta-3));
  --serie-5:color-mix(in oklab, var(--secondary) 50%, var(--tinta-3));
  --serie-6:color-mix(in oklab, var(--secondary) 45%, var(--gold));
  /* Siete y ocho: el brand board del CEM trae ocho colores cromáticos y las
     paletas derivadas también necesitan llegar a ocho, o una lista de ocho
     categorías repite color en la séptima y parecen la misma cosa. */
  --serie-7:color-mix(in oklab, var(--primary) 30%, var(--gold));
  --serie-8:color-mix(in oklab, var(--secondary) 65%, var(--primary));
  --gr-pista:var(--hueco);
  /* Las barras redondean lo que diga la forma elegida, pero con tope: una
     barra de 9 px de alto con esquinas de 999 px es una pastilla, no un dato. */
  --gr-radio:min(var(--r), 6px);
}

.gr{margin:0;display:flex;flex-direction:column;gap:var(--e2);}
.gr + .gr{margin-top:var(--aire);padding-top:var(--aire);
  border-top:1px solid var(--filete);}
.gr-tit{font-size:var(--t-sm);font-weight:var(--peso-fuerte);color:var(--tinta);
  line-height:1.35;}
.gr-pie{margin:0;font-size:var(--t-xs);color:var(--tinta-2);line-height:1.5;
  max-width:72ch;}
.gr-vacio{margin:0;padding:var(--e3) 0;font-size:var(--t-sm);color:var(--tinta-3);}
.gr-mas{margin:var(--e0) 0 0;font-size:var(--t-xs);color:var(--tinta-3);}

/* La tabla de debajo: plegada, porque el gráfico ya contestó la pregunta. Se
   abre cuando hace falta el número exacto para pegarlo en un correo. */
.gr-tabla > summary{cursor:pointer;font-size:var(--t-xs);color:var(--tinta-2);
  list-style:none;display:inline-flex;align-items:center;gap:var(--e0);
  padding:var(--e0) var(--e1);border-radius:var(--r);min-height:32px;}
.gr-tabla > summary::-webkit-details-marker{display:none;}
.gr-tabla > summary::before{content:'▸';font-size:11px;color:var(--tinta-3);}
.gr-tabla[open] > summary::before{content:'▾';}
.gr-tabla > summary:hover{background:var(--hueco);color:var(--tinta);}
.gr-tabla .table-wrap{margin-top:var(--e1);}

/* ── columnas: el tiempo, de izquierda a derecha ─────────────────────────── */
.gr-cols{display:flex;align-items:flex-end;gap:3px;height:var(--gr-alto,150px);}
.gr-cols .gr-col{flex:1;min-width:0;display:flex;flex-direction:column;
  justify-content:flex-end;height:100%;text-decoration:none;color:inherit;
  border-radius:var(--gr-radio);}
.gr-cols a.gr-col{cursor:pointer;}
.gr-cols a.gr-col:hover .palo{background:var(--serie-3);}
.gr-cols a.gr-col:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.gr-cols .palo{background:var(--serie-1);border-radius:var(--gr-radio) var(--gr-radio) 0 0;
  min-height:2px;}
.gr-cols .v{font-family:var(--cifra);font-size:10px;color:var(--tinta-3);
  text-align:center;line-height:1.2;margin-bottom:2px;
  font-variant-numeric:tabular-nums;
  /* Con más de ocho columnas los números se pisan; entonces sólo el tooltip. */
  overflow:hidden;white-space:nowrap;}
.gr-cols .etq{font-size:10px;color:var(--tinta-3);text-align:center;
  margin-top:var(--e0);overflow:visible;white-space:nowrap;}
/* Una fecha no cabe en una columna de veinte puntos, y recortada con puntos
   suspensivos («1…») no dice nada. Se enseña una de cada tantas y las demás se
   ocultan: el eje sigue siendo legible y las columnas no se estrechan. */
.gr-cols .etq.salta{visibility:hidden;}
.gr-cols .gr-col.ojo .palo{background:var(--secondary);}
.gr-cols.histograma .gr-col.bajo .palo{background:var(--error);}
.gr-cols .palo.pila{display:flex;flex-direction:column-reverse;overflow:hidden;}
@media (max-width:640px){
  .gr-cols{gap:2px;}
  .gr-cols .v{display:none;}
}

/* ── barras horizontales: comparar cosas con nombre ──────────────────────── */
.gr-barras{display:flex;flex-direction:column;gap:var(--e1);}
.gr-fila{display:grid;grid-template-columns:minmax(90px,1.1fr) minmax(0,2.4fr) auto;
  gap:var(--e1);align-items:center;font-size:var(--t-xs);}
.gr-etq{overflow-wrap:anywhere;color:var(--tinta-2);line-height:1.3;}
.gr-pista{display:block;height:20px;background:var(--gr-pista);
  border-radius:var(--gr-radio);overflow:hidden;}
a.gr-pista{cursor:pointer;}
a.gr-pista:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.gr-relleno{display:block;height:100%;background:var(--serie-1);
  border-radius:var(--gr-radio);}

/* Cada barra de su color, girando por las ocho series.
   ------------------------------------------------------------------------
   Antes todas eran --serie-1 y un tablero entero salía del mismo azul: doce
   gráficos que parecían el mismo gráfico. Y no es sólo estética — en una
   comparación entre COSAS DISTINTAS (programas, canales, categorías) el color
   es lo que dice «esta fila no es la de arriba». Donde el color sí sería
   mentira es en una serie temporal, y ésas son columnas, no barras: las
   columnas siguen de un solo color a propósito, porque los meses son la misma
   cosa medida doce veces.
   Se salta a la vuelta de ocho para que la novena no repita la primera pegada.

   El turno va dentro de `:where()` a propósito: así pesa (0,2,0) y no (0,4,0),
   y las reglas de estado de abajo —que son las que dicen «esto está vencido»—
   ganan sin tener que inflarlas. Una fila vencida sale roja aunque le tocara
   el turquesa, que es lo correcto: el color de turno es decoración, el de
   estado es información. */
:where(.gr-barras .gr-fila:nth-child(8n+1)) .gr-relleno{background:var(--serie-1);}
:where(.gr-barras .gr-fila:nth-child(8n+2)) .gr-relleno{background:var(--serie-2);}
:where(.gr-barras .gr-fila:nth-child(8n+3)) .gr-relleno{background:var(--serie-3);}
:where(.gr-barras .gr-fila:nth-child(8n+4)) .gr-relleno{background:var(--serie-4);}
:where(.gr-barras .gr-fila:nth-child(8n+5)) .gr-relleno{background:var(--serie-5);}
:where(.gr-barras .gr-fila:nth-child(8n+6)) .gr-relleno{background:var(--serie-6);}
:where(.gr-barras .gr-fila:nth-child(8n+7)) .gr-relleno{background:var(--serie-7);}
:where(.gr-barras .gr-fila:nth-child(8n+8)) .gr-relleno{background:var(--serie-8);}
/* Al pasar por encima, la barra se aclara en su propio color en vez de
   cambiar al azul de otra serie, que hacía parecer que el dato había cambiado. */
a.gr-pista:hover .gr-relleno{filter:brightness(1.12) saturate(1.1);}

.gr-fila.ok .gr-relleno{background:var(--ok);}
.gr-fila.warn .gr-relleno{background:var(--warn);}
.gr-fila.err .gr-relleno{background:var(--error);}
.gr-cifra{font-family:var(--cifra);font-variant-numeric:tabular-nums;
  color:var(--tinta);white-space:nowrap;text-align:right;min-width:64px;}
.gr-cifra small{display:block;font-size:10px;color:var(--tinta-3);}
@media (max-width:520px){
  .gr-fila{grid-template-columns:minmax(0,1fr) auto;gap:var(--e0) var(--e1);}
  .gr-fila .gr-pista{grid-column:1 / -1;}
}

/* ── una barra apilada + su leyenda ──────────────────────────────────────── */
.gr-apilada{display:flex;height:26px;border-radius:var(--gr-radio);overflow:hidden;
  background:var(--gr-pista);}
.gr-parte{display:block;background:var(--serie-1);min-width:2px;
  transition:filter var(--rapido);}
a.gr-parte{cursor:pointer;}
a.gr-parte:hover{filter:brightness(1.12);}
a.gr-parte:focus-visible{outline:2px solid var(--primary);outline-offset:-2px;}
.gr-parte.dos{background:var(--serie-2);}   .gr-parte.tres{background:var(--serie-3);}
.gr-parte.cuatro{background:var(--serie-4);} .gr-parte.cinco{background:var(--serie-5);}
.gr-parte.seis{background:var(--serie-6);}
.gr-parte.ok{background:var(--ok);}   .gr-parte.warn{background:var(--warn);}
.gr-parte.err{background:var(--error);} .gr-parte.info{background:var(--info);}
.gr-parte.gris{background:var(--filete-fuerte);}

.gr-leyenda{display:flex;flex-wrap:wrap;gap:var(--e1) var(--e2);font-size:var(--t-xs);
  color:var(--tinta-2);}
.gr-clave{display:inline-flex;align-items:center;gap:var(--e0);}
.gr-clave b{font-family:var(--cifra);color:var(--tinta);
  font-variant-numeric:tabular-nums;font-weight:var(--peso-fuerte);}
.gr-clave small{color:var(--tinta-3);}
.gr-punto{width:10px;height:10px;border-radius:var(--gr-radio);flex:none;
  background:var(--serie-1);}
.gr-punto.dos{background:var(--serie-2);}   .gr-punto.tres{background:var(--serie-3);}
.gr-punto.cuatro{background:var(--serie-4);} .gr-punto.cinco{background:var(--serie-5);}
.gr-punto.seis{background:var(--serie-6);}
.gr-punto.ok{background:var(--ok);}   .gr-punto.warn{background:var(--warn);}
.gr-punto.err{background:var(--error);} .gr-punto.info{background:var(--info);}
.gr-punto.gris{background:var(--filete-fuerte);}

/* ── línea ───────────────────────────────────────────────────────────────── */
.gr-linea{position:relative;}
.gr-linea svg{display:block;width:100%;height:var(--gr-alto,170px);overflow:visible;}
.gr-linea .rejilla{stroke:var(--filete);stroke-width:1;vector-effect:non-scaling-stroke;}
.gr-linea .referencia{stroke:var(--warn);stroke-width:1.5;stroke-dasharray:4 3;
  vector-effect:non-scaling-stroke;}
.gr-linea .area{fill:var(--serie-1);opacity:.10;}
.gr-linea .trazo{fill:none;stroke:var(--serie-1);stroke-width:2;
  stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
.gr-puntos{position:absolute;inset:0 0 auto;height:var(--gr-alto,170px);}
.gr-punto-dato{position:absolute;width:22px;height:22px;transform:translate(-50%,50%);
  border-radius:var(--r-full);display:block;}
.gr-punto-dato::after{content:'';position:absolute;inset:7px;border-radius:var(--r-full);
  background:var(--papel);border:2px solid var(--serie-1);}
a.gr-punto-dato{cursor:pointer;}
a.gr-punto-dato:hover::after{inset:5px;background:var(--serie-1);}
a.gr-punto-dato:focus-visible{outline:2px solid var(--primary);outline-offset:1px;}
.gr-eje{display:flex;justify-content:space-between;margin-top:var(--e0);
  font-size:10px;color:var(--tinta-3);}
.gr-eje span{flex:1;text-align:center;overflow:hidden;white-space:nowrap;}
.gr-ref-etq{font-size:10px;color:var(--warn);margin-top:var(--e0);}
@media (max-width:640px){ .gr-eje span.salta{visibility:hidden;} }

/* ── embudo ──────────────────────────────────────────────────────────────── */
.gr-embudo{display:flex;flex-direction:column;gap:var(--e1);}
/* La barra en su columna y la etiqueta en la suya: con todo en una fila
   flexible, el escalón del 100 % empujaba su propio nombre fuera de la caja y
   el primer paso salía sin decir de qué era. */
.gr-paso{display:grid;grid-template-columns:minmax(0,1fr) minmax(96px,auto) auto;
  gap:var(--e1);align-items:center;}
.gr-escalon{display:flex;align-items:baseline;gap:var(--e1);min-width:0;
  padding:var(--e1) var(--e2);border-radius:var(--gr-radio);
  background:var(--serie-1);color:var(--on-primary);text-decoration:none;
  min-height:38px;}
/* Cada escalón de su color, pero sólo entre los que aguantan letra blanca
   encima. El escalón lleva su cifra DENTRO, en blanco, y sobre el verde #7EFF72
   o el amarillo #FFE45E el blanco desaparece: por eso el embudo gira entre
   cuatro —azul, rosa, púrpura y naranja— y no entre los ocho. Los otros cuatro
   se lucen en las barras, que no llevan texto encima.
   Con `:where()` para que un escalón con estado siga ganando. */
:where(.gr-embudo .gr-paso:nth-child(4n+1)) .gr-escalon{background:var(--serie-1);}
:where(.gr-embudo .gr-paso:nth-child(4n+2)) .gr-escalon{background:var(--serie-3);}
:where(.gr-embudo .gr-paso:nth-child(4n+3)) .gr-escalon{background:var(--serie-5);}
:where(.gr-embudo .gr-paso:nth-child(4n+4)) .gr-escalon{background:var(--serie-4);}
a.gr-escalon{cursor:pointer;}
a.gr-escalon:hover{filter:brightness(1.12) saturate(1.1);}
a.gr-escalon:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.gr-escalon b{font-family:var(--cifra);font-size:var(--t-sm);
  font-variant-numeric:tabular-nums;}
.gr-paso-etq{font-size:var(--t-xs);color:var(--tinta-2);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1;}
.gr-caida{font-family:var(--cifra);font-size:var(--t-xs);color:var(--error);
  white-space:nowrap;}

/* ── anillo ──────────────────────────────────────────────────────────────── */
.gr-anillo{display:flex;align-items:center;gap:var(--e3);flex-wrap:wrap;}
.gr-anillo-dibujo{position:relative;width:132px;height:132px;flex:none;display:block;}
a.gr-anillo-dibujo{cursor:pointer;}
a.gr-anillo-dibujo:focus-visible{outline:2px solid var(--primary);outline-offset:3px;
  border-radius:var(--r-full);}
.gr-anillo svg{width:100%;height:100%;transform:rotate(-90deg);}
.gr-anillo .pista{fill:none;stroke:var(--gr-pista);stroke-width:11;}
.gr-anillo .avance{fill:none;stroke:var(--serie-1);stroke-width:11;
  stroke-linecap:round;}
.gr-anillo-centro{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;}
.gr-anillo-centro b{font-family:var(--cifra);font-size:var(--t-xl);
  font-variant-numeric:tabular-nums;line-height:1;}
.gr-anillo-centro span{font-size:10px;color:var(--tinta-3);margin-top:2px;}
.gr-anillo-cifras{display:flex;gap:var(--e3);}
.gr-anillo-cifras b{display:block;font-family:var(--cifra);font-size:var(--t-md);
  font-variant-numeric:tabular-nums;}
.gr-anillo-cifras span{font-size:var(--t-xs);color:var(--tinta-3);}

/* ── hitos: cosas con fecha, no una serie ────────────────────────────────── */
.gr-hitos{list-style:none;margin:0;padding:0 0 0 var(--e2);
  border-left:2px solid var(--filete);display:flex;flex-direction:column;
  gap:var(--e2);}
.gr-hito{position:relative;display:grid;grid-template-columns:1fr auto;
  gap:0 var(--e1);text-decoration:none;color:inherit;font-size:var(--t-xs);
  padding:var(--e0) 0;}
a.gr-hito{cursor:pointer;}
a.gr-hito:hover .gr-hito-t{color:var(--primary);}
a.gr-hito:focus-visible{outline:2px solid var(--primary);outline-offset:2px;
  border-radius:var(--r);}
.gr-marca{position:absolute;left:calc(var(--e2) * -1 - 6px);top:8px;width:10px;height:10px;
  border-radius:var(--r-full);background:var(--filete-fuerte);
  box-shadow:0 0 0 3px var(--papel);}
.gr-hitos li.ok .gr-marca{background:var(--ok);}
.gr-hitos li.warn .gr-marca{background:var(--warn);}
.gr-hitos li.err .gr-marca{background:var(--error);}
.gr-hitos li.info .gr-marca{background:var(--info);}
.gr-hito-t{font-weight:var(--peso-fuerte);color:var(--tinta);}
.gr-hito-f{font-family:var(--cifra);color:var(--tinta-3);white-space:nowrap;}
.gr-hito-d{grid-column:1 / -1;color:var(--tinta-2);}

/* Bajo cualquiera de los estilos de vidrio, la pista de las barras se apoya
   sobre un fondo translúcido: --hueco se pierde y la barra parece flotar. */
:root:not([data-estilo="plano"]) .gr-pista,
:root:not([data-estilo="plano"]) .gr-apilada,
:root:not([data-estilo="plano"]) .gr-anillo .pista{
  background:color-mix(in srgb, var(--tinta) 9%, transparent);}
:root:not([data-estilo="plano"]) .gr-anillo .pista{
  background:none;stroke:color-mix(in srgb, var(--tinta) 9%, transparent);}

@media (prefers-reduced-motion:no-preference){
  .gr-relleno,.gr-cols .palo,.gr-anillo .avance{
    transition:background var(--rapido), width var(--entra), height var(--entra);}
}

/* Los gráficos van de dos en dos: con auto-fit caben tres de 280 px por fila y
   una serie de doce meses en 280 px es una fila de rayitas. */
.grid.graficos{grid-template-columns:repeat(auto-fit,minmax(420px,1fr));}
@media (max-width:900px){ .grid.graficos{grid-template-columns:minmax(0,1fr);} }
/* Con sitio de sobra, el nombre de la barra puede ocupar más y dejar de
   partirse en tres líneas. */
@container (min-width:460px){ .gr-fila{grid-template-columns:minmax(150px,1.3fr) minmax(0,2fr) auto;} }
/* Contenedor de consulta SÓLO en la tarjeta que lleva un gráfico dentro.
   ═══════════════════════════════════════════════════════════════════════════
   Esto era `.card{container-type:inline-size;}` a secas —todas las tarjetas de
   la plataforma— para que funcionara la única regla `@container` que hay, la
   de aquí arriba. Y salía caro: `container-type:inline-size` implica
   `contain:inline-size`, o sea «no mires dentro para calcular el ancho». En
   una rejilla cuyas columnas se dimensionan por el contenido, el navegador se
   queda entonces sin nada de lo que deducir el ancho y la tarjeta sale del
   tamaño de una palabra.

   Se veía en la portada: cuando no hay convocatoria abierta, `inicio.html` le
   quita la clase `g3` a la rejilla para dejar una sola tarjeta centrada, y sin
   `g3` no queda ninguna columna declarada. La tarjeta «Te avisamos en cuanto
   abra» salía de 50 px de ancho —un renglón por palabra— en medio de una
   franja de 1.200. Medido en el navegador: quitándole `container-type` a mano,
   520 px; con él, 50. Ni una columna `1fr` explícita ni `justify-self:stretch`
   lo arreglaban, porque el problema no era la columna: era la contención.

   `:has()` acota la contención a lo que de verdad la necesita. El resto de las
   tarjetas vuelven a medirse como cualquier caja. */
.card:has(.gr-fila){container-type:inline-size;}

/* ── LO QUE SE QUEDA PEGADO TIENE QUE TAPAR ────────────────────────────────
   Una barra pegajosa se queda quieta mientras el resto se desplaza POR DEBAJO
   de ella. Si es translúcida, lo de debajo se lee a través: dos textos en el
   mismo sitio, que es lo que se veía en la evaluación —el aviso «esta
   evaluación no tiene preguntas» asomando por dentro de la barra del examen.

   No es un problema de márgenes: por mucho aire que se ponga, al desplazar
   vuelven a encontrarse. Lo que hace falta es que la barra sea opaca.

   La barra superior, la lateral y la de abajo son la excepción y por eso no
   están aquí: llevan `backdrop-filter`, que desenfoca lo de detrás hasta
   volverlo textura. Ahí la transparencia es el efecto, no un descuido. */
/* La especificidad importa aquí más que en ningún otro sitio. La regla del
   vidrio es `:root:not([data-estilo="plano"]) :is(.card,.caja)` —(0,3,0)— y le pone
   `position:relative` a toda tarjeta. Un `.pegajosa` a secas —(0,1,0)— pierde,
   así que la barra dejaba de ser pegajosa en cuanto se elegía cualquier estilo
   que no fuera el plano… pero el `top:70px` sobrevivía, y sobre un elemento
   relativo eso lo empuja 70 px hacia abajo SIN reservarle el hueco: se
   plantaba encima de la primera pregunta. Ése era el solapamiento, y por eso
   no se arreglaba con márgenes: no era un problema de aire. */
:root .card.pegajosa,
:root .pegajosa{position:sticky;z-index:20;}
:root:not([data-estilo="plano"]) :is(.card,.caja).pegajosa,
:root:not([data-estilo="plano"]) .pegajosa > :is(.card,.caja){
  background:var(--papel);
  -webkit-backdrop-filter:var(--desenfoque);
  backdrop-filter:var(--desenfoque);
}
/* El encabezado de una tabla larga es el mismo caso: se queda arriba mientras
   las filas pasan por debajo. Estaba al 90 % de opacidad, así que cada fila
   se veía como un fantasma cruzando los títulos de las columnas. */
:root:not([data-estilo="plano"]) thead th{
  background:var(--papel);
  -webkit-backdrop-filter:var(--desenfoque);
  backdrop-filter:var(--desenfoque);
}

/* ── el panel de apariencia ────────────────────────────────────────────────
   Vivía dentro de la hoja de Configuración. Al poder abrirse desde el pie del
   menú en cualquier pantalla, sus estilos tienen que estar donde están todos. */
.sub-ap{font-size:var(--t-xs);color:var(--tinta-3);font-weight:var(--peso-fuerte);
  margin:var(--e3) 0 var(--e1);}
.paletas{display:grid;grid-template-columns:repeat(auto-fill,minmax(215px,1fr));gap:var(--e1);}
/* La muestra es el argumento: el nombre se lee después de haber visto el color. */
.pal{display:flex;align-items:center;gap:var(--e2);padding:var(--e1) var(--e2);
  border:1px solid var(--filete);border-radius:var(--r);background:var(--papel);
  cursor:pointer;text-align:left;min-height:56px;
  transition:border-color var(--rapido),background var(--rapido);}
.pal:hover{border-color:var(--filete-fuerte);}
.pal[aria-pressed="true"]{border-color:var(--primary);background:var(--primary-suave);}
.pal .tiras{display:flex;flex:none;border-radius:var(--r-full);overflow:hidden;
  box-shadow:0 0 0 1px var(--filete);}
.pal .tiras i{width:15px;height:26px;display:block;}
.pal .nom{font-size:var(--t-sm);font-weight:var(--peso-fuerte);line-height:1.25;}
.pal .des{font-size:var(--t-xs);color:var(--tinta-2);line-height:1.35;}
.ap-panel .sub-ap:first-child{margin-top:0;}

/* ── la portada de un curso ────────────────────────────────────────────────
   Una lista de programas sin imágenes es una lista de párrafos: todos se
   parecen y hay que leerlos para distinguirlos. Con la portada se reconoce el
   suyo de un vistazo, que es lo único que hace un estudiante en esta pantalla.

   Y cuando un curso todavía no tiene foto no se deja un rectángulo gris: se
   pinta una tapa con su inicial, sacada de la paleta de quien mira. Un hueco
   gris parece un error de carga; una tapa con letra parece una decisión. */
.portada{display:block;width:100%;height:132px;object-fit:cover;
  background:var(--hueco);border-radius:var(--r) var(--r) 0 0;}
.portada-vacia{display:flex;align-items:center;justify-content:center;
  font-family:var(--titulo, inherit);font-size:38px;font-weight:var(--peso-fuerte);
  color:var(--on-primary);letter-spacing:-.03em;
  background:linear-gradient(135deg,
    color-mix(in oklab, var(--primary) calc(var(--mezcla, 50) * 1%), var(--secondary)),
    color-mix(in oklab, var(--primary) 85%, #000));}
/* La tarjeta con portada pega la imagen a los bordes: el padding va dentro. */
.card.con-portada{padding:0;overflow:hidden;}
.card.con-portada > .dentro{padding:var(--e3);}
@media (max-width:720px){ .portada{height:112px;} }

/* ── La tarjeta del curso se levanta al pasar por encima ───────────────────
   Los cursos son lo único de esta pantalla en lo que se hace clic, y hasta
   ahora no lo parecían: se veían igual que las tarjetas de dato, que no llevan
   a ninguna parte. Levantarlas un poco es la forma más barata de decir «esto
   se abre» sin escribirlo.

   `perspective` en el contenedor y no en cada tarjeta: si va en cada una, el
   punto de fuga se recalcula por tarjeta y las de los extremos se inclinan
   hacia lados distintos. Puesto en el padre, todas comparten el mismo punto y
   el conjunto se lee como una sola superficie.

   `will-change` no se pone: sobre una rejilla de tarjetas obliga al navegador
   a mantener una capa por cada una, y en un teléfono eso se nota más que la
   animación que pretende suavizar. */
.tarjetas-elevables{perspective:1100px;}
.tarjetas-elevables > .card.con-portada{
  transition:transform var(--entra) ease, box-shadow var(--entra) ease;
  transform-style:preserve-3d;}
.tarjetas-elevables > .card.con-portada:hover,
.tarjetas-elevables > .card.con-portada:focus-within{
  transform:translateY(-6px) rotateX(1.6deg) scale(1.014);
  box-shadow:0 18px 34px -18px rgba(0,0,0,.32), 0 3px 10px -4px rgba(0,0,0,.16);}
/* La portada acompaña con un acercamiento mínimo. Más que esto y se ve el
   recorte moverse, que parece un fallo en vez de un detalle. */
.tarjetas-elevables > .card.con-portada .portada{
  transition:transform var(--entra) ease;}
.tarjetas-elevables > .card.con-portada:hover .portada{transform:scale(1.045);}

/* Quien pidió que no le muevan la pantalla no se la movemos. No es un extra:
   para algunas personas el movimiento marea de verdad. */
@media (prefers-reduced-motion:reduce){
  .tarjetas-elevables > .card.con-portada,
  .tarjetas-elevables > .card.con-portada .portada{transition:none;}
  .tarjetas-elevables > .card.con-portada:hover,
  .tarjetas-elevables > .card.con-portada:focus-within{
    transform:none;box-shadow:var(--sombra);}
  .tarjetas-elevables > .card.con-portada:hover .portada{transform:none;}
}

/* El botón que abre el curso. Es la acción principal de la pantalla y estaba
   del mismo tamaño que «Ver programa», que no lo es. */
.btn.abrir-curso{min-height:44px;padding:var(--e2) var(--e3);font-size:var(--t-md);flex:1;}
.btn.abrir-curso .material-symbols-outlined{font-size:20px;}

/* ═══════════════════════════════════════════════════════════════════════════
   EL PRIMER DÍA, Y CÓMO VOY
   ═══════════════════════════════════════════════════════════════════════════ */

/* La bienvenida (mejora 2). Ocupa sitio a propósito y sólo una vez: es la
   pantalla donde se decide si alguien empieza el curso hoy o «mañana lo
   miro». En cuanto se ve la primera lección desaparece sola. */
.bienvenida{position:relative;text-align:center;padding:var(--e4) var(--e3);
  background:linear-gradient(150deg,
    color-mix(in oklab, var(--primary) 12%, var(--papel)),
    color-mix(in oklab, var(--secondary) 8%, var(--papel)));
  border-color:color-mix(in oklab, var(--primary) 26%, var(--filete));}
.bienvenida .ico{font-size:40px;color:var(--primary);}
.bienvenida h2{margin:var(--e1) 0 var(--e1);font-size:var(--t-lg);}
.bienvenida p{margin:0 auto var(--e1);max-width:52ch;font-size:var(--t-sm);line-height:1.6;}
.bienvenida .btn{margin-top:var(--e2);}
.bienvenida .cerrar{position:absolute;top:var(--e1);right:var(--e1);}

/* Las pistas de la tarjeta de curso: qué falta para el certificado y cómo va
   contra el grupo (mejoras 6 y 10). Son una frase, no una tarjeta dentro de
   otra: al lado del progreso y con el mismo peso que el resto del texto. */
.pistas{margin-top:var(--e1);display:flex;flex-direction:column;gap:4px;}
.pista{display:flex;align-items:flex-start;gap:6px;margin:0;
  font-size:var(--t-xs);line-height:1.5;color:var(--tinta-2);}
.pista .material-symbols-outlined{font-size:16px;flex:none;margin-top:1px;
  color:var(--tinta-3);}
.pista.ok{color:var(--ok);} .pista.ok .material-symbols-outlined{color:var(--ok);}
.pista.warn{color:var(--warn);} .pista.warn .material-symbols-outlined{color:var(--warn);}

/* ── el buscador de arriba, con resultados (mejora 22) ─────────────────────
   Cuelga del propio campo para que no se despegue al desplazar la página, y
   por encima de todo: si algo la tapa, la lista existe pero no sirve. */
.gsearch-res{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:60;
  background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  box-shadow:var(--sombra-alta, 0 12px 32px rgba(0,0,0,.16));
  max-height:min(60vh, 420px);overflow:auto;padding:4px;}
.gsearch-item{display:flex;align-items:center;gap:var(--e1);padding:var(--e1);
  border-radius:var(--r-chico, 8px);text-decoration:none;color:var(--tinta);}
.gsearch-item .crece{display:flex;flex-direction:column;min-width:0;}
.gsearch-item b{font-size:var(--t-sm);font-weight:var(--peso-fuerte);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.gsearch-item .tiny{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.gsearch-item .material-symbols-outlined{font-size:19px;color:var(--tinta-3);flex:none;}
.gsearch-item:hover, .gsearch-item.on{background:var(--primary-suave);}
.gsearch-nada{padding:var(--e2);font-size:var(--t-sm);color:var(--tinta-2);}
@media(max-width:720px){ .gsearch-res{position:fixed;top:56px;left:8px;right:8px;} }

/* ── qué hay que hacer hoy (mejora 24) ─────────────────────────────────────
   No son indicadores: son botones. Cada uno lleva a la pantalla donde se
   despacha eso, y los que están a cero se apagan en vez de desaparecer —saber
   que no hay nada pendiente también es información. */
.pendientes{display:grid;gap:var(--e1);
  grid-template-columns:repeat(auto-fill, minmax(220px, 1fr));}
.pendiente{display:flex;align-items:center;gap:var(--e2);padding:var(--e2);
  border:1px solid var(--filete);border-radius:var(--r);text-decoration:none;
  color:var(--tinta);background:var(--papel);}
.pendiente:hover{border-color:var(--primary);background:var(--hueco);}
.pendiente .n{font-size:var(--t-xl, 26px);font-weight:var(--peso-fuerte);
  font-variant-numeric:tabular-nums;line-height:1;min-width:1.6ch;text-align:right;}
.pendiente .que{font-size:var(--t-sm);line-height:1.35;}
.pendiente.vacio{opacity:.5;}
.pendiente.warn .n{color:var(--warn);}
.pendiente.err .n{color:var(--error);}
.pendiente.info .n{color:var(--info);}

/* El botón de exportar que se le pone sola a cada tabla (mejora 29). Cuando la
   tarjeta no tiene fila de título donde colgarlo, va arriba a la derecha y en
   gris: es una salida, no la acción de la pantalla. */
.card.con-exportar{position:relative;}
.card.con-exportar > .exportar-tabla{position:absolute;top:var(--e1);right:var(--e1);
  z-index:2;color:var(--tinta-3);}
.card.con-exportar > .exportar-tabla:hover{color:var(--tinta);}
@media print{ .exportar-tabla{display:none!important;} }

/* ═══════════════════════════════════════════════════════════════════════════
   EL REPRODUCTOR DE LA CASA
   ═══════════════════════════════════════════════════════════════════════════
   El <iframe> de YouTube va debajo del todo y sin recibir un solo evento: la
   lámina de encima se los come, y sin ratón encima YouTube no llega a dibujar
   su título, su canal ni su botón de «Mirar en YouTube». No los tapamos —no
   aparecen. */
.repro{position:relative;width:100%;height:100%;background:#000;overflow:hidden;
  border-radius:inherit;outline:none;user-select:none;-webkit-user-select:none;}
.repro:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
/* `pointer-events:none` en el marco es la defensa de verdad, y va aquí y no en
   la lámina por un motivo: la lámina es un argumento de geometría —«este
   rectángulo tapa aquel»— y los argumentos de geometría se rompen solos en
   cuanto alguien cambia un alto. Esto no depende de ninguna medida: el
   <iframe> no recibe un solo evento de ratón, esté donde esté. Sin eventos,
   YouTube no dibuja su título ni su canal ni su «Mirar en YouTube», y su menú
   del clic derecho —el que ofrece «Copiar vínculo»— no llega a existir.
   La API sigue funcionando: habla por `postMessage`, no por el ratón. */
.repro-video{position:absolute;inset:0;pointer-events:none;}
.repro-video iframe{width:100%!important;height:100%!important;border:0;display:block;}
.repro-hueco{width:100%;height:100%;}

/* La portada, encima del <iframe> y debajo de todo lo demás. Existe porque
   `pointer-events:none` no llega al momento anterior al primer play: ahí el
   <iframe> dibuja SU portada —título, canal, botón rojo, «Mirar en YouTube»—
   sin que nadie haya movido el ratón, porque ése es su estado de partida. Y es
   justo el momento en que todo el mundo está mirando. Así que se tapa con la
   misma imagen del vídeo, sin una letra encima. */
.repro-tapa{position:absolute;inset:0;z-index:1;width:100%;height:100%;
  object-fit:cover;background:#000;display:block;pointer-events:none;}
.repro.arrancado .repro-tapa{display:none;}

/* La lámina, que ahora sólo sirve para recoger el clic de reproducir y pausar.
   Cubre el recuadro ENTERO. Antes se paraba antes de la franja de los mandos
   —para no pelearse con la barra— y ese hueco era justo el fallo: al pasar el
   ratón por ahí llegaba al <iframe> y YouTube sacaba toda su interfaz. No hacía
   falta el hueco: los mandos van en z-index 5, por encima de la lámina, así que
   reciben sus clics igual. */
.repro-lamina{position:absolute;inset:0;z-index:2;
  cursor:pointer;background:transparent;}
.repro.compacto{--repro-mandos:44px;}

/* El velo de la pausa. Al PAUSAR, YouTube saca su cartel —título, canal,
   compartir— aunque nadie tenga el ratón encima: es su estado de pausa, no una
   reacción a nada, así que `pointer-events` tampoco llega aquí. Se le pone una
   banda opaca por encima.

   Sólo la banda de arriba, y no la portada entera: quien pausa una clase suele
   estar mirando el fotograma —una fórmula, una diapositiva— y taparlo para
   esconder un logotipo sería cambiar un problema por otro peor. Abajo no hace
   falta banda: los mandos ya son opacos. */
.repro-velo{position:absolute;z-index:3;left:0;right:0;top:0;height:16%;min-height:52px;
  background:linear-gradient(to bottom, rgba(0,0,0,.97) 62%, rgba(0,0,0,0));
  opacity:0;transition:opacity var(--rapido);pointer-events:none;}
.repro.arrancado:not(.sonando) .repro-velo{opacity:1;}

/* La marca de agua, encima de todo lo del vídeo y por debajo de los mandos.
   `pointer-events:none` para que no se coma los clics. Va sobre NUESTRO
   recuadro, así que a pantalla completa sigue puesta. */
.repro-agua{position:absolute;z-index:3;pointer-events:none;user-select:none;
  font-size:12px;line-height:1.35;letter-spacing:.02em;
  color:rgba(255,255,255,.42);text-shadow:0 1px 3px rgba(0,0,0,.85);
  transition:top .9s ease, left .9s ease;white-space:nowrap;}
.repro.compacto .repro-agua{font-size:11px;}
@media(max-width:700px){ .repro-agua{font-size:10px;} }

/* El botón grande del centro: sólo mientras está en pausa. Es el gesto que
   todo el mundo busca al abrir un vídeo. */
.repro-grande{position:absolute;z-index:4;top:50%;left:50%;transform:translate(-50%,-50%);
  width:68px;height:68px;border-radius:var(--r-full);border:none;cursor:pointer;
  background:rgba(0,0,0,.55);color:#fff;display:grid;place-items:center;
  transition:opacity var(--rapido), transform var(--rapido);}
.repro-grande .material-symbols-outlined{font-size:38px;}
.repro-grande:hover{background:rgba(0,0,0,.75);transform:translate(-50%,-50%) scale(1.06);}
.repro.sonando .repro-grande{opacity:0;pointer-events:none;}

/* Los mandos. Siempre visibles: esconderlos y hacer que aparezcan al pasar el
   ratón obliga a mover el ratón para saber por dónde va el vídeo, y en una
   tableta directamente no hay ratón que pasar. */
/* Opacos, no en degradado. El degradado dejaba ver a través de su parte de
   arriba justo la esquina donde YouTube pone su logotipo, así que la barra
   escondía a medias lo que venía a esconder. */
.repro-mandos{position:absolute;z-index:5;left:0;right:0;bottom:0;
  display:flex;align-items:center;gap:8px;padding:8px 10px;
  background:rgba(0,0,0,.94);}
.repro-btn{border:none;background:transparent;color:#fff;cursor:pointer;
  width:34px;height:34px;border-radius:var(--r-full);display:grid;place-items:center;flex:none;}
.repro-btn:hover{background:rgba(255,255,255,.16);}
.repro-btn:focus-visible{outline:2px solid #fff;outline-offset:1px;}
.repro-btn .material-symbols-outlined{font-size:21px;}
.repro.compacto .repro-btn.sec{display:none;}
.repro.compacto .repro-mandos{padding:6px 8px;gap:6px;}

.repro-barra{position:relative;flex:1;min-width:40px;height:16px;cursor:pointer;
  display:flex;align-items:center;touch-action:none;}
.repro-barra::before{content:'';position:absolute;left:0;right:0;height:4px;
  border-radius:2px;background:rgba(255,255,255,.32);}
.repro-visto{position:absolute;left:0;height:4px;border-radius:2px;
  background:var(--primary);width:0;}
.repro-punto{position:absolute;width:12px;height:12px;border-radius:var(--r-full);
  background:var(--primary);left:0;transform:translateX(-50%);
  box-shadow:0 0 0 3px rgba(0,0,0,.35);}
.repro-barra:focus-visible{outline:2px solid #fff;outline-offset:2px;border-radius:4px;}

.repro-tiempo{color:#fff;font-size:var(--t-xs);font-variant-numeric:tabular-nums;
  flex:none;opacity:.9;}
.repro.compacto .repro-tiempo{font-size:10px;}

/* El panel de ajustes. Va por encima de la lámina y de los mandos, y dentro
   del recuadro: así sigue funcionando a pantalla completa. Se le pone un alto
   máximo porque en un vídeo pequeño la lista de calidades no cabe entera. */
.repro-panel{position:absolute;z-index:6;right:8px;bottom:calc(var(--repro-mandos, 52px) + 6px);
  min-width:196px;max-width:min(280px, calc(100% - 16px));max-height:min(64%, 320px);
  overflow-y:auto;padding:8px;color:#fff;background:rgba(16,16,16,.96);
  border:1px solid rgba(255,255,255,.14);border-radius:10px;
  box-shadow:0 10px 30px rgba(0,0,0,.5);}
.repro-panel[hidden]{display:none;}
.repro-grupo + .repro-grupo{margin-top:8px;padding-top:8px;
  border-top:1px solid rgba(255,255,255,.1);}
.repro-grupo h4{margin:0 0 4px;font-size:10px;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:rgba(255,255,255,.6);}
.repro-op{display:flex;align-items:center;gap:6px;width:100%;padding:5px 6px;border:0;
  border-radius:6px;background:transparent;color:#fff;font:inherit;font-size:12px;
  text-align:left;cursor:pointer;}
.repro-op:hover{background:rgba(255,255,255,.14);}
.repro-op:focus-visible{outline:2px solid #fff;outline-offset:-2px;}
.repro-op.on{background:rgba(255,255,255,.1);font-weight:600;}
.repro-op .material-symbols-outlined{font-size:15px;width:15px;flex:none;}
.repro-nota{margin:6px 2px 0;font-size:10px;line-height:1.35;color:rgba(255,255,255,.55);}
.repro-espera{margin:0;padding:5px 6px;font-size:11px;line-height:1.35;
  color:rgba(255,255,255,.6);}
.repro-vol{padding:2px 6px 4px;}
.repro-vol input{width:100%;accent-color:#fff;}

/* A pantalla completa manda nuestro recuadro, no el <iframe>: por eso la marca
   de agua y los mandos siguen ahí. */
.repro:fullscreen{width:100vw;height:100vh;border-radius:0;}
.repro:fullscreen .repro-agua{font-size:15px;}
.repro:fullscreen .repro-panel{max-height:min(70%, 420px);}

.repro-sin{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:6px;height:100%;padding:30px;text-align:center;color:#fff;background:#000;}
.repro-sin .material-symbols-outlined{font-size:44px;opacity:.65;}
.repro-sin b{font-size:var(--t-sm);}
.repro-sin span:not(.material-symbols-outlined){font-size:var(--t-xs);opacity:.7;max-width:38ch;}

@media print{ .repro{display:none!important;} }

/* ── una tarjeta que se pliega ────────────────────────────────────────────
   Un historial que sólo crece —solicitudes, movimientos, avisos ya leídos—
   no puede ocupar media pantalla debajo de lo que uno viene a mirar. Como
   <details> sigue siendo una tarjeta: sólo cambia que su título es el tirador
   y que la flecha dice en qué estado está. */
details.card > summary{list-style:none;cursor:pointer;}
details.card > summary::-webkit-details-marker{display:none;}
details.card > summary .material-symbols-outlined{color:var(--tinta-3);
  transition:transform var(--rapido);}
details.card[open] > summary .material-symbols-outlined{transform:rotate(180deg);}

/* ── el cobro de un programa, plegado ─────────────────────────────────────
   La línea cerrada tiene que contestar sola las dos preguntas que trae quien
   cobra: cuánto lleva pagado de esto y si hay algo vencido. Por eso la barra
   y el chip van en la cabecera, no dentro. */
.resumen-programa{display:flex;align-items:center;gap:var(--e2);flex-wrap:wrap;}
.resumen-programa .t{font-weight:var(--peso-fuerte);}
.resumen-programa .s{font-size:var(--t-xs);color:var(--tinta-3);margin-top:2px;}
.avance-programa{flex:none;width:190px;}
.avance-programa .bar{margin-bottom:4px;}
details.card > summary + .table-wrap{margin-top:var(--e2);}
details.card h3.sec{margin-top:var(--e3);}
@media (max-width:720px){
  /* En un teléfono la barra debajo del nombre, a lo ancho: al lado se queda
     en ochenta píxeles y deja de decir nada. */
  .avance-programa{width:100%;order:3;}
  .resumen-programa .grow{flex:1 1 60%;}
}

/* El retrato del perfil: redondo, con el hueco marcado cuando todavía no hay
   foto —un círculo vacío se lee como «falta algo», que es la verdad—. */
.retrato-perfil{width:96px;height:96px;border-radius:var(--r-full);flex:none;overflow:hidden;
  background:var(--hueco);border:1px solid var(--filete);
  display:flex;align-items:center;justify-content:center;}
.retrato-perfil img{width:100%;height:100%;object-fit:cover;display:block;}
.retrato-perfil .material-symbols-outlined{font-size:44px;color:var(--filete-fuerte);}

/* ── La cabecera de un perfil ───────────────────────────────────────────────
   Un perfil que se reparte para que lo abra alguien que no te conoce necesita
   una cara y un nombre antes que un formulario. «Mi perfil» abría con los
   campos de la cédula y la foto estaba cuatro tarjetas más abajo: servía para
   corregir datos, no para enseñárselo a nadie.

   Se usa en dos sitios —el perfil propio y la página pública— y se ve igual en
   los dos a propósito: es la misma persona, y quien publica tiene que poder
   reconocer lo que va a ver el otro. Por eso vive aquí y no dentro de una
   pantalla. */
.perfil-cabeza{padding:0;overflow:hidden;}

.perfil-portada{height:190px;position:relative;
  /* Sin foto no se deja un hueco gris: un degradado de la casa ya es una
     portada, y así el perfil se puede repartir desde el primer día. */
  background:linear-gradient(115deg,var(--primary-suave),var(--secondary-container));}
.perfil-portada img{width:100%;height:100%;object-fit:cover;display:block;}
.perfil-portada .encima{position:absolute;right:var(--e2);bottom:var(--e2);
  display:flex;gap:var(--e1);}
@media (max-width:640px){
  .perfil-portada{height:130px;}
  /* En un teléfono «Poner una portada» no cabe y el recorte de la tarjeta le
     comía la primera letra. El icono de la cámara ya lo dice; el nombre
     completo se queda en el `aria-label` para quien no ve el icono. */
  .perfil-portada .encima .solo-ancho{display:none;}
}

/* La foto pisa la portada, que es lo que hace que esto se lea como un perfil y
   no como una tarjeta con una imagen arriba.

   El desplazamiento hacia arriba va SÓLO en la foto, no en la fila entera. Con
   la fila entera subida, el nombre quedaba dentro de la portada — y encima
   debajo de ella: la portada lleva `position:relative` y eso la pinta por
   encima de lo que no está posicionado, así que el nombre se veía cortado por
   la mitad. Sube la foto; el nombre se queda donde se lee. */
.perfil-identidad{display:flex;gap:var(--e2);align-items:flex-end;flex-wrap:wrap;
  padding:var(--e2) var(--e3) var(--e3);position:relative;}
.perfil-identidad .retrato-perfil{width:132px;height:132px;margin-top:-76px;
  border:4px solid var(--papel);box-shadow:var(--sombra);background:var(--papel);}
.perfil-identidad .quien{min-width:200px;flex:1 1 240px;padding-bottom:var(--e1);}
.perfil-identidad h1,.perfil-identidad h2{margin:0;font-size:var(--t-2xl);
  letter-spacing:-.03em;line-height:1.1;}
.perfil-identidad .titular{color:var(--tinta-2);font-size:var(--t-sm);margin-top:2px;}
.perfil-identidad .credenciales{display:inline-flex;align-items:center;gap:var(--e0);
  color:var(--secondary);font-size:var(--t-xs);font-weight:var(--peso-fuerte);
  margin-top:var(--e0);}
.perfil-identidad .credenciales .material-symbols-outlined{font-size:16px;}
.perfil-identidad .manos{display:flex;gap:var(--e1);flex-wrap:wrap;
  padding-bottom:var(--e1);margin-left:auto;}

/* Un botón encima de la foto: cambiarla es lo que todo el mundo intenta hacer
   tocándola, y hasta ahora había que bajar a otra tarjeta a buscarlo. */
.retrato-editable{position:relative;padding:0;cursor:pointer;}
.retrato-editable .lapiz{position:absolute;right:2px;bottom:2px;width:34px;height:34px;
  border-radius:var(--r-full);background:var(--primary);color:var(--on-primary);
  display:flex;align-items:center;justify-content:center;border:3px solid var(--papel);}
.retrato-editable .lapiz .material-symbols-outlined{font-size:17px;color:inherit;}

@media (max-width:640px){
  .perfil-identidad{justify-content:center;text-align:center;}
  .perfil-identidad .retrato-perfil{width:104px;height:104px;margin-top:-62px;}
  .perfil-identidad .quien{flex-basis:100%;}
  .perfil-identidad .manos{margin-left:0;width:100%;justify-content:center;}
  .perfil-identidad .credenciales{justify-content:center;}
}

/* Las cifras de debajo: cuántos títulos, cuántas insignias. Es lo primero que
   mira quien está a punto de contratar. */
.perfil-cifras{display:flex;gap:var(--e3);flex-wrap:wrap;
  border-top:1px solid var(--filete);padding:var(--e2) var(--e3);}
.perfil-cifras > div{display:flex;flex-direction:column;}
.perfil-cifras b{font-family:var(--cifra);font-size:var(--t-lg);line-height:1.1;
  font-variant-numeric:tabular-nums;}
.perfil-cifras span{font-size:var(--t-xs);color:var(--tinta-2);}

/* El enlace que se reparte, a la vista y en la letra de las cifras: se lee
   carácter a carácter cuando alguien lo dicta o lo comprueba. El fondo y el
   filete no se pintan aquí: los pone `caja`, y así el recuadro sigue el estilo
   que la persona haya elegido en vez de quedarse en uno fijo. */
.enlace-compartir{display:flex;align-items:center;gap:var(--e1);flex-wrap:wrap;
  padding:var(--e1) var(--e2);font-family:var(--cifra);font-size:var(--t-sm);
  word-break:break-all;}

/* Lo que queda del perfil, y por qué está aquí y no en su pantalla.
   ═══════════════════════════════════════════════════════════════════════════
   El perfil dejó de ser una pantalla del estudiante: hoy lo montan también el
   equipo y los profesores desde sus carpetas. Con estas reglas en el `<style>`
   de una de las tres páginas, las otras dos salían sin ellas — la lista de
   diplomas sin filas y los datos amontonados en una columna. */
.sub-perfil{font-size:var(--t-xs);color:var(--tinta-3);font-weight:var(--peso-fuerte);
  margin-bottom:var(--e1);}

/* La lista de diplomas. Cada fila entera es el botón: en un teléfono apuntar a
   un enlace pequeño dentro de una fila es la diferencia entre abrirlo a la
   primera y pelearse con el dedo. */
.lista-diplomas{display:flex;flex-direction:column;}
.fila-diploma{display:flex;align-items:center;gap:var(--e1);width:100%;
  padding:var(--e2) var(--e1);background:none;border:0;border-bottom:1px solid var(--filete);
  text-align:left;color:inherit;font:inherit;cursor:pointer;min-height:56px;}
.fila-diploma:last-child{border-bottom:0;}
.fila-diploma:hover{background:var(--hueco);}
.fila-diploma .crece{flex:1;min-width:0;display:flex;flex-direction:column;}
.fila-diploma .crece b{overflow-wrap:anywhere;}
.fila-diploma > .material-symbols-outlined{color:var(--gold,#C9A227);flex:none;}
.fila-diploma .flecha{color:var(--tinta-3);}

/* Los datos, para mirarlos. Dos columnas en pantalla ancha y una en el
   teléfono; lo que falta se marca, porque un hueco vacío en una lista pasa
   desapercibido y «falta» no. */
.datos-mirar{display:grid;gap:0;margin:var(--e2) 0 0;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));}
.datos-mirar .dato{padding:10px 0;border-bottom:1px solid var(--filete);
  display:flex;flex-direction:column;gap:2px;padding-right:var(--e2);}
.datos-mirar dt{font-size:var(--t-xs);color:var(--tinta-3);
  font-weight:var(--peso-fuerte);}
.datos-mirar dd{margin:0;overflow-wrap:anywhere;}
.datos-mirar dd.falta{color:var(--warn);font-weight:var(--peso-fuerte);}

/* ══════════════════════════ LOS MENSAJES ═══════════════════════════════════
   La bandeja a la izquierda y la conversación a la derecha, como cualquier
   buzón. En el teléfono no caben las dos: se enseña la lista, y al abrir una
   conversación ésta la tapa entera —el cuerpo lleva `msg-abierta` mientras hay
   una abierta— porque media pantalla para cada cosa no sirve para ninguna.

   Las burbujas no se definen aquí: son las mismas `.chat-linea` y
   `.chat-burbuja` que usa Cemi. Dos conversaciones con dos aspectos distintos
   en la misma casa se notan, y no para bien. */
.msg-tablero{display:grid;grid-template-columns:minmax(260px, 360px) 1fr;
  gap:var(--e2);align-items:start;}
.msg-lista{max-height:calc(100vh - 220px);overflow-y:auto;}
.msg-hilo{display:flex;flex-direction:column;min-height:420px;
  max-height:calc(100vh - 220px);}

/* Cada conversación de la lista. Toda la fila es el botón: apuntar con el dedo
   a un enlace pequeño dentro de una fila es pelearse con la pantalla.
   sin-caja: son filas dentro de la tarjeta, separadas por una raya. */
.msg-fila{display:flex;align-items:flex-start;gap:var(--e1);width:100%;
  padding:var(--e1);background:none;border:0;border-bottom:1px solid var(--filete);
  text-align:left;color:inherit;font:inherit;cursor:pointer;}
.msg-fila:last-child{border-bottom:0;}
.msg-fila:hover{background:var(--hueco);}
.msg-fila.abierta{background:var(--hueco);}
.msg-fila .crece{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
.msg-arriba{display:flex;justify-content:space-between;gap:var(--e1);align-items:baseline;}
.msg-fila:focus-visible{outline:2px solid var(--primary);outline-offset:-2px;}

.msg-cab{display:flex;align-items:center;gap:var(--e1);
  padding-bottom:var(--e1);border-bottom:1px solid var(--filete);}
/* El botón de volver a la lista sólo tiene sentido donde la lista se esconde,
   o sea en el teléfono. En pantalla ancha las dos columnas están a la vista y
   un «atrás» que no lleva a ninguna parte sobra. */
.solo-estrecho{display:none;}

/* El contador de sin leer, pegado a la derecha de su entrada del menú. */
.nav-item .nav-cuenta{margin-left:auto;flex:none;}

/* ══════════════ LA LÁMINA DE UN DIPLOMA, EN PEQUEÑO Y EN GRANDE ═════════════
   Vivían en el `<style>` del panel del egresado, cuando sólo él veía diplomas.
   Ahora también los enseña la ficha que mira el equipo, y dos copias de estas
   reglas serían dos miniaturas distintas dentro de un mes.

   sin-caja: enmarca una lámina, no un panel. El diploma ya trae su fondo y sus
   colores, así que esto debe ser un filete neutro y no heredar el relleno ni la
   sombra de una tarjeta, que competirían con él. */
.dip-mini{flex:none;width:168px;min-height:56px;padding:0;border:1px solid var(--filete);
  border-radius:var(--r);background:var(--hueco);cursor:pointer;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  transition:border-color var(--rapido);}
.dip-mini:hover{border-color:var(--primary);}
.dip-mini:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.dip-mini canvas{display:block;width:100%;height:auto;}

/* La fila de un diploma: la lámina, el texto y los botones. La lámina va
   primero y con tamaño fijo porque es lo que la persona viene a ver. */
.dip-fila{display:flex;align-items:center;gap:14px;padding:10px 0;
  border-bottom:1px solid var(--filete);}
.dip-texto{flex:1;min-width:0;}

/* La lámina grande, dentro de la ventana. Se limita por alto además de por
   ancho: un diploma apaisado que ocupe todo el ancho deja los botones de abajo
   fuera de la pantalla, y no se ve que se puede descargar. */
.dip-grande{display:flex;align-items:center;justify-content:center;min-height:180px;
  background:var(--hueco);border-radius:var(--r);padding:10px;}
.dip-grande img{max-width:100%;max-height:70vh;height:auto;display:block;
  box-shadow:0 2px 14px rgba(0,0,0,.18);}

/* En un teléfono la fila se apila y la lámina pasa a ocupar el ancho: a 168 px
   en una columna estrecha no se lee lo que dice el diploma, que es justo para
   lo que está. */
@media(max-width:560px){
  .dip-fila{flex-wrap:wrap;}
  .dip-mini{width:100%;}
  .dip-texto{flex-basis:100%;}
}
/* El nombre de arriba abre su ficha: es lo primero que se intenta cuando no
   sabes con quién estás hablando.
   sin-caja: no dibuja recuadro, sólo se subraya al pasar por encima. */
.msg-quien{display:flex;align-items:center;gap:var(--e1);flex:1;min-width:0;
  background:none;border:0;padding:0;font:inherit;color:inherit;
  text-align:left;cursor:pointer;}
.msg-quien .crece{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.25;}
.msg-quien:hover b{color:var(--primary);text-decoration:underline;}
.msg-quien:focus-visible{outline:2px solid var(--primary);outline-offset:2px;}
.msg-asunto{font-weight:var(--peso-fuerte);padding:var(--e1) 0;}
.msg-cuerpo{flex:1;min-height:200px;padding:var(--e1) 0;}
.msg-hora{font-size:var(--t-xs);opacity:.7;margin-top:4px;}

.msg-pie{display:flex;gap:var(--e1);align-items:flex-end;
  padding-top:var(--e1);border-top:1px solid var(--filete);}
.msg-pie textarea{flex:1;min-width:0;resize:vertical;}

.msg-nada{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--e1);min-height:320px;text-align:center;color:var(--tinta-3);}
.msg-nada .material-symbols-outlined{font-size:44px;opacity:.5;}

/* El directorio y la ficha, dentro de sus ventanas. */
.msg-directorio{max-height:46vh;overflow-y:auto;margin-top:var(--e1);}
/* sin-caja: filas de una lista, no paneles. */
.msg-persona{display:flex;align-items:center;gap:var(--e1);width:100%;
  padding:var(--e1);background:none;border:0;border-bottom:1px solid var(--filete);
  text-align:left;color:inherit;font:inherit;cursor:pointer;}
.msg-persona:last-child{border-bottom:0;}
.msg-persona:hover{background:var(--hueco);}
.msg-persona .crece{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.3;}

.ficha{display:flex;flex-direction:column;gap:var(--e2);}
.ficha-portada{height:110px;border-radius:var(--r);background:var(--hueco);overflow:hidden;}
.ficha-portada img{width:100%;height:100%;object-fit:cover;display:block;}
.ficha-quien{display:flex;align-items:center;gap:var(--e2);margin-top:-46px;
  padding-left:var(--e2);}
.ficha-quien > div{padding-top:46px;}

@media (max-width: 900px) {
  .msg-tablero{grid-template-columns:1fr;}
  .msg-lista{max-height:none;}
  .msg-hilo{max-height:none;}
  /* Una cosa cada vez: con una conversación abierta, la lista se va. */
  body.msg-abierta .msg-lista{display:none;}
  body:not(.msg-abierta) .msg-hilo{display:none;}
  .solo-estrecho{display:inline-flex;}
}

/* ══════════════════════════ EL DIPLOMA ═══════════════════════════════════
   El dibujo del certificado. Vive aquí y no en la pantalla del estudiante
   porque el mismo diploma se enseña en tres sitios —su cuenta, la ficha del
   equipo y el perfil que reparte— y sólo cambia una cosa entre ellos: si
   lleva marca de agua o no.

   El papel es blanco y la tinta oscura A PROPÓSITO, en los dos temas: un
   diploma que en modo noche saliera con el fondo negro se imprimiría negro,
   y quien lo comparte lo comparte como imagen. Es un documento, no una
   pantalla, así que no sigue el tema de la interfaz. */
/* sin-caja: es la hoja del diploma, no un panel de la interfaz. */
.diploma{position:relative;overflow:hidden;border-radius:var(--r);
  background:#fff;color:#15181c;}
.diploma-hoja{border:8px double #cfd8e3;padding:32px 26px;text-align:center;}
.diploma-casa{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:#5b6572;}
.diploma-titulo{margin:14px 0 6px;font-size:24px;color:#0d2440;}
.diploma-pie-linea{margin:0;font-size:12px;color:#5b6572;}
.diploma-nombre{font-size:26px;font-weight:700;margin:12px 0;overflow-wrap:anywhere;}
.diploma-programa{font-size:17px;font-weight:600;margin:10px 0;overflow-wrap:anywhere;}
.diploma-dentro-de{font-size:12px;color:#5b6572;}
.diploma-firmas{display:flex;justify-content:center;gap:26px;margin-top:22px;
  align-items:flex-end;flex-wrap:wrap;}
.diploma-qr img{display:block;margin:0 auto;}
.diploma-firma{border-top:1px solid #8b96a4;padding-top:6px;min-width:190px;font-size:13px;}
.diploma-mini{font-size:11px;color:#5b6572;}
.diploma-codigo{margin-top:20px;overflow-wrap:anywhere;}

/* La marca de agua.
   ───────────────────────────────────────────────────────────────────────
   La dibuja `capaDeMarca()` en assets/certificado.js: una baldosa de SVG con
   el código repetido, que se TESELA por toda la hoja. Aquí sólo se coloca y
   se gira. El giro va en la capa entera y no en cada baldosa para que no
   queden costuras, y la capa se sale por los cuatro lados (`inset:-50%`)
   para que al girar no aparezca ninguna esquina sin cubrir.

   Un primer intento puso el texto centrado con `::after`: cubría las
   esquinas y dejaba el centro del diploma limpio, que es justo el trozo que
   alguien recortaría.

   No deja pulsarse, así que el código de debajo se sigue leyendo y copiando:
   la marca disuade de hacer pasar una captura por el original, no de leer el
   documento. */
.diploma-marca{position:absolute;inset:-50%;
  transform:rotate(-28deg);transform-origin:50% 50%;
  background-repeat:repeat;
  pointer-events:none;user-select:none;}
@media print{
  /* Al imprimir se refuerza: muchas impresoras aclaran tanto los grises
     claros que la marca desaparece y el papel sale como si fuera original. */
  .diploma-marca{filter:contrast(1.5) saturate(0);opacity:1.35;}
}

/* ══════════════════ ENCUADRAR LA FOTO DE PERFIL ══════════════════════════
   El recuadro donde se mueve la imagen. Es cuadrado porque el resultado lo
   es, y lleva encima una máscara redonda para que se vea desde ya lo que va
   a quedar dentro del avatar: enseñar un cuadrado y guardar un círculo es
   pedirle a la persona que adivine.
   sin-caja: el recuadro ES el recorte, no un panel de la interfaz. */
.recorte{position:relative;width:100%;aspect-ratio:1/1;max-width:320px;margin:var(--e2) auto 0;
  overflow:hidden;border-radius:var(--r);background:#111;
  cursor:grab;touch-action:none;user-select:none;}
.recorte:active{cursor:grabbing;}
.recorte img{position:absolute;top:0;left:0;transform-origin:0 0;
  max-width:none;pointer-events:none;-webkit-user-drag:none;}
/* El velo de fuera del círculo. `box-shadow` con un radio enorme pinta todo
   lo que queda por fuera sin necesidad de un segundo elemento. */
.recorte-mascara{position:absolute;inset:0;pointer-events:none;
  border-radius:var(--r);
  box-shadow:0 0 0 9999px rgba(0,0,0,.45) inset;
  -webkit-mask:radial-gradient(circle at 50% 50%, transparent 49.5%, #000 50%);
  mask:radial-gradient(circle at 50% 50%, transparent 49.5%, #000 50%);}

/* ══════════════════════ EL ASISTENTE ═════════════════════════════════════
   El botón flota abajo a la derecha en todas las pantallas y la ventana se
   abre encima. En el teléfono la ventana ocupa la pantalla entera: una caja
   de 380 px en un móvil deja un teclado tapando media conversación.

   El z-index va por debajo de los modales a propósito. Si un diálogo pide
   confirmar algo, el chat NO debe poder taparlo. */
/* El contenido se aparta del botón. Sin esto, la última tarjeta de cualquier
   pantalla queda debajo del asistente y no se puede pulsar: el elemento que
   flota tapa justo la esquina donde acaban las listas. */
.content{padding-bottom:calc(var(--e5) + 56px);}
.chat-boton{position:fixed;right:var(--e2);bottom:var(--e2);z-index:60;
  display:flex;align-items:center;gap:var(--e0);
  padding:6px 14px 6px 6px;border:1px solid var(--filete);border-radius:var(--r-full);
  background:var(--papel);color:var(--tinta);box-shadow:var(--sombra);
  cursor:pointer;transition:transform var(--rapido);}
.chat-boton:hover{transform:translateY(-2px);}
.chat-boton-txt{font-size:var(--t-sm);font-weight:var(--peso-fuerte);}
.mascota{width:44px;height:44px;border-radius:var(--r-full);object-fit:cover;
  background:var(--hueco);display:block;}
.mascota.chica{width:30px;height:30px;flex:0 0 30px;}
.mascota-entera{width:100%;max-width:260px;height:auto;display:block;margin-inline:auto;}

.chat-caja{position:fixed;right:var(--e2);bottom:calc(var(--e2) + 64px);z-index:60;
  width:min(380px, calc(100vw - var(--e2) * 2));height:min(560px, calc(100vh - 140px));
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--papel);border:1px solid var(--filete);border-radius:var(--r);
  box-shadow:var(--sombra);animation:subir var(--entra) ease-out;}
.chat-cab{display:flex;align-items:center;gap:var(--e1);
  padding:var(--e1) var(--e2);border-bottom:1px solid var(--filete);background:var(--hueco);}
.chat-cab > div{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.25;}
.chat-lista{flex:1;overflow-y:auto;padding:var(--e2);
  display:flex;flex-direction:column;gap:var(--e1);}
.chat-linea{display:flex;gap:var(--e0);align-items:flex-end;max-width:100%;}
.chat-linea.mia{justify-content:flex-end;}
.chat-burbuja{max-width:78%;padding:8px 12px;border-radius:var(--r);
  background:var(--hueco);font-size:var(--t-sm);line-height:1.5;
  /* Sin esto una dirección larga o un código de certificado estira la caja y
     saca la conversación de la pantalla. */
  overflow-wrap:anywhere;}
/* La tinta sale del token, no de un blanco fijo: de noche el primario se
   aclara y la letra blanca encima se quedaba en 1,4:1. Mismo caso que el
   botón teal. */
.chat-linea.mia .chat-burbuja{background:var(--primary);color:var(--on-primary);}
.chat-aviso{margin-top:6px;padding-top:6px;border-top:1px solid var(--filete);
  font-size:var(--t-xs);color:var(--warn);}

/* El botón que lleva a la pantalla de la que Cemi acaba de hablar. Va DENTRO
   de la burbuja y no debajo: pertenece a esa respuesta, no a la conversación. */
.chat-ir{display:inline-flex;align-items:center;gap:var(--e0);margin-top:8px;
  padding:6px 12px 6px 8px;border:1px solid var(--primary);border-radius:var(--r-full);
  background:var(--papel);color:var(--primary);font-size:var(--t-xs);
  font-weight:var(--peso-fuerte);text-decoration:none;white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis;}
.chat-ir:hover{background:var(--primary);color:var(--on-primary);}
.chat-ir .material-symbols-outlined{font-size:16px;flex:none;}

/* Un punto en el botón de la mascota cuando hay conversación a medias: se ve
   que sigue ahí sin tener que abrirla. */
.chat-boton.con-hilo{position:fixed;}
.chat-boton.con-hilo::after{content:'';position:absolute;top:4px;right:4px;
  width:9px;height:9px;border-radius:var(--r-full);background:var(--secondary);
  border:2px solid var(--papel);}
.chat-sugerencias{display:flex;flex-wrap:wrap;gap:var(--e0);margin-left:34px;}
.chat-sug{padding:6px 10px;border:1px solid var(--filete);border-radius:var(--r-full);
  background:var(--papel);color:var(--tinta-2);font-size:var(--t-xs);cursor:pointer;}
.chat-sug:hover{border-color:var(--primary);color:var(--primary);}
.chat-pie{display:flex;gap:var(--e0);padding:var(--e1);border-top:1px solid var(--filete);}
.chat-pie input{flex:1;min-width:0;}

/* Los tres puntos de «está escribiendo». Es la única animación en bucle de la
   plataforma, y existe porque sin ella una respuesta que tarda cuatro segundos
   se lee como que el chat se rompió. */
.chat-burbuja.pensando{display:flex;gap:5px;padding:12px;}
.chat-burbuja.pensando span{width:7px;height:7px;border-radius:var(--r-full);
  background:var(--tinta-3);animation:latir 1.1s infinite ease-in-out;}
.chat-burbuja.pensando span:nth-child(2){animation-delay:.15s;}
.chat-burbuja.pensando span:nth-child(3){animation-delay:.3s;}
@keyframes latir{0%,60%,100%{opacity:.25;transform:translateY(0);}
  30%{opacity:1;transform:translateY(-3px);}}
@media (prefers-reduced-motion:reduce){
  /* La regla global deja la animación en 0,01 ms, lo que aquí congela los tres
     puntos apagados y parece un fallo. Se quedan encendidos y quietos. */
  .chat-burbuja.pensando span{opacity:.6;}
}

@media (max-width:640px){
  .chat-caja{right:0;bottom:0;width:100vw;height:100dvh;max-height:none;
    border-radius:0;border:0;}
  .chat-boton-txt{display:none;}
  .chat-boton{padding:5px;}
  /* Con la ventana a pantalla completa el botón queda debajo y se ve el borde
     asomando por una esquina. */
  body.con-chat-abierto .chat-boton{display:none;}
}

/* ═══ El temario público: los diplomados y sus módulos ═══════════════════════
   Las páginas de /programas/ las escribe herramientas/generar-seo.mjs y son
   las que ve quien llega desde Google — muchas veces, lo primero que sabe de
   la escuela. Nacieron como texto suelto en gris, sin parecerse a nada del
   resto de la casa, y eso en la página que más tiene que convencer.

   No hay foto de ningún módulo porque no existen. Así que la portada de cada
   uno se dibuja: su icono sobre su color, del mismo arcoíris que el birrete
   del logotipo. El color se decide por el sitio del módulo en su diplomado,
   así que el mismo módulo se reconoce en la portada, en el índice y en su
   propia página. Ver herramientas/temario.mjs. */

/* La portada de un diplomado en el índice de programas.
   ═══════════════════════════════════════════════════════════════════════════
   Una sola pieza por diplomado, no dieciséis tarjetas de módulo repartidas por
   la página. La decisión que se toma en el índice es entre DOS diplomados; el
   temario se ve al entrar.

   La tira de colores de arriba son los ocho módulos, en su orden y con su
   color: enseña de un vistazo que hay ocho cosas dentro sin ocupar el sitio de
   ocho tarjetas. Es decorativa —cada módulo tiene su enlace dentro—, así que
   va con `aria-hidden`: para quien navega escuchando serían ocho «imagen» sin
   nombre. */
.portada-programa{background:var(--papel);border:1px solid var(--filete);
  border-radius:var(--r);overflow:hidden;}
.pp-tira{display:flex;height:6px;}
.pp-tira i{flex:1;}
.pp-cuerpo{padding:var(--e4);display:flex;flex-direction:column;align-items:flex-start;}
.pp-cuerpo h2{margin:0 0 var(--e1);}
.pp-cuerpo h2 a{color:inherit;text-decoration:none;}
.pp-cuerpo h2 a:hover{color:var(--primary);}
.pp-cuerpo .entrada{margin-bottom:var(--e3);}

/* Las cuatro cifras. `tabular-nums` para que no bailen al comparar los dos
   diplomados uno debajo del otro. */
.pp-datos{list-style:none;margin:0 0 var(--e2);padding:0;
  display:flex;flex-wrap:wrap;gap:var(--e4);
  border-top:1px solid var(--filete);border-bottom:1px solid var(--filete);
  padding-block:var(--e2);width:100%;}
/* 3.1 · Las cifras, SIEMPRE del mismo color.
   Heredaban `--mod-color`, que es el color del primer módulo del diplomado: en
   la tarjeta de Marketing salía rojo y en la de IA naranja, mientras el mismo
   componente en la portada y en la ficha salía navy. El rojo sobre números
   neutros —«8 módulos», «48 lo han cursado»— se lee como una alerta: en un
   tablero, rojo significa que algo va mal. Aquí sólo significaba «módulos».
   El color del diplomado se queda donde sí distingue: la tira de arriba. */
.pp-datos b{display:block;font-size:var(--t-xl);line-height:1.05;
  font-variant-numeric:tabular-nums;color:var(--primary);}
.pp-datos span{font-size:var(--t-xs);color:var(--tinta-3);}

@media (max-width:640px){
  .pp-cuerpo{padding:var(--e3);}
  .pp-datos{gap:var(--e3);}
}

.rejilla-modulos{display:grid;gap:var(--e2);
  grid-template-columns:repeat(auto-fit,minmax(238px,1fr));}

.mod{display:flex;flex-direction:column;background:var(--papel);
  border:1px solid var(--filete);border-radius:var(--r);overflow:hidden;
  text-decoration:none;color:inherit;
  transition:border-color var(--rapido), transform var(--rapido), box-shadow var(--rapido);}
a.mod:hover{border-color:var(--mod-color, var(--primary));transform:translateY(-2px);
  box-shadow:0 6px 20px rgb(0 0 0 / .07);}
a.mod:focus-visible{outline:2px solid var(--mod-color, var(--primary));outline-offset:2px;}

/* La cubierta.
   ─────────────────────────────────────────────────────────────────────────
   Lo que manda es el NÚMERO del módulo, grande y en su color. El icono va de
   acompañante, más pequeño y a media tinta.

   Y ese reparto no es capricho. Los iconos de la casa son una tipografía de
   ligaduras: dentro del HTML no hay un dibujo, hay la palabra «calendar_month».
   Si esa fuente tarda o no llega —y viene de un servidor de fuera—, lo que se
   lee en su sitio es la palabra en inglés. En una pantalla interna es feo; en
   la página por la que alguien llega desde Google y decide si esta escuela es
   serio, es otra cosa.
   Con el número al frente, si el icono falla se pierde un adorno pequeño y la
   tarjeta sigue en pie. El `overflow:hidden` y el `max-width` del glifo son el
   cinturón: aunque caiga la palabra entera, no se sale de la tarjeta. */
.mod-cubierta{display:flex;align-items:center;justify-content:center;gap:10px;
  height:96px;background:color-mix(in srgb, var(--mod-color) 12%, transparent);
  border-bottom:1px solid var(--filete);position:relative;overflow:hidden;}
.mod-cifra{font-family:var(--cifra);font-size:38px;line-height:1;font-weight:700;
  color:var(--mod-color);font-variant-numeric:tabular-nums;letter-spacing:-.02em;}
.mod-cubierta .material-symbols-outlined{font-size:30px;color:var(--mod-color);
  opacity:.55;line-height:1;max-width:44px;overflow:hidden;}
.mod-numero{position:absolute;top:8px;left:10px;font-size:var(--t-xs);
  font-weight:var(--peso-fuerte);letter-spacing:.08em;text-transform:uppercase;
  color:var(--mod-color);opacity:.85;max-width:calc(100% - 20px);
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
.mod-cuerpo{padding:var(--e2);display:flex;flex-direction:column;gap:4px;flex:1;}
.mod-cuerpo h3{margin:0;font-size:var(--t-md);line-height:1.25;}
.mod-cuerpo p{margin:0;font-size:var(--t-sm);color:var(--tinta-2);}
.mod-pie{margin-top:auto;padding-top:var(--e1);font-size:var(--t-xs);
  color:var(--tinta-3);}

/* La cabecera de un diplomado y la de un módulo, con su color de fondo.
   El degradado es del color al 10 % hacia transparente: da identidad sin
   competir con el texto, que es lo que se ha venido a leer. */
.cabecera-tema{position:relative;overflow:hidden;
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--mod-color, var(--primary)) 13%, transparent) 0%,
    transparent 62%);}
/* `overflow:hidden` como en `.mod-cubierta`, y por lo mismo.
   ───────────────────────────────────────────────────────────────────────────
   Material Symbols es una tipografía de ligaduras: mientras no llega, lo que
   hay escrito ahí es la PALABRA —«smartphone», «campaign»—, no un icono. El
   `display=block` de la URL de la fuente la esconde durante los primeros
   segundos, pero a quien nunca le llegue la fuente —un proxy de oficina, un
   bloqueador, una conexión caída— le sale la palabra. Sin recorte se desborda
   del medallón y cruza la cabecera; con él, se queda dentro de sus 64 px y
   parece un adorno raro en vez de una página rota. Visto en pruebas: salía
   «sm» encima del título. */
.medallon-tema{width:64px;height:64px;min-width:64px;border-radius:var(--r);
  display:flex;align-items:center;justify-content:center;margin-bottom:var(--e2);
  overflow:hidden;
  background:color-mix(in srgb, var(--mod-color, var(--primary)) 15%, transparent);
  color:var(--mod-color, var(--primary));}
.medallon-tema .material-symbols-outlined{font-size:34px;line-height:1;
  max-width:48px;overflow:hidden;}

/* Los ocho módulos como una fila de puntos de color: se ve de un vistazo que
   el diplomado tiene ocho partes y en cuál estás. */
.tira-modulos{display:flex;gap:6px;flex-wrap:wrap;margin:var(--e1) 0 var(--e2);}
.tira-modulos a{width:26px;height:26px;border-radius:8px;display:flex;
  align-items:center;justify-content:center;font-size:var(--t-xs);
  font-weight:var(--peso-fuerte);text-decoration:none;
  background:color-mix(in srgb, var(--p) 14%, transparent);color:var(--p);
  transition:transform var(--rapido);}
.tira-modulos a:hover{transform:scale(1.12);}
.tira-modulos a[aria-current="page"]{background:var(--p);color:#fff;}

/* La prueba: cuánta gente tiene ese certificado. Va en una línea con su punto
   de color, no en una caja gris más. */
.avala{display:flex;align-items:center;gap:var(--e1);flex-wrap:wrap;
  font-size:var(--t-sm);color:var(--tinta-2);}
.avala .punto{width:8px;height:8px;border-radius:50%;
  background:var(--mod-color, var(--primary));flex:0 0 auto;}

/* Un titular más largo que «Educamos hoy, lideras mañana».
   ───────────────────────────────────────────────────────────────────────────
   `.portada-cem h1` se limita a 15ch, que para el lema de la portada de la
   plataforma —cuatro palabras— es exactamente lo que hace falta. Los titulares
   de las páginas que lee Google llevan dentro las palabras por las que se
   busca, así que son más largos, y a 15ch se partían en seis renglones de tres
   palabras: una escalera, no un titular.

   Va como clase aparte y no cambiando la regla de origen porque la portada de
   la plataforma está bien como está: lo que cambia es el caso, no la regla. */
/* Y con aire entre renglones, que es la otra mitad.
   ───────────────────────────────────────────────────────────────────────────
   A 26ch el titular de la portada del dominio salía en CINCO renglones, y a
   `line-height:1.08` esos cinco iban tan juntos que las mayúsculas de uno casi
   tocaban las bajas del anterior: una masa negra, no un titular. El 1,08 está
   bien para dos o tres renglones cortos —que es para lo que se eligió—, no
   para cinco.

   Se ensancha la medida para que quepan menos renglones y se les da aire.
   `balance` reparte las palabras para que no quede un último renglón de dos
   letras colgando. */
.portada-cem h1.titular-largo{max-width:34ch;text-wrap:balance;line-height:1.16;}

/* Y el 34ch no servía de nada sin esto, que costó una medición entender.
   ───────────────────────────────────────────────────────────────────────────
   Quien mandaba no era la medida del titular sino la de su contenedor:
   `.portada-una .portada-dicho` está capado a 62ch, y esos «ch» se cuentan con
   la letra HEREDADA —16 px—, o sea unos 490 px. El titular va a 46 px, así que
   sus 34ch son unos 780: nunca llegaba a usarlos. Medido: la caja salía de
   486 px por mucho que se ensanchara el h1.

   El 62ch del contenedor existe para que el PÁRRAFO no cruce la pantalla, y
   eso ya lo garantiza `.lema`, que se capa solo a 52ch. Así que aquí se
   ensancha la caja y cada pieza se queda con su propia medida: el titular
   ancho porque es un titular, el párrafo estrecho porque se lee.

   Con `:has()` para que sólo afecte al héroe que tiene un titular largo. El de
   la plataforma —«Educamos hoy, lideras mañana»— cabe en dos renglones y no
   necesita nada. */
.portada-una .portada-dicho:has(h1.titular-largo){max-width:76ch;}

/* La cabecera de las páginas que no cargan JavaScript.
   ═══════════════════════════════════════════════════════════════════════════
   En un teléfono, `.pub-inner nav` y `.pub-cta` se esconden y sólo vuelven con
   la clase `.abierto`, que pone el botón de hamburguesa. Las páginas de
   /programas/ y la portada del dominio NO cargan app.js —a propósito: son
   estáticas y tienen que pintar sin esperar 196 KB—, así que en un móvil se
   quedaban con la marca y nada más. Ni un enlace. Y de un buscador llega sobre
   todo gente con el teléfono en la mano.

   Ahora esas páginas traen el botón y veinte líneas sueltas que lo encienden,
   escritas dentro del propio HTML (ver `menuDelTelefono` en
   herramientas/generar-seo.mjs): no cuestan una petición, y a cambio la
   cabecera se pliega igual que en el resto del sitio.

   Lo de abajo es lo que queda cuando eso NO ocurre —JavaScript desactivado, o
   el navegador de un buscador—: el menú no se pliega, se parte en dos
   renglones. Cinco enlaces caben. De ahí el `:not(.con-menu)`: la clase la
   pone ese mismo script, así que si no corrió, esto sigue mandando. Lo que no
   puede fallar es lo que no depende de nada. */
@media(max-width:720px){
  .pub-header.estatica:not(.con-menu) .pub-inner nav{display:flex;order:3;width:100%;
    flex-wrap:wrap;gap:var(--e1) var(--e3);padding-bottom:var(--e1);}
  .pub-header.estatica:not(.con-menu) .pub-inner .pub-cta{display:flex;order:4;width:100%;
    margin-left:0;padding-bottom:var(--e1);}
  .pub-header.estatica:not(.con-menu) .pub-cta > .btn{flex:1;justify-content:center;}
}

/* Las tres rayas del botón, y la equis en que se convierten.
   ═══════════════════════════════════════════════════════════════════════════
   La cabecera de app.js usa la ligadura `menu` de la tipografía de iconos;
   ésta lleva un SVG dibujado, porque estas páginas tienen que aguantar que esa
   fuente no llegue. Se dibujan las dos igual: la de arriba y la de abajo giran
   hasta cruzarse y la del medio se va, que es lo que hace entender que el
   mismo botón cierra lo que abrió.

   `transform-box: fill-box` es imprescindible: sin él, el origen del giro es
   la esquina del lienzo del SVG y las rayas salen despedidas fuera del botón
   en vez de cruzarse en el centro. */
.pub-menu-btn svg{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;}
.pub-menu-btn .raya{transform-box:fill-box;transform-origin:center;
  transition:transform .18s ease, opacity .12s ease;}
.pub-header.abierto .pub-menu-btn .raya.sup{transform:translateY(5px) rotate(45deg);}
.pub-header.abierto .pub-menu-btn .raya.inf{transform:translateY(-5px) rotate(-45deg);}
.pub-header.abierto .pub-menu-btn .raya.med{opacity:0;}
@media(prefers-reduced-motion:reduce){
  .pub-menu-btn .raya{transition:none;}
}

/* La promoción, como franja debajo de la cabecera.
   ═══════════════════════════════════════════════════════════════════════════
   Dos intentos antes que éste. Primero, una barra pegada al borde de abajo, de
   lado a lado, que se leía como un aviso de galletas. Después, una pastilla en
   la esquina, tan discreta que nadie la veía — y que además sólo salía en
   cuatro pantallas del portal y a quien no tuviera sesión, así que el equipo
   no la vio nunca.

   Ésta está hecha a partir de las páginas de precios que el equipo enseñó
   como ejemplo (septiembre de 2026): una cinta rayada con el descuento, el
   titular a dos tonos, y una tarjeta con la cuenta atrás. Va dentro del flujo
   de la página, debajo de la cabecera, y no encima del contenido: se ve, se
   puede leer lo de abajo sin cerrarla, y Google no la cuenta como
   intersticial.

   Es un mundo propio, oscuro en los dos temas, a propósito: es lo único de la
   página que vende, y tiene que distinguirse del resto con el tema que sea.
   Por eso todos sus colores están aquí, explícitos, y no dependen de los
   tokens del tema. El rosa es sólo de la promoción; no aparece en ningún otro
   sitio de la casa. */
.promo-franja{
  --pf-rosa:#ff4f96; --pf-rosa-2:#ff82b5; --pf-rosa-3:#ffb3cf;
  --pf-fondo:#120f1d; --pf-fondo-2:#221327; --pf-hundido:rgba(8,7,14,.58);
  --pf-blanco:#ffffff; --pf-gris:#cdcfdb; --pf-gris-2:#a9abbd;
  /* z-index propio: el velo blanco que aclara el texto de la portada
     (.portada-dicho::before) se extiende hacia arriba y, al pintarse después,
     caía encima de la franja como una mancha clara en el centro. */
  position:relative;z-index:2;box-sizing:border-box;
  /* Se adapta a SU ancho y no al de la ventana: la misma franja va a lo ancho
     de la portada y dentro del diálogo de Campañas, que mide la mitad. */
  container:franja / inline-size;
  width:calc(100% - 32px);max-width:1080px;margin:var(--e2) auto 0;
  border-radius:18px;overflow:hidden;color:var(--pf-blanco);
  border:1px solid rgba(255,79,150,.45);
  background:
    radial-gradient(120% 150% at 88% 40%, rgba(255,79,150,.22), transparent 55%),
    radial-gradient(rgba(255,255,255,.055) 1px, transparent 1.2px) 0 0 / 14px 14px,
    linear-gradient(135deg, var(--pf-fondo), var(--pf-fondo-2));
  box-shadow:0 22px 44px -24px rgba(255,79,150,.55);
  font-family:inherit;
  animation:pf-entra .35s ease-out;}
@keyframes pf-entra{from{opacity:0;transform:translateY(-6px);}}
@media (prefers-reduced-motion:reduce){ .promo-franja{animation:none;} }
@media print{ .promo-franja{display:none;} }

/* La cinta: rayas en diagonal, como un precinto. */
.pf-cinta{display:flex;align-items:center;flex-wrap:wrap;gap:10px;
  min-height:38px;padding:6px 48px 6px 18px;
  background:repeating-linear-gradient(-45deg, var(--pf-rosa) 0 16px, var(--pf-rosa-2) 16px 32px);}
.pf-cinta-tag{display:inline-flex;align-items:center;gap:6px;
  font-size:12px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--pf-blanco);text-shadow:0 1px 0 rgba(120,0,50,.35);}
.pf-cinta-tag .material-symbols-outlined{font-size:17px;}
.pf-cinta-limite{background:#0b0a12;color:var(--pf-blanco);border-radius:999px;
  padding:3px 12px;font-size:11px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;}
.pf-x{position:absolute;top:5px;right:7px;width:30px;height:30px;border:0;border-radius:999px;
  display:grid;place-items:center;cursor:pointer;
  background:rgba(11,10,18,.55);color:var(--pf-blanco);}
.pf-x .material-symbols-outlined{font-size:18px;}
.pf-x:hover{background:rgba(11,10,18,.8);}
.pf-x:focus-visible{outline:2px solid var(--pf-blanco);outline-offset:2px;}

/* El cuerpo: el texto a la izquierda, el reloj a la derecha. */
.pf-cuerpo{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
  gap:var(--e3) var(--e4);padding:26px 30px 28px;}
.pf-texto{min-width:0;}
.pf-sello{display:inline-flex;align-items:center;gap:6px;border-radius:999px;
  padding:4px 12px 4px 10px;background:var(--pf-rosa);color:var(--pf-blanco);
  font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;}
.pf-sello .material-symbols-outlined{font-size:16px;}
/* El titular es la única excepción de la casa a los dos pesos: 700 y en
   mayúsculas, porque es un cartel y no un párrafo. */
.pf-titular{margin:14px 0 10px;font-weight:700;text-transform:uppercase;
  letter-spacing:-.02em;line-height:1.02;font-size:clamp(26px,4.2cqi,46px);
  text-wrap:balance;}
.pf-l1{display:block;color:var(--pf-rosa);}
.pf-l2{display:block;color:var(--pf-blanco);}
.pf-linea{margin:0;max-width:58ch;font-size:15px;line-height:1.5;color:var(--pf-gris);}
.pf-pie{flex-basis:100%;margin:2px 0 0;font-size:12px;color:var(--pf-gris-2);}

.pf-accion{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:18px;}
.pf-btn{display:inline-flex;align-items:center;gap:8px;min-height:46px;padding:0 22px;
  border:0;border-radius:999px;cursor:pointer;font:inherit;font-size:15px;font-weight:700;
  background:var(--pf-rosa);color:var(--pf-blanco);
  box-shadow:0 10px 24px -10px rgba(255,79,150,.9);
  transition:transform var(--rapido), filter var(--rapido);}
.pf-btn .material-symbols-outlined{font-size:20px;}
.pf-btn:hover{filter:brightness(1.08);transform:translateY(-1px);}
.pf-btn:focus-visible{outline:2px solid var(--pf-blanco);outline-offset:3px;}
.pf-btn:disabled{opacity:.6;cursor:progress;transform:none;}
.pf-forma{display:flex;flex-wrap:wrap;gap:10px;width:100%;max-width:480px;margin:0;}
.pf-forma input{flex:1 1 220px;min-width:0;min-height:46px;padding:0 18px;
  border-radius:999px;border:1px solid rgba(255,255,255,.28);
  background:rgba(255,255,255,.08);color:var(--pf-blanco);font:inherit;font-size:15px;}
.pf-forma input::placeholder{color:var(--pf-gris-2);}
.pf-forma input:focus-visible{outline:2px solid var(--pf-rosa-2);outline-offset:2px;}
.pf-error{flex-basis:100%;margin:0;font-size:13px;color:var(--pf-rosa-3);}

/* El reloj. Números tabulares: que no bailen al cambiar de segundo. */
.pf-reloj{position:relative;min-width:300px;border-radius:16px;padding:20px 16px 14px;
  text-align:center;border:1px solid rgba(255,79,150,.5);
  background:linear-gradient(160deg, rgba(255,79,150,.24), rgba(255,79,150,.06));}
.pf-reloj-insignia{position:absolute;top:-12px;right:14px;border-radius:999px;padding:3px 11px;
  background:var(--pf-rosa);color:var(--pf-blanco);font-size:12px;font-weight:700;}
.pf-reloj-tit{display:flex;align-items:center;justify-content:center;gap:6px;
  margin:0 0 12px;font-size:14px;color:var(--pf-gris);}
.pf-reloj-tit .material-symbols-outlined{font-size:18px;}
.pf-cajas{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:8px;}
.pf-caja{border-radius:12px;padding:12px 6px 9px;background:var(--pf-hundido);min-width:62px;}
.pf-caja b{display:block;font-size:clamp(26px,3.2vw,38px);line-height:1;font-weight:700;
  font-variant-numeric:tabular-nums;color:var(--pf-blanco);}
.pf-caja span{display:block;margin-top:6px;font-size:12px;color:var(--pf-gris);}
.pf-reloj-pie{margin:10px 0 0;font-size:12px;color:var(--pf-gris);}
.pf-reloj-pie b{color:var(--pf-blanco);}

/* El código conseguido: grande, monoespaciado, con su botón de copiar. */
.pf-hecho{display:flex;flex-direction:column;align-items:flex-start;gap:10px;
  width:100%;max-width:480px;}
.pf-hecho-tit{display:flex;align-items:center;gap:8px;margin:0;font-size:17px;font-weight:700;}
.pf-hecho-tit .material-symbols-outlined{color:#6ee7a0;font-size:24px;}
.pf-codigo-caja{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;
  padding:8px 8px 8px 16px;border-radius:12px;background:var(--pf-hundido);
  border:1px dashed rgba(255,79,150,.75);}
.pf-codigo{flex:1;min-width:0;overflow-wrap:anywhere;font-size:18px;font-weight:600;
  letter-spacing:.06em;color:var(--pf-blanco);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
.pf-copiar{display:inline-flex;align-items:center;gap:6px;flex:none;padding:8px 14px;
  border:0;border-radius:999px;cursor:pointer;font:inherit;font-size:13px;font-weight:600;
  background:rgba(255,255,255,.12);color:var(--pf-blanco);}
.pf-copiar .material-symbols-outlined{font-size:17px;}
.pf-copiar:hover{background:rgba(255,255,255,.2);}
.pf-copiar:focus-visible{outline:2px solid var(--pf-blanco);outline-offset:2px;}
.pf-enlace{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:999px;
  border:1px solid rgba(255,255,255,.35);color:var(--pf-blanco);text-decoration:none;
  font-weight:700;font-size:14px;}
.pf-enlace:hover{background:rgba(255,255,255,.08);}
.pf-enlace:focus-visible{outline:2px solid var(--pf-blanco);outline-offset:2px;}

/* En el teléfono, una columna: el titular con su botón y, debajo, el reloj
   a todo el ancho, con las cajas encogidas. */
@container franja (max-width:720px){
  .pf-cuerpo{grid-template-columns:1fr;padding:16px 16px 18px;gap:14px;}
  /* El sello repite lo que ya dicen la cinta y el titular. A lo ancho sobra
     sitio; en una columna, cada renglón empuja la portada hacia abajo. */
  .pf-sello{display:none;}
  .pf-titular{margin-top:2px;font-size:clamp(26px,8cqi,34px);}
  .pf-linea{font-size:14px;}
  .pf-accion{margin-top:14px;}
  .pf-reloj{min-width:0;padding:16px 10px 10px;}
  .pf-reloj-tit{margin-bottom:8px;font-size:13px;}
  .pf-caja{min-width:0;padding:8px 2px 6px;}
  .pf-caja b{font-size:26px;}
  .pf-caja span{margin-top:4px;font-size:11px;}
  .pf-cinta{padding-left:14px;}
}
@container franja (max-width:460px){
  /* En una sola línea: «Oferta limitada» ya lo dice el reloj. */
  .pf-cinta-limite{display:none;}
}
/* Para el navegador que no sepa de contenedores (iOS 15 y anteriores): lo
   imprescindible, que es no salirse de la pantalla del teléfono. */
@supports not (container-type:inline-size){
  @media (max-width:760px){
    .pf-cuerpo{grid-template-columns:1fr;padding:16px;}
    .pf-reloj{min-width:0;}
    .pf-caja{min-width:0;}
    .pf-titular{font-size:30px;}
  }
}

/* La vista previa de Campañas: la misma franja, quieta y sin márgenes. */
.promo-franja.es-previa{width:100%;margin:0;animation:none;}
.promo-franja.es-previa :is(button,input){pointer-events:none;}

/* Opiniones de graduados. Hoy no sale ninguna —no hay testimonios recogidos y
   no se inventan—, pero el sitio está hecho: nombre, qué hace ahora y en qué
   promoción estuvo, que es el dato comprobable contra su certificado. */
.resenas{display:grid;gap:var(--e2);grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  margin-top:var(--e3);align-items:stretch;}
.resena{height:100%;}
.resena{margin:0;display:flex;flex-direction:column;gap:var(--e2);}
.resena blockquote{margin:0;}
.resena blockquote p{margin:0;font-size:var(--t-sm);}
.resena figcaption{display:flex;align-items:center;gap:10px;margin-top:auto;}
.resena figcaption img{border-radius:var(--r-full);object-fit:cover;flex:none;}
.resena figcaption b{display:block;font-size:var(--t-sm);}
.resena figcaption span{display:block;font-size:var(--t-xs);color:var(--tinta-2);}
.resena-cuando{color:var(--tinta-3);}

/* La imagen del hero de la portada. Vacía hoy: ver ESCUELA.heroImagen en
   generar-seo.mjs. Cuando haya, va debajo de los hechos y encima de las
   cifras, a ancho completo de la columna del héroe. */
.portada-imagen{margin:var(--e3) 0 0;}
.portada-imagen img{width:100%;height:auto;border-radius:var(--r-lg,14px);
  box-shadow:var(--sombra-2,0 8px 30px rgba(13,36,64,.14));display:block;}
