
  /* Tema oscuro como variables: todos los colores estructurales salen de acá.
     Antes eran 489 literales dispersos; si uno cambiaba había que buscarlo en
     toda la hoja. Ahora un token es un token y no hay forma de que se
     desincronicen el claro y el oscuro: cada propiedad se declara una vez y
     el tema claro la sobreescribe. Los colores semánticos (score, situación
     BCRA, envguard, testbar, suspect, edited) NO van acá: son información y
     tienen que ser iguales en ambos temas para no romper el significado.

     PALETA MIEL (2026-09-01). El ámbar de este tablero ya estaba ocupado por
     señales —sit-3, dot.media, bcra-unid, --sig-warn, .stale, el marco de
     envguard— así que pintar el cromo del mismo ámbar las apagaba a todas. La
     regla que lo evita: la miel del cromo va desaturada y sobre superficies
     grandes (fondos, bordes, la barra del botón); el ámbar de señal va saturado
     y sobre marcas chicas (texto, puntitos, filetes de 3px). Son dos familias
     con el mismo matiz y distinta pureza, y el ojo las separa por eso.
     Lo dominante no se toca, que es lo que salva la lectura del ejecutivo: en
     claro la tarjeta sigue siendo blanco puro, en oscuro sigue siendo el fondo
     más profundo de la pantalla. La calidez está alrededor, no abajo del dato.
     Cada token rotó el matiz conservando la luminosidad, así que los contrastes
     medidos en los comentarios de abajo siguen valiendo. */
  :root {
    color-scheme: dark;
    /* La serif de titulares. Vive acá y no en body.pubbody —donde estaba— para
       que el dashboard también la tenga: el cajón de herramientas la usa. Sin
       binario propio ni CDN, que la CSP no admite; Georgia está en Windows y
       macOS y la cadena cubre Linux y Android. */
    --display: Georgia, "Iowan Old Style", Charter, "Bitstream Charter",
      "Liberation Serif", serif;
    /* Azul marino y no el gris casi negro de antes: es el fondo contra el que
       la miel del acento tiene que resaltar, y un neutro no la hace resaltar,
       la deja flotando. Complementario y frío, para que todo lo cálido de
       encima se lea como puesto ahí a propósito. */
    --bg:        #0b1220;
    --bg2:       #0e1728;
    --card:      #141f33;
    --border:    #1f2d47;
    --border2:   #1c2841;
    /* #2c3d5c daba 1,65:1 contra --bg2 y 1,51:1 contra --card: por debajo del
       3:1 que WCAG pide para el borde de un control (1.4.11). Mismo matiz,
       más claro: #4f6ea6 da 3,51:1 contra --bg2 y 3,23:1 contra --card. */
    --border3:   #4f6ea6;
    --row-alt:   #101b2d;
    --row-hover: #131f34;
    --row-grp:   #17253c;
    --pin-bg:    #0b1220;
    --pin-hover: #131f34;
    /* La selección se vuelve cálida en los dos temas: es la única forma de que
       el acento miel y la fila elegida sean la misma idea. Eso obligó a mover
       --tomada-*, que era el amarillo de antes, al marino: ver abajo. */
    --sel-bg:    #33280f;
    --sel-hover: #43340f;
    --sel-bdr:   #7a5c1c;
    /* "Tomada por otro ejecutivo" era amarillo pálido y ahora ese lugar lo
       ocupa la selección. Se va al marino elevado, que además dice mejor lo
       que significa: no es una alerta, es una fila que no es tuya. */
    --tomada-bg: #1d2942;
    --tomada-hv: #24324f;
    /* Miel sobre marino. #e8b25c da 9,1:1 contra --on-accent y es más naranja y
       menos puro que el #f0c419 de sit-3 y dot.media, que son las dos señales
       de las que hay que poder distinguirlo. Ayuda que el acento vive en el
       cromo (botones, solapas, links) y esas dos viven adentro de la grilla. */
    --accent:    #e8b25c;
    --accent2:   #f2c880;
    /* Texto que va encima de --accent. No se puede fijar un color solo: el
       acento oscuro es celeste claro y el claro es azul fuerte, así que el
       mismo negro que contrasta acá se hunde en el otro tema. */
    --on-accent: #241705;
    --txt:       #f0e7d8;
    --txt2:      #d5c9b4;
    --muted:     #a99c86;
    /* Mismo problema que en el tema claro: #6b7280 no llegaba a 4,5:1 contra el
       fondo oscuro. Se mide contra --card (#141f33), que es el fondo real de las
       notas al pie y las etiquetas de los ejes, no contra --bg. El marino es un
       pelo más claro que el gris que reemplazó, así que el gris cálido se
       aclaró para compensar: #98907e da 5,2:1 y sigue por debajo de --muted. */
    --muted2:    #98907e;
    --drawer-bg: #0e1728;
    --tip-bg:    #070d18;
    --modal-bg:  #0e1728;
    --skel1:     #141f33;
    --skel2:     #1e2c46;
    --pgbtn-bg:  #111c2e;
    --pgbtn-hv:  #1a2740;
    /* Botón genérico de las pantallas /dev. Más claro que --card para que el
       botón se despegue visualmente del panel que lo contiene. */
    --btn-bg:    #1b2942;
    --btn-bdr:   #2c3d5c;
  }
  /* Tema claro. El ejecutivo usa la pantalla a plena luz del dia: el fondo
     blanco reduce el cansancio visual respecto del oscuro en ambientes con
     mucha luz. Los tokens semanticos (score, sit-*, suspect, edited, testbar,
     envguard) se dejan intactos porque son senales, no decoracion. */
  :root.light {
    color-scheme: light;
    /* --bg es papel cálido y --card sigue siendo blanco puro. Esa diferencia es
       todo el tema claro: la tarjeta donde el ejecutivo lee la grilla no cambió
       ni un punto, y lo que se entibió es la hoja de atrás. Si se invirtiera
       —tarjeta miel sobre fondo blanco— el dato quedaría leyéndose sobre color,
       que es justo lo que no se quiere. */
    --bg:        #faf6ec;
    --bg2:       #ffffff;
    --card:      #ffffff;
    --border:    #ddd0b6;
    --border2:   #ebe1cd;
    /* #cdbc9c daba 1,86:1 contra el blanco de --bg2/--card: mismo problema que
       en oscuro. Mismo matiz, más oscuro: #a98c56 da 3,2:1. */
    --border3:   #a98c56;
    --row-alt:   #f9f4e8;
    --row-hover: #f3ebd9;
    --row-grp:   #ece0c6;
    --pin-bg:    #faf6ec;
    --pin-hover: #f3ebd9;
    --sel-bg:    #fcefd0;
    --sel-hover: #f8e3b0;
    --sel-bdr:   #dcae4e;
    /* Antes era el amarillo pálido #fefce8 y ahora ese lugar lo ocupa la
       selección. Queda como papel apagado, sin el borde ámbar que sí lleva la
       fila seleccionada: son parecidas de tono y se distinguen por el filete. */
    --tomada-bg: #f4eee0;
    --tomada-hv: #ebe3d0;
    /* Marrón miel oscuro y no la miel clara del tema oscuro: sobre blanco, la
       miel clara no llega ni a 2:1. #8a5a12 da 5,75:1 contra el blanco del
       botón, casi lo mismo que daba el azul #1d6cc8 que reemplaza. */
    --accent:    #8a5a12;
    --accent2:   #6d460b;
    --on-accent: #ffffff;
    --txt:       #241c11;
    --txt2:      #4a3d2c;
    --muted:     #514734;
    /* #6b7280 daba 4,46:1 sobre el fondo claro y el mínimo AA para texto normal
       es 4,5:1. Lo usan las notas al pie y las etiquetas de los ejes, que son
       justo el texto chico que más necesita el contraste. El equivalente cálido
       #6b6154 da 6,06:1 sobre blanco. */
    --muted2:    #6b6154;
    --drawer-bg: #ffffff;
    --tip-bg:    #2b2214;
    --modal-bg:  #ffffff;
    --skel1:     #efe7d6;
    --skel2:     #ddd0b6;
    --pgbtn-bg:  #f9f4e8;
    --pgbtn-hv:  #ebe1cd;
    --btn-bg:    #faf6ec;
    --btn-bdr:   #ddd0b6;
  }
  /* Señales de las pantallas de administración: verde "salió bien", ámbar
     "mirá esto", rojo "falló", naranja "quedó a medias". El significado lo
     lleva el tono, así que el tono no cambia entre temas; lo que cambia es la
     luminosidad. Eso es lo que las distingue de los tokens semánticos que se
     dejan literales: aquellos ya se leen en los dos fondos, estos no. El mismo
     #6fcf97 que contrasta 7:1 sobre el fondo oscuro da 2:1 sobre blanco, y con
     los literales que había /dev/scrapeo mostraba las cifras de "Calidad de los
     datos" en ámbar sobre blanco y /dev/revision dibujaba los diffs como barras
     negras en medio de la página.
     Van acá y no en cada hoja de /dev porque las cuatro pantallas usaban el
     mismo ámbar escrito cuatro veces. */
  :root {
    --sig-ok:    #6fcf97;
    --sig-warn:  #ffd479;
    --sig-bad:   #ff8f8f;
    --sig-alert: #ffb454;
    --ok-bg:      #16281d; --ok-bdr:      #2f7d4f; --ok-txt:      #d7ffe6;
    --aviso-bg:   #2a2113; --aviso-bdr:   #6b5320; --aviso-txt:   #ffd479;
    --peligro-bg: #2a1717; --peligro-bdr: #5a2b2b; --peligro-txt: #ff8f8f;
  }
  :root.light {
    --sig-ok:    #15803d;
    --sig-warn:  #a16207;
    --sig-bad:   #b91c1c;
    --sig-alert: #c2410c;
    --ok-bg:      #e7f6ec; --ok-bdr:      #2f7d4f; --ok-txt:      #14532d;
    --aviso-bg:   #fdf3dc; --aviso-bdr:   #a16207; --aviso-txt:   #4a3208;
    --peligro-bg: #fdecec; --peligro-bdr: #f0b4b4; --peligro-txt: #b91c1c;
  }
  /* Tamaño de letra (sidebar.ts, grupo "Configuración"): tres pasos alrededor
     del cuerpo actual de 14px. data-letra lo pone THEME_BOOT_JS, leyendo
     localStorage, antes del primer pintado — mismo mecanismo que el tema y
     que la barra colapsada. No reemplaza cada tamaño fijo del sistema: eso
     sería una campaña aparte. Escala lo que promete el enunciado de la
     opción — la grilla (table, más abajo), la barra (sidebar.ts), el
     consultor (consulta-page.ts) y las tarjetas (.card, más abajo) — y deja
     el resto en sus tamaños de siempre. */
  :root { --fs-base: 14px; }
  :root[data-letra="chica"]  { --fs-base: 13px; }
  :root[data-letra="grande"] { --fs-base: 16px; }

  /* ---- Escala de botones -------------------------------------------------
     Un solo lugar define cuánto mide un botón. Antes cada pantalla tenía la
     suya y el mismo control cambiaba de tamaño al navegar: medido el
     2026-09-16, "Ingresar" media 32px en la portada y 44px en /login, y
     "Buscar" 50px en la portada contra 42px en el consultor. Eso es lo que se
     ve como que los botones "se desfiguran" al cruzar de página.

     Los tres tamaños, y cuándo va cada uno:

       sm  32px  acciones densas dentro de una tabla o de una fila de /dev,
                 donde un botón de 40 rompe el renglón.
       md  40px  el default de todo el sitio. Es la altura de botón estándar
                 de Material 3 y la que usan los productos con los que se lo
                 compara.
       lg  48px  la acción principal de una pantalla: entrar, buscar, guardar
                 un formulario entero. Pasa el mínimo táctil de 44px de Apple
                 y el criterio 2.5.5 de WCAG.

     El buscador de la portada queda aparte, a 56px, porque es el control más
     importante del sitio y el único que tiene que leerse como invitación.

     Se usa min-height y no solo padding: con padding, el alto depende del
     tamaño de letra —que el usuario cambia desde Configuración— y la escala
     se desarma sola en "letra grande". */
  :root {
    --btn-h-sm: 32px; --btn-pad-sm: 0 12px;  --btn-fs-sm: 12.5px; --btn-r-sm: 7px;
    --btn-h-md: 40px; --btn-pad-md: 0 18px;  --btn-fs-md: 14px;   --btn-r-md: 9px;
    --btn-h-lg: 48px; --btn-pad-lg: 0 26px;  --btn-fs-lg: 15px;   --btn-r-lg: 11px;
  }
  /* Color de acento (mismo grupo): cuatro opciones sobre --accent/--accent2.
     --on-accent no se toca: en los dos temas ya es un extremo (casi negro en
     oscuro, blanco en claro) y contrasta igual de bien contra cualquiera de
     los cuatro colores medidos abajo. Azul, verde y violeta comparten el
     mismo esquema que ya usaba ámbar: en oscuro el tono es claro y --accent2
     lo aclara más para el hover; en claro el tono es oscuro y --accent2 lo
     oscurece más. Contraste contra --on-accent, medido con la fórmula WCAG:
       oscuro  #6ea8fe / #0b1220 → 7,75:1   claro #1d6cc8 / #ffffff → 5,21:1
       oscuro  #57c98a / #0b1220 → 9,03:1   claro #1f7a4c / #ffffff → 5,32:1
       oscuro  #b18cf7 / #0b1220 → 7,09:1   claro #6b3fc0 / #ffffff → 6,78:1
     Los tres pasan holgado el 4,5:1 que pide texto chico contra --on-accent
     real de cada tema (#241705 oscuro, #ffffff claro es igual al blanco de
     acá; el oscuro es más oscuro todavía que el #0b1220 medido, así que el
     contraste real es mayor, nunca menor). */
  :root[data-accent="azul"]    { --accent: #6ea8fe; --accent2: #96c2fe; }
  :root[data-accent="verde"]   { --accent: #57c98a; --accent2: #7fdba8; }
  :root[data-accent="violeta"] { --accent: #b18cf7; --accent2: #cbb0fb; }
  :root.light[data-accent="azul"]    { --accent: #1d6cc8; --accent2: #14508f; }
  :root.light[data-accent="verde"]   { --accent: #1f7a4c; --accent2: #155c38; }
  :root.light[data-accent="violeta"] { --accent: #6b3fc0; --accent2: #55329c; }
  /* Todas estas pantallas nacen cortas y crecen cuando vuelve el fetch. En
     ese momento aparece la barra de scroll y corre el ancho util ~15px: se
     mueve la pagina entera, incluso el encabezado, que no crecio. Reservar
     el canal desde el principio es lo que evita ese salto lateral. */
  html { scrollbar-gutter: stable; }
  * { box-sizing: border-box; }
  body { font-family: system-ui, sans-serif; margin: 0; background: var(--bg);
    color: var(--txt); font-size: var(--fs-base); }
  header { padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 14px; }
  header .grow { flex: 1; }
  /* El flex: none va porque los contenedores donde entra son flex y el
     navegador achica las imágenes antes que al texto cuando falta lugar. */
  .brandmark { flex: none; display: block; }
  /* El logo perdió el fondo crema, y la mitad gris del cubo queda casi contra
     el fondo del tema oscuro: medido a 26px, sus tonos dominantes dan 1,42:1
     y 1,81:1. El filtro los sube a 2,05:1 y 2,98:1 sin tocar el archivo, que
     en el tema claro está bien como está.
     No llega a 3:1 y se deja así: el logo va con alt vacío y siempre tiene al
     lado un título que dice el mismo nombre, así que es decorativo y no hay
     información que dependa de verlo. Subir más el brillo quema el azul. */
  :root:not(.light) .brandmark { filter: brightness(1.5) saturate(1.15); }
  /* 26px cabe en el alto que ya tienen los botones del encabezado (~35px),
     así que el logo no empuja la grilla ni un pixel hacia abajo. */
  header .brandmark { width: 26px; height: 26px; }
  h1 { font-size: 18px; margin: 0; }
  a { color: var(--accent); }
  main { padding: 24px; }
  /* Boton de cambio de tema. Va en el header junto al nombre del usuario.
     Ocupa el mismo alto que los botones del encabezado para no desalinear
     nada. El icono no es un emoji: es texto ASCII que funciona en todos los
     sistemas sin depender de fuentes externas. */
  /* El botón dice el modo al que cambia, así que su texto alterna entre
     "Claro" (57,2 px) y "Oscuro" (69,2 px). El HTML sale del servidor con uno y
     el JS pone el otro al arrancar, y esos 12 px de diferencia alcanzaban para
     que en un teléfono el encabezado envolviera distinto y "Salir" saltara de
     renglón a los 120 ms — justo cuando el dedo va hacia él. Se reserva el
     ancho de la palabra más larga y el texto puede cambiar sin mover nada. */
  #themeToggle { background: transparent; border: 1px solid var(--border3);
    color: var(--muted); border-radius: 8px; padding: 5px 10px;
    min-width: 70px; cursor: pointer; font-size: 12px; font-family: inherit; }
  #themeToggle:hover { color: var(--txt); border-color: var(--muted); }
  /* La lista se cuenta en ediciones, así que si el scrapeo se cae sigue
     mostrando las últimas N aunque sean viejas y nada en pantalla lo delata.
     Este cartel es el que lo delata: va arriba de todo y no se puede cerrar,
     porque el problema no se resuelve mirándolo. */
  /* .stale y .testbar y .envguard son senales de estado del sistema: sus
     colores no son decoracion sino informacion (ambar = atencion requerida,
     violeta = modo prueba). Se dejan como literales para que sean identicos
     en ambos temas y no puedan confundirse con el fondo del tema claro. */
  .stale { background: #3a2a12; border: 1px solid #7a5a1e; color: #f0d9a8;
    border-radius: 10px; padding: 10px 14px; margin-bottom: 16px;
    font-size: 14px; }
  .stale b { color: #ffd479; }
  /* .stale es la excepcion a la regla de arriba: no es chrome del sistema como
     .testbar y .envguard, es un parrafo adentro del contenido. Dejarlo marron
     oscuro sobre el fondo claro no lo hace mas visible, lo hace parecer un
     error de pintado. Sigue siendo ambar —la senal no cambia—, invertido al
     patron habitual de alerta: fondo suave, texto oscuro. */
  /* El #fdf3dc de antes daba contra el #f4f6f8 gris azulado que era el fondo
     claro; contra el papel cálido #faf6ec de la paleta miel son prácticamente
     el mismo color y el cartel desaparecía dentro de la página. Es el caso
     exacto de la regla de arriba: cuando el cromo se entibia, la señal ámbar
     tiene que subir de saturación para seguir siendo señal. */
  :root.light .stale { background: #fbe6b0; border-color: #c08a1a;
    color: #5a3d05; }
  :root.light .stale b { color: #3d2903; }
  /* Nunca hay que adivinar en qué modo se está: la banda va arriba de todo,
     fija, y no se puede cerrar. Un ensayo que se confunde con trabajo real es
     el único error que este modo no puede permitirse. */
  .testbar { position: sticky; top: 0; z-index: 50; background: #3d1f4a;
    border-bottom: 1px solid #7a4a94; color: #e9d5f5; text-align: center;
    padding: 6px 14px; font-size: 13px; font-weight: 600; }
  .testbar b { color: #f0c8ff; }
  /* Marca de entorno: aparece solo cuando el hostname no es el de producción.
     El error que viene a evitar es escribir en la pestaña equivocada, y eso no
     lo evita un cartel que hay que ir a mirar: el marco alrededor de todo el
     viewport se ve con el rabillo del ojo, que es el único momento en que
     uno todavía puede frenar. El cartel abajo a la izquierda dice cuál es el
     host, para cuando ya frenaste y querés saber dónde estás parado.
     Sombra interior y no borde: un borde empujaría el layout de la grilla, la
     sombra no ocupa lugar. El script lee location.hostname en el cliente, así
     que si una config del server se olvida, el error cae del lado barato
     (parece desarrollo) y no del caro (parece producción sin serlo).

     Usaba los tokens ámbar de .stale, y con la paleta miel eso dejó de
     funcionar: un marco cálido alrededor de una pantalla cálida es camuflaje,
     justo en la señal que más necesita entrar por el rabillo del ojo. Pasa al
     violeta de .testbar, que es la única familia de color que la paleta miel no
     toca en ningún lado. No es un tono inventado: .testbar y .envguard dicen la
     misma cosa —"esto que estás mirando no es lo real"— por dos motivos
     distintos, y ahora se parecen tanto como se parece lo que significan. */
  .envguard { position: fixed; inset: 0; z-index: 9999; pointer-events: none;
    box-shadow: inset 0 0 0 3px #7a4a94; }
  .envguard b { position: absolute; bottom: 12px; left: 12px;
    background: #3d1f4a; border: 1px solid #7a4a94; color: #e9d5f5;
    border-radius: 6px; padding: 5px 10px; font-size: 12px; font-weight: 600;
    line-height: 1.3; }
  /* El cajón de las bajas. Va en las dos pantallas que listan usuarios: las
     cuentas se crean y se desactivan, nunca se borran, así que sin plegarlas
     la lista deja de contestar de un vistazo quién entra hoy. */
  /* Las pantallas /dev se arman enteras con un solo fetch, y su alto final
     depende del dato (cuántos meses tiene el calendario, cuánta gente hay).
     Reservar el alto de antemano sería adivinar, así que se pinta una sola
     vez, ya armada: lo que nunca se vio no puede saltar. El cartel de espera
     va fijo, fuera del flujo, para que sacarlo tampoco mueva nada. */
  .devmain { visibility: hidden; }
  .devmain.ready { visibility: visible; }
  /* #6b7280 era el gris de la paleta anterior (Tailwind slate-500), frío y
     a 3,87:1 sobre --bg, por debajo del 4,5:1 que pide texto de este tamaño.
     --muted da 6,94:1 y comparte temperatura con el cromo marino. */
  .booting { position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%);
    color: var(--muted); font-size: 13px; }
  .devmain.ready + .booting { display: none; }
  .offbox { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 12px; }
  .offbox > summary { cursor: pointer; font-size: 12px; color: var(--muted);
    text-transform: uppercase; letter-spacing: .05em; }
  .cards { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; }
  .card { background: var(--card); border: 1px solid var(--border); border-radius: 10px;
    padding: 12px 16px; min-width: 140px; }
  .card .n { font-size: calc(var(--fs-base) + 10px); font-weight: 700; }
  .card .l { font-size: calc(var(--fs-base) - 2px); color: var(--muted);
    text-transform: uppercase; }
  /* Las cuatro reservaban el alto con un &nbsp;, así que durante la espera eran
     cuatro rectángulos en blanco: reservar el lugar sin decir para qué se lee
     como que las tarjetas se rompieron, sobre todo al lado de los renglones de
     abajo, que sí se animan. El gris usa el mismo latido y las mismas dos
     variables, y se va solo cuando renderCards() reemplaza el contenido. */
  .card.waiting .n, .card.waiting .l { border-radius: 3px;
    background: linear-gradient(90deg, var(--skel1), var(--skel2), var(--skel1));
    background-size: 200% 100%; animation: skel 1.1s linear infinite; }
  .card.waiting .l { width: 70%; }
  /* Ocupa el lugar de las cuatro tarjetas cuando no hay filas. Del ancho de la
     pantalla y no del de una tarjeta: es lo unico que hay para leer ahi, y
     achicarlo lo devolveria a la categoria de nota al pie que nadie mira. */
  /* El borde lateral era #2f3a4d, un literal sin token entre --border y
     --border3. El borde izquierdo ya usa var(--border3) (vía border-left), así
     que el perimetral va al mismo token para que los cuatro lados sean una sola
     decisión. Los bordes de baja especificidad no comunican información, así que
     el contraste mínimo de 3:1 no aplica aquí. */
  .emptyNote { flex: 1; background: var(--card); border: 1px solid var(--border3);
    border-left: 3px solid var(--accent); border-radius: 10px; padding: 14px 18px; }
  .emptyNote .t { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
  .emptyNote .d { font-size: 13px; color: var(--muted); margin-bottom: 10px; }
  /* El enlace al padrón de ARCA viaja al lado de "Ver todos" y tiene que
     leerse como el mismo tipo de salida. La regla button.ghost está atada a
     la etiqueta button, así que la copia para el ancla va acá y no se puede
     resolver aflojando aquel selector: hay .tab.ghost y .publink.ghost que
     heredarían el padding. */
  .emptyNote a.ghost { display: inline-block; margin-left: 8px;
    padding: 6px 12px; border-radius: 6px; font-size: 13px;
    background: transparent; color: var(--muted); text-decoration: none;
    border: 1px solid var(--border3); }
  .emptyNote a.ghost:hover { color: var(--txt); border-color: var(--accent); }
  /* Una sola fila de herramientas (2026-09-17): buscador, fecha, los seis
     filtros de a varios como pastillas chicas y, a la derecha, agrupar /
     seleccionar / exportar. Antes cada filtro era un <fieldset class="fgroup">
     con su <legend>, ocho cajas con borde propio que hacían tres renglones
     completos arriba de la tabla; esa clase se retiró entera porque ya no la
     usa nadie (el picker ya dice "Tipo: todos" solo, con su placeholder). */
  .filters { display: flex; flex-direction: column; gap: 6px;
    margin-bottom: 10px; }
  .filterbar { display: flex; gap: 8px; align-items: center;
    flex-wrap: wrap; width: 100%; }
  .filterbar #q { flex: 1 1 240px; min-width: 200px; }
  .filterbar .grow { flex: 1 1 auto; min-width: 8px; }
  /* Un filtro puesto se ve puesto: el borde y el texto en azul. Los filtros
     se guardan por usuario y sobreviven al cierre de sesion, asi que sin la
     marca la lista aparece corta al dia siguiente sin motivo aparente. */
  input.set, select.set { border-color: var(--accent); color: var(--accent); }
  /* Buscador con chips: el control de todos los filtros que admiten varios
     valores. La lista se posiciona sobre el contenido y no empuja la barra de
     filtros: si la barra creciera, la grilla saltaría en cada tecla. */
  .pick { position: relative; display: inline-block; }
  /* Un solo ancho para todos los filtros: el desplegable de bancos se estiraba
     hasta el nombre más largo y su caja quedaba del doble que las demás. El
     nombre completo sigue estando en el título y en la lista abierta. */
  .pick .pq { width: 200px; }
  /* Lo elegido queda a la vista: los filtros suman varios valores y sin verlos
     no se sabe por que la lista quedo corta.

     Todos los chips van juntos en un renglon propio debajo de la barra, y no
     dentro de cada pastilla. Adentro, cada chip empujaba a su vecino: poner un
     tipo mandaba el segmento a otro renglon y corria el resto de la fila.

     El div solo se monta (Filtros.tsx) cuando hay al menos un chip puesto: a
     diferencia de la version anterior, ya no reserva un renglon en blanco de
     forma permanente — eso era alto que la tabla perdia sin que hubiera nada
     que leer ahi (medido el 2026-09-17). */
  .chipbar { display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
    width: 100%; }
  .pchip .ptag { color: var(--muted2); font-size: 11px; text-transform: uppercase;
    letter-spacing: .04em; }
  .pchip { display: inline-flex; align-items: center; gap: 4px; font-size: 12px;
    padding: 2px 4px 2px 8px; border-radius: 999px; color: var(--accent);
    border: 1px solid var(--accent); background: var(--card); }
  .pchip .px { border: 0; background: none; cursor: pointer; padding: 0 3px;
    color: var(--accent); font-size: 14px; line-height: 1; }
  .pchip .px:hover { color: #f87171; }
  .plist { display: none; position: absolute; z-index: 40; top: 100%; left: 0;
    margin-top: 4px; min-width: 320px; max-height: 300px; overflow-y: auto;
    background: var(--card); border: 1px solid var(--border3); border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,.55); }
  .pick.open .plist { display: block; }
  .pitem { display: flex; align-items: baseline; gap: 8px; cursor: pointer;
    padding: 6px 10px; font-size: 13px; }
  .pitem.on { background: var(--row-alt); }
  /* El tilde ocupa lugar siempre, puesto o no: si apareciera solo cuando el
     valor esta elegido, el renglon se correria al elegirlo. */
  .pitem .ptick { flex: 0 0 12px; color: var(--accent); font-size: 11px; }
  .pitem.puesta .pname { color: var(--accent); }
  .pitem .pname { flex: 1; color: var(--txt); }
  .pitem .pkind { font-size: 10px; color: var(--muted2); text-transform: uppercase;
    letter-spacing: .04em; }
  .pitem .pn { color: var(--muted); min-width: 28px; text-align: right; }
  .pempty { padding: 8px 10px; font-size: 13px; color: var(--muted2); }
  .emptyRow { padding: 28px 10px; font-size: 13px; color: var(--muted2);
    text-align: center; }
  /* El interruptor del header usa el mismo control que los de la barra de
     filtros: sin esta segunda mitad del selector quedaba sin estilo, pegado
     al texto y con otro tamano que el resto del encabezado. */
  .filters .toggle, header .toggle { display: flex; align-items: center;
    gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; }
  /* El display:flex de arriba le gana al atributo hidden del navegador: sin
     esta linea, el interruptor se le mostraba igual a quien no puede usarlo. */
  header .toggle[hidden] { display: none; }
  /* La pestaña Personal esconde la grilla con el atributo hidden, y el
     atributo pierde contra cualquier display: flex del CSS del bloque: con
     Personal activa seguían a la vista las pestañas, las tarjetas y los
     filtros (visto el 2026-09-11). El !important es lo que vuelve al atributo
     lo que debería ser: la última palabra. */
  [hidden] { display: none !important; }
  input, select, textarea { background: var(--card); color: var(--txt);
    border: 1px solid var(--border3); border-radius: 8px; padding: 8px 10px;
    font-size: 14px; font-family: inherit; }
  input[type=search] { min-width: 260px; }
  /* La tabla arranca vacía y se llena recién cuando vuelve /api/cross. Sin
     este alto reservado, el pie de la lista se dibuja arriba del pliegue y se
     va al fondo de golpe cuando llegan las filas. */
  /* El scroll de costado es de la tabla, no de la página. Hasta el 2026-09-13
     lo era de la página, y eso rompía dos cosas que Romen reportó juntas:
     al correrse para leer Zona se iban también el cartel de datos viejos, las
     tarjetas de resumen y los filtros —quedaba media pantalla en blanco—, y la
     columna pegada (Sociedad) se anclaba al borde de la VENTANA, así que con el
     cajón lateral desplegado quedaba debajo de él.

     Confinar el eje X obliga a confinar también el Y: no existe la combinación
     overflow-x scrolleable con overflow-y visible —el motor computa el visible
     como auto—, y clip tampoco sirve: se probó el 2026-09-13 y el encabezado
     sticky dejaba de pegarse (quedaba en -2390 al bajar la página), porque una
     caja de recorte también le sirve de ancla al sticky. Así que la tabla pasa a
     ser la cápsula de abajo, con su propio scroll en los dos ejes, que es
     justamente la forma que pidió Romen.

     El alto se acota contra la VENTANA y no contra el contenido: la zona de
     arriba mide 610px, así que restarla dejaría la tabla en 250px en una
     pantalla de 860. Acotando contra la ventana, esos 610px se van con el scroll
     de la página como siempre y la tabla queda ocupándola entera. Se descuenta
     --topbar (la banda de modo prueba) para que no la tape. */
  .tablewrap { min-height: 60vh; position: relative;
    overflow: auto; max-height: calc(100vh - var(--topbar, 0px) - 12px);
    overscroll-behavior-x: contain; }
  /* Los renglones grises de la primera espera. Absolutos y encima de la tabla:
     adentro del tbody interferirían con el <colgroup> de layout:fixed. Con
     layout:fixed el encabezado ya no salta al llegar los datos —el ancho lo
     fijan las columnas declaradas, no el contenido—, pero se mantienen absolutos
     de todos modos para no sumar nodos al tbody mientras carga. */
  .skel { position: absolute; inset: 0; pointer-events: none; }
  /* En #card-list, en cambio, van en el flujo: ese contenedor arranca en cero y
     una capa absoluta adentro de algo sin alto no se ve. Los renglones grises
     pasan a reservar el alto que la lista de fichas nunca reservó. */
  .skel-flow { position: static; padding-top: 8px; }
  .skel i { display: block; height: 11px; margin: 0 10px 23px; border-radius: 3px;
    background: linear-gradient(90deg, var(--skel1), var(--skel2), var(--skel1));
    background-size: 200% 100%; animation: skel 1.1s linear infinite; }
  .skel i:nth-child(even) { opacity: .7; }
  @keyframes skel { to { background-position: -200% 0; } }
  @media (prefers-reduced-motion: reduce) {
    .skel i { animation: none; }
  }
  table { width: 100%; border-collapse: collapse;
    font-size: calc(var(--fs-base) - 1px); }
  th, td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border2); }
  /* La lista se recorre de arriba hacia abajo llamando: cada pixel de alto de
     fila es una fila menos que se ve sin scrollear. La celda de Gestion no
     puede envolver y la de situacion tampoco, que parte "5 Irrecuperable". */
  td.gestion, [class^="sit-"] { white-space: nowrap; }
  /* Textos largos dentro de una celda de ancho fijo (table-layout:fixed): el
     span recorta con ellipsis y el title del span expone el valor completo.
     display:block es necesario porque un inline no respeta max-width del padre. */
  .cell-trunc { display: block; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; }
  /* La linea del encabezado va como sombra y no como border-bottom: con
     border-collapse el borde le pertenece a la tabla y no viaja con la celda
     pegada, asi que al scrollear las filas se metian debajo del encabezado sin
     nada que las separara. El z-index va por encima del .reportBox (20). */
  /* top: 0 y no var(--topbar) desde el 2026-09-13: el encabezado ya no se pega
     contra la ventana sino contra .tablewrap, que es el contenedor que scrollea,
     y ahí el cero es su propio borde de arriba. La banda de modo prueba se
     descuenta del max-height de la cápsula, así que no hay nada que esquivar
     acá. Antes --topbar era el alto de lo que estuviera pegado más arriba: con
     la banda en pantalla, un top 0 metía el encabezado debajo de ella. */
  /* La linea de familia se suma a la sombra en vez de ser un border-top por lo
     mismo que dice el parrafo de arriba: con border-collapse el borde le
     pertenece a la tabla y no viaja con la celda pegada. Sin familia,
     var(--fam) cae en transparent y el encabezado queda como estaba. */
  th { color: var(--muted); font-weight: 600; position: sticky;
    top: 0;
    background: var(--bg); z-index: 25; border-bottom: none;
    box-shadow: inset 0 3px 0 var(--fam, transparent),
      inset 0 -1px 0 var(--border2); }
  /* Las 19 columnas miden 1998px y no entran ni en una pantalla de 1900: al
     correrse para leer Zona o Nacionalidad, el ejecutivo pierde de vista de
     qué sociedad es el renglón que está mirando. Score y Sociedad se quedan
     pegadas a la izquierda. Cuál es la última pegada lo decide el JS —depende
     de qué columnas tenga escondidas cada uno—, así que el corrimiento de la
     segunda viaja en --pin1 y no está escrito acá.

     El scroll horizontal es el de .tablewrap (desde el 2026-09-13; antes era el
     de la página), así que left se mide contra el borde del contenedor. Eso es
     justamente lo que hace que la columna pegada ya no quede debajo del cajón
     lateral cuando está desplegado: el contenedor empieza donde termina el
     cajón. El fondo tiene que ser opaco y repetir el color de cada estado
     (normal, hover, grupo) porque debajo pasan las columnas que se corren. */
  th.pin, td.pin { position: sticky; background: var(--pin-bg); }
  td.pin { z-index: 15; }
  th.pin { z-index: 30; }
  /* --pin0 es cero salvo en modo seleccion, donde adelante de todo aparece la
     columna de casillas y hay que correr a las pegadas por su ancho. */
  .pin-0 { left: var(--pin0, 0px); }
  .pin-1 { left: calc(var(--pin0, 0px) + var(--pin1, 0px)); }
  /* La costura marca donde termina lo que no se mueve. */
  .pin-last { min-width: 170px;
    box-shadow: 1px 0 0 var(--border), 4px 0 8px -2px #00000073; }
  th.pin-last { box-shadow: inset 0 3px 0 var(--fam, transparent),
    inset 0 -1px 0 var(--border2), 1px 0 0 var(--border),
    4px 0 8px -2px #00000073; }
  /* Cada uno se acomoda la grilla arrastrando el encabezado. El cursor es la
     única señal de que se puede: un ícono de agarre en las 19 celdas ensuciaba
     la única fila que se lee de un vistazo. */
  #head th { cursor: grab; user-select: none; }
  #head th.dragging { cursor: grabbing; opacity: .45; }
  /* La linea marca donde va a caer la columna. Va como box-shadow y no como
     border porque un borde ensancharia la celda y correria toda la fila un px. */
  #head th.drop-before { box-shadow: inset 2px 0 0 var(--accent); }
  #head th.drop-after { box-shadow: inset -2px 0 0 var(--accent); }
  tr:hover td { background: var(--row-hover); }
  tr:hover td.pin { background: var(--pin-hover); }

  /* Familias de columnas (COLUMN_GROUPS). Hasta ahora la agrupacion existia
     solo en el panel del dev: en la grilla eran catorce columnas del mismo
     color y habia que leer el encabezado para saber si lo que se mira lo
     publico el Boletin o lo contesto el BCRA. El color lo dice sin leer.

     Dos intensidades, y la diferencia es deliberada. La linea del encabezado va
     a color pleno porque es la que agrupa. La banda del cuerpo va casi
     transparente porque ahi abajo compiten senales que si deciden a quien se
     llama —la situacion crediticia, la fila tomada por otro, el hover— y una
     banda fuerte les ganaria la atencion sin merecerla.

     Los tonos son cinco matices separados y desaturados, no los colores de
     estado: verde y rojo estan tomados por la situacion crediticia y repetirlos
     aca haria leer riesgo donde solo hay una familia de columnas.

     El par de cada familia se escribe junto —el pleno y el aguado— y no con
     color-mix, que no se usa en ninguna otra regla de esta hoja. El selector va
     sin elemento adelante porque los mismos cinco pares los usan tres cosas: el
     <col> de la banda, el <th> de la linea y la leyenda de arriba de la tabla.
     Que el color de "La persona" sea uno solo en los tres lados es justamente
     lo que hace que la leyenda sirva. */
     La opacidad de la banda cambia con el tema y los colores no. Sobre el marino
     el mismo 7% que en el papel se pierde: el fondo claro tiene de donde
     restar y el oscuro no. Va como variable y no como cinco reglas repetidas
     por tema, que es la unica diferencia entre los dos. */
  :root       { --fam-a: .13; }
  :root.light { --fam-a: .07; }
  .fam-prioridad { --fam: #d9a441; --fam-bg: rgba(217, 164,  65, var(--fam-a)); }
  .fam-sociedad  { --fam: #5b8bd0; --fam-bg: rgba( 91, 139, 208, var(--fam-a)); }
  .fam-socio     { --fam: #9b7fd4; --fam-bg: rgba(155, 127, 212, var(--fam-a)); }
  .fam-banco     { --fam: #4aa8a0; --fam-bg: rgba( 74, 168, 160, var(--fam-a)); }
  .fam-gestion   { --fam: #d4798a; --fam-bg: rgba(212, 121, 138, var(--fam-a)); }
  /* La banda se declara sobre el <col>, que el navegador pinta por debajo del
     fondo de la celda. Por eso el hover, la fila marcada y la tomada por otro
     la tapan sin que haya que repetir ninguna de esas reglas aca, y por eso
     tampoco se ve en las columnas pegadas: su fondo es opaco a proposito,
     porque debajo pasan las que se corren. En esas dos la familia la dice la
     linea del encabezado, que si se ve. */
  col[class*="fam-"] { background: var(--fam-bg); }

  /* Leyenda de los colores. Cada item lleva su muestra como pseudoelemento y no
     como un <span> mas: el texto de la leyenda ya dice todo lo que hay que
     leer, y un elemento vacio de adorno seria una parada mas para quien navega
     con lector de pantalla. */
  .famkey { display: flex; flex-wrap: wrap; gap: 6px 18px; list-style: none;
    margin: 0 0 8px; padding: 0; font-size: 11.5px; color: var(--muted2);
    letter-spacing: .01em; }
  .famkey li { display: inline-flex; align-items: center; gap: 6px; }
  .famkey li::before { content: ""; width: 14px; height: 3px; border-radius: 2px;
    background: var(--fam); }

  /* Modo seleccion. La casilla va en columna propia y pegada a la izquierda.
     Ancho fijo porque no muestra un dato. */
  th.selcol, td.selcol { position: sticky; left: 0; background: var(--pin-bg);
    width: 34px; min-width: 34px; padding: 4px 8px; text-align: center; }
  td.selcol { z-index: 15; }
  th.selcol { z-index: 30; cursor: default; }
  tr:hover td.selcol { background: var(--pin-hover); }
  td.selcol input { cursor: pointer; }
  /* .55 y no .35: el mismo criterio que button:disabled (mas abajo) es bajar
     el fondo, no el texto; para una casilla nativa que no tiene fondo propio
     que bajar, .35 dejaba la marca del check sin leerse. */
  td.selcol input:disabled { cursor: not-allowed; opacity: .55; }
  /* La fila marcada se pinta entera, incluidas las pegadas. */
  tr.picked td, tr.picked td.pin, tr.picked td.selcol { background: var(--sel-bg); }
  tr.picked:hover td, tr.picked:hover td.pin,
  tr.picked:hover td.selcol { background: var(--sel-hover); }

  /* Otro ejecutivo esta contactando este prospecto ahora. Ambar y no rojo: rojo
     ya significa deuda irrecuperable en la columna de situacion. Los tokens de
     ambar se dejan como literales iguales en ambos temas: son senales, no
     decoracion, y tienen que seguir siendo distinguibles en el tema claro. */
  tr.tomada td, tr.tomada td.pin, tr.tomada td.selcol { background: var(--tomada-bg); }
  tr.tomada:hover td, tr.tomada:hover td.pin,
  tr.tomada:hover td.selcol { background: var(--tomada-hv); }

  /* La barra de lote aparece pegada arriba de la tabla y solo con algo marcado. */
  .bulkbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
    margin: 0 0 10px; padding: 8px 12px; border-radius: 10px;
    background: var(--sel-bg); border: 1px solid var(--sel-bdr); font-size: 13px;
    color: var(--muted); }
  /* El atributo hidden del HTML vale display:none con especificidad de
     atributo, y la regla de arriba es una clase: sin esto la barra vacia se
     dibujaba igual y quedaba una franja azul arriba de la tabla. */
  .bulkbar[hidden] { display: none; }
  .bulkbar b { color: var(--txt); }
  /* Los badges son semanticos: el verde es "cliente", el gris es "no cliente",
     el violeta es "prospecto", etc. Sus colores se mantienen identicos en ambos
     temas para que el significado no cambie con la luz del monitor. */
  .badge { display: inline-block; padding: 2px 8px; border-radius: 999px;
    font-size: 11px; font-weight: 600; }
  .badge.yes { background: #1f6f3f; color: #d7ffe6; }
  .badge.no { background: #333; color: #bbb; }
  /* Concurso o quiebra: el único rojo de la fila, porque es la única celda
     que puede decir que la empresa ya no es un prospecto. */
  .badge.concurso { background: #7f1d1d; color: #ffe1e1; }
  .badge.prospect { background: #7a4dff; color: #fff; }
  .badge.mixta { background: #7a4dff; color: #fff; }
  .badge.captacion { background: #1f6f3f; color: #d7ffe6; }
  .badge.greenfield { background: #2b6cb0; color: #dbeeff; }
  .badge.profundizacion { background: #8a5a00; color: #ffe9c2; }
  .badge.group { background: #185f63; color: #ccf5f7; cursor: help; }
  /* Gris a proposito: "empresa" no es una alarma como el rojo del ⚠ ni una
     categoria comercial como el violeta de prospecto. Es de que tipo es el
     contacto, y compite por atencion con el nombre que tiene al lado. */
  .badge.org { background: #4a4a52; color: #e4e4ea; cursor: help;
    margin-left: 5px; padding: 1px 6px; font-size: 10px; font-weight: 500; }
  .score { display: inline-flex; align-items: center; gap: 7px;
    font-weight: 700; font-variant-numeric: tabular-nums; }
  .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
  .dot.alta { background: #57d68d; box-shadow: 0 0 6px #57d68d80; }
  .dot.media { background: #f0c419; }
  /* #4b5563 era Tailwind slate-600: frío y a 2,18:1 sobre --card, por debajo
     del 3:1 de WCAG 1.4.11 para componentes UI (el punto comunica el nivel de
     score). Va --muted2 y no un gris propio: da 5,20:1, y sobre todo cambia solo
     al pasar a claro, que es lo que un literal no hace. Los otros dos puntos sí
     son literales porque son señal saturada; este es la ausencia de señal, que
     es lo que ese token significa en el resto de la pantalla. */
  .dot.baja { background: var(--muted2); }
  .sit-1 { color: #57d68d; } .sit-2 { color: #b7d657; }
  .sit-3 { color: #f0c419; } .sit-4 { color: #f0932b; }
  .sit-5, .sit-6 { color: #eb4d4b; }
  /* Los mismos cinco pasos, oscurecidos para el fondo claro. El matiz no se
     toca —verde sigue verde, ambar ambar, rojo rojo— porque lo que el ojo
     reconoce es el color, no el tono exacto. Lo que cambia es el contraste:
     medido sobre el fondo #f4f6f8, los tonos pensados para el tema oscuro
     daban entre 1,5:1 y 2,2:1, y la situacion crediticia es el dato por el que
     se levanta el telefono. Estos pasan 4,5:1, que es el minimo legible. */
  :root.light .sit-1 { color: #157347; }
  :root.light .sit-2 { color: #5c6d10; }
  :root.light .sit-3 { color: #8a6100; }
  :root.light .sit-4 { color: #a4470a; }
  :root.light .sit-5, :root.light .sit-6 { color: #b02a28; }
  /* Los dos estados sin situación crediticia. No entran a la escala verde-rojo
     a propósito: esa escala es un juicio sobre cómo paga alguien, y acá no hay
     con qué juzgar. "No figura" es un dato del BCRA y se lee como texto normal;
     "Sin identificar" es una falla nuestra de identificación y va en el ámbar
     de aviso, porque nada de esa fila se puede dar por verificado. El punteado
     avisa que hay una explicación al pasar el mouse. */
  .bcra-absent { color: var(--txt2); border-bottom: 1px dotted var(--muted2);
    cursor: help; }
  .bcra-unid { color: #f0932b; border-bottom: 1px dotted currentColor;
    cursor: help; }
  :root.light .bcra-unid { color: #a4470a; }
  .muted { color: var(--muted2); }
  .sub { display: block; color: var(--muted2); font-size: 11px; }
  /* La localidad deducida se subraya punteada: no la dijo el aviso, la infiro
     la geometria de la calle y acierta 94 de cada 100 veces. */
  .sub.deducida { border-bottom: 1px dotted var(--muted2); cursor: help; }
  /* El capital sin desglose que lo confirme se marca igual y por lo mismo. */
  td.estimado { border-bottom: 1px dotted var(--muted2); cursor: help; }
  /* El cargo se lee de un vistazo o no sirve. */
  .rol { text-transform: capitalize; color: var(--txt2); font-weight: 600; }
  /* La nacionalidad importa cuando no es la de todos: 1302 de los 18798 socios
     que la declaran. Antes aca decia "362 de 3983", que era cierto cuando la
     base tenia la mitad; el numero envejecio y la proporcion no.
     Azul claro: identico en ambos temas porque es una senal de atencion. */
  td.extranjero { color: #93c5fd; }
  /* Mismo azul, corrido a donde se lee: 1,66:1 sobre el fondo claro no era una
     senal de atencion, era un texto que desaparecia. */
  :root.light td.extranjero { color: #1d4ed8; }
  .barrio { font-size: 11px; color: var(--muted); }
  /* "Cargar más" va abajo de la tabla y no arriba: el ejecutivo llega acá
     después de recorrer la lista, no antes. */
  .pager { display: flex; align-items: center; justify-content: center;
    gap: 12px; margin: 10px 0 24px; }
  .pgbtn { background: var(--pgbtn-bg); color: var(--txt2); border: 1px solid var(--border3);
    border-radius: 6px; padding: 6px 14px; cursor: pointer; font-size: 13px; }
  .pgbtn:hover:not(:disabled) { background: var(--pgbtn-hv); border-color: var(--muted2); }
  .pgbtn:disabled { opacity: .4; cursor: default; }
  .pgpos { color: var(--muted); font-size: 13px; }
  /* "Ver de a" queda al costado y en gris: es un ajuste, no la accion. */
  .pgstep { display: inline-flex; align-items: center; gap: 6px;
    color: var(--muted); font-size: 13px; }
  .pgstep select { background: var(--pgbtn-bg); color: var(--txt); border: 1px solid var(--border3);
    border-radius: 6px; padding: 4px 6px; font-size: 13px; }
  .tip { position: relative; cursor: help; border-bottom: 1px dotted var(--border); }
  /* El tipbox tiene fondo oscuro fijo en ambos temas: es texto blanco sobre
     negro, que da el maximo contraste y no confunde con el contenido de abajo. */
  /* position: fixed y no absolute desde el 2026-09-13, cuando la tabla pasó a
     tener su propio scroll (ver .tablewrap). Un absolute es hijo del contenedor
     y el contenedor ahora recorta: el desglose de la primera fila se cortaba
     contra el borde de arriba —medido: el globo empezaba en 599 y la cápsula en
     610—. Un fixed se mide contra la ventana y el overflow de los ancestros no
     lo recorta, siempre que ninguno tenga transform (ninguno lo tiene).
     A cambio, las coordenadas no las puede poner el CSS: las calcula
     posicionarTip() en Tabla.tsx, en el hover. */
  .tipbox { display: none; position: fixed; z-index: 60;
    background: var(--tip-bg); border: 1px solid var(--border3);
    border-radius: 8px; padding: 8px 10px; white-space: nowrap;
    box-shadow: 0 6px 20px rgba(0,0,0,.45); font-size: 12px; color: #e6e6e6; }
  .tipbox div { padding: 1px 0; }
  /* focus-within además de hover: el globo era inalcanzable sin mouse, y el
     desglose (de dónde sale el score, el monto banco por banco) no está escrito
     en ningún otro lado. El .tip lleva tabindex desde el 2026-09-14. */
  .tip:hover .tipbox, .tip:focus-within .tipbox { display: block; }
  /* El punto de prioridad sobre papel blanco: medido el 2026-09-14, el verde
     (#57d68d) daba 1,84:1 y el ámbar (#f0c419) 1,66:1 contra --card, cuando un
     componente gráfico necesita 3:1 (WCAG 1.4.11). En el tema oscuro pasan; es
     el claro el que nunca recibió su override, a diferencia de .sit-*. */
  :root.light .dot.alta { background: #3d9663; }
  :root.light .dot.media { background: #a88912; }
  /* Los botones de acomodar columnas del cajón. Angostos y sin fondo: la fila
     es una casilla con su nombre, y el orden es la acción secundaria. */
  #colTools { border: 0; padding: 0; margin: 0; }
  /* Botón de icono: CUADRADO, con el ancho atado al alto.
     Medía 26x22 —casi cuadrado— hasta que button.small pasó a tener
     min-height 32px con la escala de botones del 2026-09-16: con el ancho
     todavía en 26 quedó de 26x32, más alto que ancho. Un botón que solo
     contiene una flecha se lee mal apenas deja de ser cuadrado, y eso es lo
     que Romen vio como "el botón de la columna quedó rectangular".
     El padding se va: con las dos medidas atadas al mismo token, el contenido
     se centra con flex y no hay nada que descuadrar. */
  .colmov { min-width: var(--btn-h-sm); min-height: var(--btn-h-sm);
    padding: 0; line-height: 1; display: inline-flex; align-items: center;
    justify-content: center; }
  /* El deshabilitado baja el fondo, no el texto: con opacity .35 sobre el
     botón entero la flecha dejaba de verse, que es el mismo criterio ya
     escrito para los botones del cajón. */
  .colmov[disabled] { cursor: default; color: var(--muted2);
    border-color: var(--border2); background: transparent; }
  .tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
    border-radius: 3px; }
  .note { color: var(--muted); font-size: 12px; margin: 8px 0 16px; }
  .login { max-width: 320px; margin: 12vh auto; padding: 28px;
    background: var(--card); border: 1px solid var(--border); border-radius: 12px; }
  .login h1 { margin: 0 0 16px; }
  /* Centrado y grande: en el ingreso el logo es lo único que confirma que
     llegaste al lugar correcto, y no compite con nada por el espacio. */
  .login .brandmark { width: 64px; height: 64px; margin: 0 auto 16px; }
  .login input { width: 100%; margin-bottom: 12px; }
  /* Toda ruta que no existe termina redirigida acá, y el sitio es público: el
     que erra una letra del link que le pasaron por WhatsApp aterriza en un
     formulario que no puede completar, sin nada que lo devuelva a la página
     que estaba buscando. Es el mismo callejón que ya se había resuelto en la
     pantalla de "Mensaje recibido". */
  .login .back { display: block; margin-top: 16px; font-size: 13px;
    text-align: center; color: var(--muted); }
  button { background: var(--accent); color: var(--on-accent); border: 0;
    border-radius: var(--btn-r-md); min-height: var(--btn-h-md);
    padding: var(--btn-pad-md); font-weight: 600; cursor: pointer;
    font-size: var(--btn-fs-md); line-height: 1; }
  button.ghost { background: transparent; color: var(--muted);
    border: 1px solid var(--border3); }
  button.small { min-height: var(--btn-h-sm); padding: var(--btn-pad-sm);
    font-size: var(--btn-fs-sm); border-radius: var(--btn-r-sm); }
  /* Para el botón que cambia de rótulo según su estado —"Seguir"/"Siguiendo",
     "Apagar"/"Confirmar"/"Activar"—. Reserva el ancho del texto más largo, que
     es lo único que evita que la fila se mueva al tocarlo.
     10ch alcanza para los rótulos que hay hoy; si aparece uno más largo, se
     sube acá y no con un estilo suelto en la pantalla de turno. */
  button.alterna { min-width: calc(10ch + 36px); justify-content: center;
    display: inline-flex; align-items: center; }
  /* Apagado pero legible, mismo criterio que .ing-btn[disabled]: baja el fondo,
     no el texto. El único disabled que existía para estos botones venía del
     bloque de /dev (opacity .45 sobre el beige), y con 45% de opacidad el texto
     dejaba de leerse: era la mitad de lo que se veía "roto" en el cajón. */
  button:disabled { cursor: not-allowed;
    background: color-mix(in srgb, var(--accent) 30%, var(--bg2));
    color: var(--muted2); }
  button.ghost:disabled { background: transparent; color: var(--muted2);
    border-color: var(--border2); }
  /* #b02a28 y no el #eb4d4b del resto de los rojos: acá el par que hay que
     medir es el texto blanco contra el fondo del botón, y eso no cambia con el
     tema. Con #eb4d4b da 3,7:1, por debajo del 4,5:1 que pide un botón de este
     tamaño; con #b02a28 da 6,6:1 en oscuro y en claro por igual. */
  button.danger { background: #b02a28; color: #fff; }
  .err { color: #eb4d4b; font-size: 13px; margin-bottom: 12px; }
  .ok { color: #57d68d; font-size: 13px; }

  /* El rojo de los mensajes de error se aclara demasiado sobre fondo blanco:
     #eb4d4b da 3,7:1 contra el 4,5:1 que pide texto de este tamaño. Mismo
     reemplazo que ya usa :root.light .sit-5. En oscuro el original está bien. */
  :root.light .err, :root.light .tmErr, :root.light .pdErr { color: #b02a28; }

  /* Avisos apilados abajo a la derecha. Van fijos y fuera del flujo. */
  #toasts { position: fixed; right: 18px; bottom: 18px; z-index: 100;
    display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
    pointer-events: none; }
  .toast { pointer-events: auto; cursor: pointer; max-width: 380px;
    padding: 10px 14px; border-radius: 10px; font-size: 13px; line-height: 1.4;
    background: var(--card); border: 1px solid var(--border); color: var(--txt);
    box-shadow: 0 8px 24px -6px #000000a6;
    opacity: 0; transform: translateY(8px); transition: opacity .18s ease,
    transform .18s ease; }
  .toast.on { opacity: 1; transform: none; }
  .toast.t-error { border-color: #6b2b2a; border-left: 3px solid #eb4d4b; }
  .toast.t-ok { border-color: #2f7d4f; border-left: 3px solid #57d68d; }
  @media (prefers-reduced-motion: reduce) {
    .toast { transition: none; transform: none; }
  }

  /* Diálogo de entrada de texto propio, reemplaza prompt() nativo en las
     pantallas /dev/*. El nativo no permite mostrar el error junto al campo ni
     manejar el foco: con un valor vacío no hay forma de avisar sin cerrar la
     ventana. Mismo token de colores y mismo patrón que #tabModal y #bulkModal. */
  #pdModal { width: min(400px, 92vw); padding: 0; border: 1px solid var(--border);
    border-radius: 12px; background: var(--modal-bg); color: var(--txt); }
  #pdModal::backdrop { background: rgba(0,0,0,.55); }
  .pdHead { padding: 16px 18px 10px; border-bottom: 1px solid var(--border2); }
  .pdHead h3 { margin: 0; font-size: 15px; }
  .pdBody { padding: 14px 18px; display: flex; flex-direction: column; gap: 8px;
    font-size: 13px; }
  .pdBody label { color: var(--muted); }
  .pdBody input { width: 100%; }
  .pdErr { color: #eb4d4b; font-size: 12px; min-height: 16px; }
  .pdFoot { display: flex; gap: 10px; justify-content: flex-end;
    padding: 12px 18px; border-top: 1px solid var(--border2); }

  /* Diálogo de confirmación propio, reemplaza confirm() nativo. A diferencia
     de #pdModal no necesita campo de entrada, pero agrega el concepto de
     gravedad variable: con peligro:true el botón de confirmar usa .danger y
     un texto descriptivo de la consecuencia ("Borrar", "Generar clave"), en
     lugar del genérico "Aceptar" que no dice nada sobre lo que va a pasar.
     El contraste del botón se arregla arriba, en button.danger, y no acá: el
     problema es del par blanco/rojo y no del tema. */
  #cdModal { width: min(380px, 92vw); padding: 0; border: 1px solid var(--border);
    border-radius: 12px; background: var(--modal-bg); color: var(--txt); }
  #cdModal::backdrop { background: rgba(0,0,0,.55); }
  .cdHead { padding: 16px 18px 10px; border-bottom: 1px solid var(--border2); }
  .cdHead h3 { margin: 0; font-size: 15px; }
  .cdBody { padding: 14px 18px; font-size: 13px; }
  .cdBody p { margin: 0; line-height: 1.5; }
  .cdFoot { display: flex; gap: 10px; justify-content: flex-end;
    padding: 12px 18px; border-top: 1px solid var(--border2); }

  /* Caja de herramientas del ejecutivo. Ya no es una columna que empuja la
     grilla: es un panel flotante que sale pegado al borde derecho de la barra
     lateral (sidebar.ts), a --barra-w del borde de la ventana (220px salvo
     que el usuario haya movido el tirador) o a 56px si la barra está
     colapsada. Su propio ancho es --herr-w (340px salvo que el usuario haya
     movido su propio tirador, ver .herr-resize).
     El truco del translateX: la distancia tiene que ser al menos "left" +
     ancho propio para que el borde derecho quede en x<=0 sin importar el
     estado de la barra; con los dos anchos variables ya no alcanza una
     constante y se calcula con --barra-w (que es siempre >= al left real,
     56px colapsada incluido) + --herr-w. Calcularlo con un porcentaje
     (translateX(-100%)) dependía de "left: 0" para funcionar: con la barra de
     por medio, un -100% relativo al propio ancho del panel ya no alcanza. */
  #sidebar { position: fixed; top: 0; left: var(--barra-w, 220px); bottom: 0;
    width: var(--herr-w, 340px);
    background: var(--drawer-bg); border-right: 1px solid var(--border);
    /* 42: ENTRE el scrim (41) y la barra lateral (45, sidebar.ts).
       Estaba en 40, o sea por encima de la barra, y el efecto era el que Romen
       reportó el 2026-09-15: el panel se veía pasar por arriba de la barra al
       abrirse y al cerrarse, tapando los iconos de navegación durante todo el
       viaje. Como el panel se esconde desplazándose hacia la izquierda —tiene
       que cruzar los 220px de la barra para salir de pantalla—, ese cruce era
       inevitable mientras estuviera por encima.
       Por debajo de la barra, el mismo movimiento se lee como lo que
       conceptualmente es: el cajón SALE DE ATRÁS de la barra y vuelve a
       guardarse ahí. No hay que tocar la animación ni el translateX.
       Por encima del scrim sí, porque el cajón no debe oscurecerse con él. */
    z-index: 42;
    /* Sombra hacia la derecha: con el scrim oscureciendo el contenido, el
       canto del panel se confundía con el fondo. La sombra lo despega sin
       agregar un borde más. */
    box-shadow: 6px 0 24px rgba(0,0,0,.28);
    transform: translateX(calc(-1 * (var(--barra-w, 220px) + var(--herr-w, 340px))));
    transition: left .15s ease, transform .18s ease, width .15s ease,
      visibility 0s .15s;
    overflow-y: auto; padding: 18px;
    /* Cerrado, "visibility: hidden" saca los controles del orden de
       tabulación además de esconderlos con el translateX; el "inert" que pone
       dashboard.js hace lo mismo para el lector de pantalla. El retraso de
       .15s (mismo tiempo que la transición del transform) deja que la
       animación de cierre termine antes de desaparecer del todo. */
  visibility: hidden; }
  html.barra-cerrada #sidebar { left: 56px; }
  #sidebar.open { transform: translateX(0); visibility: visible;
    transition: left .15s ease, transform .18s ease, width .15s ease; }
  .herr-resize { position: absolute; top: 0; right: 0; bottom: 0; width: 6px;
    cursor: col-resize; background: transparent; touch-action: none; }
  .herr-resize:hover, .herr-resize:focus-visible { background: var(--accent); opacity: .5; }
  .herr-resize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  /* Mismo criterio que .barra :focus-visible (sidebar.ts): el flotante tiene
     su propio anillo de foco porque vive fuera de .barra. */
  #sidebar :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* El encabezado del cajón, con el gesto editorial de la portada: el nombre de
     la herramienta en la serif de titulares y un filete de acento abajo. Antes
     era un h2 de 15px en sans, que al lado de su propio contenido de 13px no se
     leía como encabezado de nada. El filete arranca en el canto izquierdo y no
     cruza el ancho entero: la columna mide 340px y una línea de punta a punta
     la parte en dos. */
  .sidehead { display: flex; align-items: baseline; justify-content: space-between;
    gap: 10px; margin: 0 0 14px; padding-bottom: 10px;
    border-bottom: 1px solid var(--border2); position: relative; }
  .sidehead::after { content: ""; position: absolute; left: 0; bottom: -1px;
    width: 44px; height: 2px; background: var(--accent); }
  #sidebar h2 { font-family: var(--display); font-size: 19px; font-weight: 600;
    margin: 0; letter-spacing: -.01em; line-height: 1.2; }
  /* El cuerpo de cada herramienta.
     **Esto es el arreglo del 2026-09-13 y conviene no volver a perderlo.** Las
     tres reglas de abajo vivían bajo .tool, que era el <details> del
     dashboard viejo. La migración a React del 2026-09-12 retiró dashboardPage
     y con él ese <details>: hoy no lo dibuja nadie —quedó nombrado solo en un
     comentario—, así que las reglas dejaron de aplicar sin que nada fallara.
     Medido en el navegador antes de tocar: el cuerpo quedaba en display:block
     sin gap ni padding, con font-size heredado de 14px, y el textarea
     medía **179px dentro de los 303 disponibles**, con min-height 0 (50px de
     alto). Afectaba a las cuatro herramientas a la vez, porque las cuatro
     dibujan .body. Ahora cuelgan de #sidebar, que es quien las dibuja. */
  #sidebar .body { display: flex; flex-direction: column; gap: 10px;
    font-size: 13px; }
  /* Todos los label del cajón, no solo los hijos directos del cuerpo: los 28 de
     "Mis columnas" cuelgan de #colTools, y con el selector acotado quedaban con
     el checkbox pegado al texto ("[x]Tipo"). Es lo que hacía .tool label. */
  #sidebar label { display: flex; align-items: flex-start; gap: 8px; }
  /* El checkbox no se estira con el texto de dos renglones ("Actividad
     declarada (apagada para el equipo)"): sin esto, flex lo deforma. */
  #sidebar label input[type="checkbox"] { flex: none; margin-top: 2px; }
  #sidebar textarea { width: 100%; box-sizing: border-box; min-height: 64px;
    resize: vertical; }
  /* El botón de la acción primaria del cajón no se estira al ancho de la
     columna (queda un bloque de 300px que grita), pero tampoco se encoge al
     largo de su texto: se alinea a la izquierda con un ancho mínimo cómodo. */
  #sidebar .body > button { align-self: flex-start; min-width: 128px; }
  /* Los botones de "Mis columnas" (PanelColumnas) sí van apilados y anchos:
     son una lista de acciones, no una acción primaria. */
  #colTools { display: flex; flex-direction: column; gap: 8px; }
  /* Estado vacío con forma. Antes era una línea gris suelta adentro de una
     caja con borde —y en el buzón no había ni eso: la lista vacía dibujaba un
     rectángulo hueco sin una palabra—. El punteado dice "acá va a haber algo"
     en vez de parecer un error de carga. */
  .emptybox { border: 1px dashed var(--border3); border-radius: 8px;
    padding: 16px 14px; text-align: center; color: var(--muted2);
    font-size: 12px; line-height: 1.5; margin: 0; }
  /* La pizarra (comun/Pizarra.tsx). Va en STYLE y no en el CSS de una pantalla
     porque la dibujan dos: el cajón de la barra lateral y "Mi día", y las dos
     cargan /css/app.css. Que el estilo esté en un solo lado es lo que evita que
     vuelvan a separarse — era la mitad del problema del 2026-09-13. */
  /* La línea que explica para qué es la herramienta. Antes era un
     .meta muted —11px, el mismo tratamiento que una fecha al pie de un
     ítem—, así que la única frase que explicaba el cajón se leía como un
     pie de foto. */
  .pz-nota { margin: 0; font-size: 12px; line-height: 1.5; color: var(--txt2);
    border-left: 2px solid var(--border3); padding-left: 10px; }
  .pz { display: flex; flex-direction: column; gap: 10px; }
  .pz-texto { width: 100%; box-sizing: border-box; min-height: 70px;
    resize: vertical; background: var(--bg2); border: 1px solid var(--border);
    border-radius: 8px; color: var(--txt); padding: 8px 10px; font: inherit; }
  /* Adentro del cajón, "#sidebar textarea" le gana por especificidad (un id
     contra una clase) y la dejaba en 64px: la misma pizarra medía distinto en
     cada pantalla por seis píxeles. El selector con el id empata y devuelve la
     decisión a la pieza. */
  #sidebar .pz-texto { min-height: 70px; }
  .pz > button { align-self: flex-start; min-width: 128px; }
  .pz-lista { list-style: none; margin: 0; padding: 0; }
  .pz-lista li { padding: 10px 0; border-top: 1px solid var(--border2); }
  .pz-lista li:first-child { border-top: 0; }
  /* white-space: pre-wrap porque una nota es texto que la persona escribió con
     sus propios renglones: sin esto, una lista de tres ítems se lee como un
     párrafo corrido. */
  .pz-body { margin: 0 0 6px; font-size: 13px; white-space: pre-wrap;
    overflow-wrap: anywhere; }
  /* La fecha y el Borrar van juntos, no uno en cada punta. Con
     justify-content: space-between quedaban pegados a los bordes: bien en la
     columna de 300px del cajón, y separados por 400px de vacío en Mi día, donde
     la tarjeta mide 500 — la misma nota se leía como dos cosas distintas. */
  .pz-meta { display: flex; align-items: center; gap: 12px;
    font-size: 11px; color: var(--muted2); }
  .pz-err { color: var(--sig-bad); font-size: 12px; }
  /* panellink es la acción primaria del encabezado, así que va en el acento y
     no en un azul fijo. El literal #1d4ed8 se justificaba cuando el acento
     también era azul y la diferencia entre los dos era invisible; con la paleta
     miel quedaban dos botones primarios de colores distintos al lado del otro
     —"Ayuda" azul y "Salir" miel— sin que nada explicara por qué. */
  .panellink { display: inline-flex; align-items: center; justify-content: center;
    min-height: var(--btn-h-md); padding: var(--btn-pad-md);
    border-radius: var(--btn-r-md); background: var(--accent);
    color: var(--on-accent); text-decoration: none; font-weight: 600;
    font-size: var(--btn-fs-md); line-height: 1; }
  .panellink:hover { background: var(--accent2); }
  /* 41 y no 30: por encima de todo lo que vive adentro de la tabla, incluido
     el bloque sticky de arriba del Panel comercial (.pc-top, 40 en
     panel-react-page.ts), que el 2026-09-17 pasó a dibujarse por encima del
     scrim y del cajón (Romen: bug2). Las capas de la izquierda y el #drawer
     suben con él; el orden completo está en sidebar.ts. */
  .scrim { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 41;
    display: none; }
  .scrim.open { display: block; }
  .iconbtn { background: transparent; border: 1px solid var(--border3); color: var(--txt);
    border-radius: var(--btn-r-md); min-height: var(--btn-h-md);
    min-width: var(--btn-h-md); padding: 0 11px; cursor: pointer; font-size: 15px;
    display: inline-flex; align-items: center; justify-content: center; }
  .who { font-size: 12px; color: var(--muted); }
  .listbox { max-height: 220px; overflow-y: auto; border: 1px solid var(--border);
    border-radius: 8px; padding: 8px; display: flex; flex-direction: column;
    gap: 8px; }
  .item { border-bottom: 1px solid var(--border2); padding-bottom: 6px; font-size: 12px; }
  .item:last-child { border-bottom: 0; }
  .item .meta { color: var(--muted2); font-size: 11px; }
  .daterange .sep { color: var(--muted2); }
  .daterange input { width: 130px; }
  .dateWindow { display: flex; align-items: center; gap: 6px; margin: 6px 0; }
  .dateWindow input { flex: 1; min-width: 0; }
  .winDays { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 2px; }
  .tfaSecret { font-family: ui-monospace, monospace; font-size: 13px;
    letter-spacing: 1px; background: var(--bg2); border: 1px solid var(--border);
    border-radius: 6px; padding: 8px; margin: 8px 0; user-select: all;
    word-break: break-all; }
  .winDays button.on { border-color: var(--accent); color: var(--accent); }
  .reply { margin: 6px 0 0 12px; padding-left: 8px;
    border-left: 2px solid var(--border); }
  /* #4b7bec era el azul de la paleta de series anterior. Con el sistema marino
     actual ese azul frío no tiene token ni rol semántico acá: el filete de
     "respuesta del dev" necesita distinguirse del gris del hilo normal (--border)
     y de las señales de estado (ámbar, verde, rojo). --c1 (#6ea8fe) es el azul de
     series decorativas y cumple ese rol sin inventar un color nuevo. */
  .reply.dev { border-left-color: var(--c1); }
  .replyBox { margin-top: 6px; }
  .replyBox textarea { width: 100%; min-height: 52px; margin-bottom: 4px; }
  .sugActions { display: flex; gap: 4px; margin-top: 4px; }
  .thread.hidden { display: none; }
  .item.closed { opacity: .55; }
  /* .flag es ambar y semantico: igual en ambos temas. */
  .flag { margin-left: 6px; color: #f0932b; }
  /* Vista agrupada. */
  tr.grp { cursor: pointer; background: var(--row-alt); }
  tr.grp:hover { background: var(--row-grp); }
  tr.grp td { border-top: 1px solid var(--border); }
  tr.grp b { margin-right: 8px; }
  tr.grp .grpline { position: sticky; left: 10px; display: inline-block; }
  .caret { display: inline-block; width: 14px; color: var(--muted2); }
  /* El triángulo del grupo es un <button> desde el 2026-09-14 (antes era un
     span y en modo agrupado no se podía abrir ningún grupo sin mouse). Se le
     saca el aspecto de botón pero NO el foco: el outline es lo único que le
     dice a quien navega con teclado dónde está parado. */
  button.caret { background: none; border: 0; padding: 0; margin: 0;
    font: inherit; line-height: inherit; cursor: pointer; }
  button.caret:focus-visible { outline: 2px solid var(--accent);
    outline-offset: 2px; border-radius: 3px; }
  .grpmeta { color: var(--muted); font-size: 12px; }
  tr.child { display: none; }
  tr.child.show { display: table-row; }
  tr.child td:first-child { padding-left: 26px; }
  /* Globo de sin leer. Vacio no ocupa lugar, asi que el layout no salta. */
  .badge-dot { display: none; margin-left: 6px; padding: 1px 6px;
    background: #eb4d4b; color: #fff; border-radius: 10px;
    font-size: 11px; font-weight: 600; line-height: 16px; vertical-align: top; }
  .badge-dot.on { display: inline-block; }
  .reportBox { position: absolute; z-index: 20; margin-top: 4px;
    display: flex; flex-direction: column; gap: 4px; width: 260px;
    background: var(--modal-bg); border: 1px solid var(--border3); border-radius: 8px;
    padding: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.45); }
  td:has(.reportBox) { position: relative; }

  /* Panel derecho: el 360 y la gestion de un prospecto. */
  #drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 380px;
    background: var(--drawer-bg); border-left: 1px solid var(--border); z-index: 43;
    transform: translateX(100%); transition: transform .18s ease;
    overflow-y: auto; padding: 18px; }
  #drawer.open { transform: translateX(0); }
  #drawer h2 { font-size: 15px; margin: 0 0 4px; }
  #drawer .field { display: flex; flex-direction: column; gap: 5px;
    margin-bottom: 12px; font-size: 13px; }
  #drawer select, #drawer textarea, #drawer input { width: 100%; }
  #drawer h3 { font-size: 13px; margin: 18px 0 6px; color: var(--muted); }
  /* El botón de cierre saca la fila de la lista de trabajo, así que no puede
     quedar pegado al de guardar: el ejecutivo escribe las notas y sube el
     pulgar a lo primero que encuentra. Va en su propio renglón y no al lado,
     porque un botón es inline-block y un margen de arriba entre dos que
     comparten línea no los separa, solo empuja la línea entera hacia abajo. */
  #fbCloseStart { display: block; margin-top: 16px; }
  #fbCloseForm { border-top: 1px solid var(--border); margin-top: 12px;
    padding-top: 12px; }
  #fbEvents .event { border-bottom: 1px solid var(--border2); padding-bottom: 6px;
    font-size: 12px; }
  #fbEvents .event:last-child { border-bottom: 0; }
  #coNotices .event { border-bottom: 1px solid var(--border2); padding-bottom: 6px;
    font-size: 12px; }
  #coNotices .event:last-child { border-bottom: 0; }
  #coNotices .hechos { color: var(--text); margin-top: 2px; }
  .gestion .owner { display: block; color: var(--muted2); font-size: 11px; }
  /* Tiempo relativo del ultimo movimiento. Va debajo del select, en bloque
     separado para no romper el nowrap del contenedor: el texto se ve solo
     cuando hay gestion cargada, asi que el renglón existe o no existe.
     En .mob-actions (flex) necesita flex-basis:100% para ocupar fila propia. */
  .gestion-when { display: block; color: var(--muted2); font-size: 11px;
    white-space: normal; margin-top: 2px; flex-basis: 100%; }
  /* Ambar semantico: identico en ambos temas. */
  .gestion .owner.contactando { color: #d9a441; }
  #fbOwner.contactando { color: #d9a441; }

  .aviso { border-bottom: 1px solid var(--border2); padding: 8px 0; font-size: 12px; }
  .aviso:last-child { border-bottom: 0; }
  .aviso.nuevo { border-left: 2px solid #d9a441; padding-left: 8px; }
  .aviso .when { color: var(--muted2); font-size: 11px; margin-top: 2px; }

  /* Ventana de confirmacion del lote. Es un dialog nativo. */
  #bulkModal { width: min(560px, 92vw); max-height: 82vh; padding: 0;
    border: 1px solid var(--border); border-radius: 12px; background: var(--modal-bg);
    color: var(--txt); }
  #bulkModal::backdrop { background: rgba(0,0,0,.55); }
  .bmHead { padding: 16px 18px 10px; border-bottom: 1px solid var(--border2); }
  .bmHead h3 { margin: 0 0 4px; font-size: 15px; }
  .bmBody { padding: 12px 18px; overflow-y: auto; max-height: 46vh; }
  .bmFoot { display: flex; gap: 10px; justify-content: flex-end;
    align-items: center; padding: 12px 18px; border-top: 1px solid var(--border2); }
  .bmFoot .bmCount { margin-right: auto; color: var(--muted); font-size: 13px; }
  .bmItem { display: flex; gap: 8px; align-items: flex-start; padding: 5px 0;
    border-bottom: 1px solid var(--border2); font-size: 13px; }
  .bmItem:last-child { border-bottom: 0; }
  .bmItem input { margin-top: 3px; }
  .bmItem.off { opacity: .45; }
  .bmItem .who { color: var(--txt); }
  .bmItem .where { display: block; color: var(--muted2); font-size: 11px; }
  #bulkModal .field { display: flex; flex-direction: column; gap: 5px;
    margin-bottom: 10px; font-size: 13px; }
  #bulkModal select, #bulkModal input[type="text"] { width: 100%; }

  /* Barra de pestanas. */
  /* El alto de una fila de pestañas, reservado desde el primer pintado. La
     barra llega vacía del servidor —las pestañas guardadas salen de /api/me— y
     al llenarse pasaba de 2 px (solo el borde) a 36, corriendo 32 px hacia
     abajo las tarjetas, los filtros y la lista entera. Es el mismo motivo por
     el que CARD_SKELETON dibuja cuatro tarjetas en blanco y por el que el
     nombre del usuario viene pintado; la barra de pestañas nació después y
     quedó afuera. Medido en teléfono: era el único salto de la carga, 0,186 de
     CLS, y no tenía nada que ver con los datos —ocurre a los 116 ms, mucho
     antes de que conteste la API—. Es min-height y no height porque con
     muchas pestañas guardadas la barra envuelve a dos filas y tiene que poder
     crecer; lo que se reserva es la primera. */
  #tabbar { display: flex; gap: 2px; flex-wrap: wrap; align-items: flex-end;
    min-height: 36px;
    border-bottom: 2px solid var(--border); margin-bottom: 18px; }
  /* El peso va siempre en 600, activa o no. Mismo motivo que .barra-radio:
     la negrita mide más ancho, así que ponerla solo en la activa hacía que
     TODA la fila de pestañas se corriera al cambiar de una a otra. La activa
     se distingue igual por color y por el filete de abajo. */
  .tab { display: inline-flex; align-items: center; gap: 5px;
    padding: 9px 16px; font-size: 14px; font-weight: 600; cursor: pointer;
    border: 0; background: none; color: var(--muted);
    border-bottom: 2px solid transparent; margin-bottom: -2px;
    font-family: inherit; white-space: nowrap; }
  .tab:hover { color: var(--txt); }
  .tab.on { color: var(--accent); border-bottom-color: var(--accent); }
  /* El botón "+ Nueva" se lee como acción, no como pestaña de navegación:
     queda más tenue y no recibe borde inferior al estar activo. */
  .tab.ghost { color: var(--muted2); font-size: 13px; margin-left: 10px;
    border-bottom-color: transparent !important; }
  .tab.ghost:hover { color: var(--muted); }
  .tabwrap { position: relative; display: inline-flex; }
  .tab.hasdel { padding-right: 26px; }
  .tab-del { position: absolute; right: 2px; top: 50%;
    transform: translateY(-50%); background: none; border: 0; width: 20px;
    height: 20px; padding: 0; font-size: 14px; line-height: 1; cursor: pointer;
    color: var(--muted2); font-family: inherit; border-radius: 50%; }
  .tab-del:hover { color: #eb4d4b; background: var(--border2); }

  /* Ventana para nombrar una pestana nueva. */
  #tabModal { width: min(430px, 92vw); padding: 0; border: 1px solid var(--border);
    border-radius: 12px; background: var(--modal-bg); color: var(--txt); }
  #tabModal::backdrop { background: rgba(0,0,0,.55); }
  #tabModal .tmHead { padding: 16px 18px 10px; border-bottom: 1px solid var(--border2); }
  #tabModal .tmHead h3 { margin: 0; font-size: 15px; }
  #tabModal .tmBody { padding: 14px 18px; display: flex; flex-direction: column;
    gap: 8px; font-size: 13px; }
  #tabModal .tmBody input { width: 100%; }
  /* El color va sin el #tabModal adelante a propósito: con el ID, esta regla
     pesa (1,1,0) y le gana al override :root.light .tmErr de más arriba, que
     queda escrito y sin efecto. El resto sí puede quedar dentro del modal. */
  .tmErr { color: #eb4d4b; }
  #tabModal .tmErr { font-size: 12px; min-height: 16px; }
  #tabModal .tmFoot { display: flex; gap: 10px; justify-content: flex-end;
    padding: 12px 18px; border-top: 1px solid var(--border2); }

  .tmKind { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 2px; }
  .tmKind label { display: grid; grid-template-columns: auto 1fr;
    gap: 2px 8px; align-items: baseline; cursor: pointer; padding: 8px 10px;
    border: 1px solid var(--border); border-radius: 8px; }
  .tmKind label:hover { border-color: var(--muted2); }
  .tmKind input { grid-row: span 2; margin: 0; }
  .tmKind b { font-size: 13px; font-weight: 600; }
  .tmKind span { font-size: 11px; color: var(--muted); line-height: 1.4; }
  .tmKind label:has(input:checked) { border-color: var(--accent); background: var(--sel-bg); }

  /* Menu de carpetas. */
  .listmenu { position: absolute; z-index: 60; min-width: 230px;
    max-height: 320px; overflow-y: auto; background: var(--card);
    border: 1px solid var(--border3); border-radius: 8px; padding: 4px;
    box-shadow: 0 8px 24px rgba(0,0,0,.5); font-size: 12px; }
  .listmenu .lmhead { padding: 5px 8px 6px; color: var(--muted2); font-size: 11px;
    text-transform: uppercase; letter-spacing: .04em; }
  .listmenu label { display: flex; align-items: center; gap: 8px;
    padding: 6px 8px; border-radius: 6px; cursor: pointer; }
  .listmenu label:hover { background: var(--row-alt); }
  .listmenu input { margin: 0; }
  .listmenu .lmnew { width: 100%; text-align: left; margin-top: 2px;
    border-top: 1px solid var(--border2); border-radius: 0 0 6px 6px; }
  .listmenu .lmempty { padding: 6px 8px; color: var(--muted2); line-height: 1.4; }
  .gestion .lmbtn.on { color: var(--accent); border-color: var(--sel-bdr); }

  /* Modo edicion: los colores de editable/reportable son informativos.
     Se dejan como literales identicos en ambos temas para que sea imposible
     editar sin darse cuenta, incluso en el tema claro. */
  body.editing td.editable { outline: 1px dashed #6ea8fe; cursor: text;
    background: #16203355; }
  body.editing td.editable:hover { background: #1b2436; }
  body.editing td.reportable { outline: 1px dashed #f0932b;
    background: #2a1d0e55; text-align: center; }
  body.editing th.editable { color: #6ea8fe; }
  body.editing th.reportable { color: #f0932b; }
  input.why { display: block; margin-top: 4px; width: 100%; min-width: 140px;
    padding: 3px 6px; font-size: 11px; color: var(--muted); }
  .edited { color: #f0c419; margin-left: 4px; }
  .suspect { color: #eb4d4b; margin-left: 4px; cursor: help; }
  /* Ámbar y no rojo: el aviso no está mal, está desactualizado, y el rojo de
     .suspect ya significa "esta fila probablemente no debería existir". El
     cursor de ayuda es lo que dice que el texto de la corrección está en el
     title, que es donde único entra. */
  .correccionCell { cursor: help; }
  .correccionCell a, .correccionCell > span:first-child { color: #f0932b; }
  .gestion select { padding: 3px 6px; font-size: 12px; }
  /* Editor flotante sobre la celda. Con table-layout:fixed el ancho de la celda
     no puede crecer: si el input viviera adentro y el texto midiera más que la
     columna, el contenido quedaría recortado a ciegas. Al posicionarlo absoluto
     el editor puede ser más ancho que su celda sin mover ninguna columna.

     El ancho lo decide el contenido y no un número fijo: con un min-width de
     230px el nombre más largo de la grilla ("NB DISTRIBUIDORA MAYORISTA
     S.R.L.", 33 caracteres) seguía sin entrar y se editaba a ciegas, que es
     exactamente lo que este editor viene a arreglar. El tope de 420px es para
     que un valor absurdo no tape media pantalla, y el de 85vw para que en un
     celular el recuadro no se salga por la derecha.

     La celda recibe position:relative y overflow:visible desde el JS que abre el
     editor. Acá había además una regla td:has(.cel-edit) que hacía lo mismo: era
     redundante y le costaba al navegador el 40% del reflow de la tabla, porque
     :has() obliga a vigilar las 30.115 celdas que puede llegar a tener la grilla
     abierta del todo. */
  /* El editor flotante tiene fondo de modal para contrastar con el contenido
     de la celda que esta editando. */
  .cel-edit { position: absolute; z-index: 35; top: 0; left: 0;
    width: max-content; min-width: 100%; max-width: min(420px, 85vw);
    background: var(--modal-bg); border: 1px solid var(--accent); border-radius: 6px;
    padding: 4px 6px; box-shadow: 0 4px 16px rgba(0,0,0,.5); }
  .cel-edit input { display: block; width: 100%; padding: 3px 6px;
    font-size: 13px; background: transparent; border: none; color: inherit; }
  /* Sin outline: none a secas: quien navega con teclado necesita ver dónde
     está parado adentro del editor flotante. offset negativo para que el
     anillo quede sobre el propio input y no se corte contra el borde del
     .cel-edit que lo envuelve. */
  .cel-edit input:focus-visible { outline: 2px solid var(--accent);
    outline-offset: -1px; }

  /* Movimiento reducido, para los dos paneles que se deslizan.
     Va en STYLE y no en HELP_STYLE, que es donde había quedado: esa hoja se
     sirve SOLO adentro de /ayuda, donde ni el panel derecho ni el cajón de
     herramientas existen, así que la regla no se aplicaba a nada.
     Se apaga la transición y no el transform: el transform es lo que sostiene
     el estado abierto/cerrado, y apagarlo dejaría los dos paneles visibles
     todo el tiempo. Lo que se saca es la animación del traslado, no el
     traslado. */
  @media (prefers-reduced-motion: reduce) {
    #drawer, #sidebar, #sidebar.open { transition: none; }
  }

  /* Portada pública. Prefijo propio porque es la única pantalla que se sirve
     sin sesión y comparte hoja de estilos con el dashboard: sin el prefijo,
     tocar el margen de un título acá movería algo adentro. Se dibuja entera en
     el server, sin una línea de JavaScript, porque no tiene nada que pedirle a
     la base después de cargar. */
  /* Sin padding abajo: ese hueco de 64px dejaba la página con aire después del
     pie oscuro, que es parte de lo que lo hacía ver como una tarjeta suelta.
     El respiro que hacía falta ahora lo pone el propio pie con su padding. */
  .pub { max-width: 880px; margin: 0 auto; padding: 0 20px; }
  .pubtop { display: flex; align-items: center; gap: 14px; padding: 18px 0;
    border-bottom: 1px solid var(--border2); }
  .pubtop .grow { flex: 1; }
  /* Esta hoja se comparte con el dashboard, y ahí header, main y footer eran
     selectores de etiqueta sin riesgo porque en las páginas públicas esos tres
     elementos no existían: eran divs. Ponerles la etiqueta que un lector de
     pantalla necesita hizo que la portada heredara reglas ajenas, y no de
     forma visible: el padding de 24px de main le comió 48px de ancho a la
     franja de pestañas —que en 360px ya mostraba 2 de 5— y el display:none de
     header .grow mató el separador que hace caer los controles en su propio
     renglón, así que "Ingresar" quedaba colgando solo abajo.
     Se neutraliza acá y no allá: el dashboard es el dueño de esas reglas.
     (Y el comentario va sin comillas invertidas a propósito: esta hoja vive
     dentro de una plantilla de template string y una sola las cierra.) */
  body.pubbody main { padding: 0; }
  body.pubbody .pubtop .grow { display: block; }
  /* Salto al contenido. Fuera de la pantalla hasta que el foco lo alcanza, y
     entonces se posa arriba a la izquierda por encima de la barra lateral
     (z-index 45, sidebar.ts). No se esconde con display:none ni con visibility: eso lo
     sacaría del orden de tabulación y no habría forma de llegar a él, que es
     justamente para lo que existe. */
  .pubskip { position: fixed; left: 12px; top: -60px; z-index: 46;
    padding: 10px 16px; border-radius: 8px; font-size: 14px; font-weight: 600;
    background: var(--accent); color: var(--on-accent);
    text-decoration: none; transition: top .15s; }
  .pubskip:focus { top: 12px; }
  /* Wordmark: FIRMC, el logo en lugar de la O, y RE.
     El flex va en el span de adentro y no en el <a>: con el <a> como flex, su
     único hijo es el span, el logo queda inline ahí adentro y el renglón se
     parte en tres —FIRMC, el cubo, RE— apenas el contenedor le pone un límite
     de ancho. El span es el que tiene que alinear las tres piezas.

     La altura va en em y no en px para que el logo siga al font-size: el mismo
     wordmark se usa en la barra de arriba y en el pie, con dos tamaños.
     1,05em y no 1em porque el archivo trae aire alrededor del símbolo: medido
     contra las mayúsculas, con 1em el cubo se ve más chico que la M de al
     lado. El margen negativo compensa lo mismo por los costados, donde ese
     aire abriría un hueco que parte la palabra en dos. */
  .pubmark { font-weight: 700; font-size: 20px; letter-spacing: -.005em;
    display: inline-block; color: var(--txt); text-decoration: none; }
  .pubmark > span { display: inline-flex; align-items: center;
    white-space: nowrap; }
  .pubmark .brandmark { width: 1.05em; height: 1.05em; margin: 0 -.06em; }
  .pubmark:hover { color: var(--accent); }
  .publink { display: inline-flex; align-items: center; justify-content: center;
    min-height: var(--btn-h-md); padding: var(--btn-pad-md);
    border-radius: var(--btn-r-md);
    background: var(--accent); color: var(--on-accent); text-decoration: none;
    font-weight: 600; font-size: var(--btn-fs-md); line-height: 1; }
  .publink:hover { background: var(--accent2); }
  /* La variante hueca: misma caja, sin el relleno de miel. El padding baja 1px
     por lado para compensar el borde que .publink no tiene, así los tres
     controles de la barra miden lo mismo.
     En la portada la lleva "Ingresar" y no "Escribinos", que es al revés de
     como estaba. Dos botones llenos compiten, así que uno de los dos tiene que
     ceder, y el que cede es el que la visita no puede usar: a la portada llega
     gente de la calle, que no tiene con qué entrar al tablero. Lo único que
     puede hacer es escribirnos. En /sugerencias, donde "Escribinos" no existe,
     "Ingresar" sigue siendo el botón lleno. */
  .publink.ghost { background: transparent; border: 1px solid var(--border3);
    color: var(--muted); padding: 7px 13px; }
  .publink.ghost:hover { background: transparent; color: var(--txt);
    border-color: var(--muted); }
  /* Secundario a propósito: al lado tiene "Ingresar", que es la única acción
     que esta página quiere que alguien haga. Dos botones llenos de miel
     compiten y el visitante no sabe cuál es el importante. */
  #pubTheme { background: transparent; border: 1px solid var(--border3);
    color: var(--muted); border-radius: 8px; padding: 7px 12px; cursor: pointer;
    font-size: 13px; font-weight: 600; font-family: inherit; }
  #pubTheme:hover { color: var(--txt); border-color: var(--muted); }
  /* Qué palabra se ve lo decide el tema, no JavaScript: el script del <head>
     solo mueve la clase del <html> y acá se lee. Así el botón nunca puede
     quedar diciendo lo contrario de lo que hace, que es lo que pasa cuando el
     texto lo sincroniza un handler. Dice el modo al que se cambia, no el
     actual, igual que el del dashboard. */
  /* Las dos palabras ocupan la misma celda y la que no toca queda invisible
     pero sigue midiendo: el botón mide siempre lo de la más larga y no cambia
     de ancho con cada click. Con display:none (hasta el 2026-09-16) medía 105
     o 118px según el tema y corría el "Escribinos" de al lado 13px por click:
     el mismo salto que ya se había arreglado en el botón del encabezado del
     dashboard (#themeToggle) y que no viajó cuando el control bajó al pie. */
  #pubTheme { display: inline-grid; }
  #pubTheme > span { grid-area: 1 / 1; }
  #pubTheme .th-b, :root.light #pubTheme .th-a { visibility: hidden; }
  :root.light #pubTheme .th-b { visibility: visible; }
  .pubnums { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; }
  .pubnum { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
    padding: 16px 20px; flex: 1; min-width: 150px; }
  .pubnum .n { font-size: 28px; font-weight: 700; letter-spacing: -.02em; }
  .pubnum .l { font-size: 12px; color: var(--muted); margin-top: 2px; }
  /* gap chico: el eje pasó de ocho meses a las veintidós ediciones hábiles de
     los últimos treinta días, y con 10px de separación los 840px útiles dejaban
     28px por barra, menos de lo que mide "14/08". */
  .pubbars { display: flex; align-items: flex-end; gap: 5px; height: 190px;
    padding-top: 10px; }
  /* min-width:0 porque el default de un item flex es min-width:auto, y como la
     etiqueta de la fecha va en nowrap, el ancho del texto le ponía piso a la
     barra: eso desbordaba 85px en un teléfono de 375px. */
  .pubbar { flex: 1; min-width: 0; display: flex; flex-direction: column;
    justify-content: flex-end; align-items: center; gap: 8px; height: 100%; }
  .pubbar .v { font-size: 11px; color: var(--txt2); font-variant-numeric: tabular-nums; }
  /* El color sale de una variable y cambia con el tema, como en charts.ts.
     Antes era el literal `linear-gradient(#6ea8fe, #3b6fc4)` con el argumento
     de que la magnitud es la misma en los dos temas; el argumento no sobrevivió
     a medirlo: #6ea8fe sobre el blanco del tema claro da 2,42:1, y justo en la
     punta de la barra, que es donde se lee el valor. En una barra el relleno
     ES el dato, así que rige el 3:1 de WCAG 1.4.11.

     El reemplazo fue --c1 y quedó azul. La paleta --c1..--c6 es de series, y
     existe para distinguir varias cosas dentro de un mismo gráfico; acá hay una
     sola serie, así que lo único que aportaba era un color ajeno al resto de la
     página. La portada ya tenía su color de barra —el --accent de .pubrow, miel
     de noche y marrón de día—, y usarlo deja la pantalla entera en una paleta.
     Medido con el mismo velo de volumen: el peor punto es la punta clara sobre
     el blanco del tema claro, 4,03:1; en oscuro, 7,63. */
  .pubbar .fill { width: 100%; background-color: var(--accent);
    background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0) 55%, rgba(0,0,0,.12));
    border-radius: 6px 6px 0 0; min-height: 3px; }
  /* Barra de total: composición al 100%, no volumen. Ver el comentario de
     `barraDeTotal`. El reparto por tipo vive solo acá: las barras diarias de
     arriba llevaron los mismos tramos un rato y eran ochenta y cuatro
     rectángulos para contar lo que cuenta esta sola barra. */
  .pubtot { margin-top: 14px; border-top: 1px solid var(--border);
    padding-top: 14px; }
  .pubtot-row { display: grid; grid-template-columns: 96px 1fr 56px;
    align-items: center; gap: 4px 12px; padding: 5px 0; }
  /* Cuarto hijo: cae solo en la segunda fila, bajo la barra. */
  .pubtot-key { grid-column: 2; display: flex; flex-wrap: wrap; gap: 2px 14px;
    font-size: 11px; color: var(--muted2); }
  .pubtot-key span { display: inline-flex; align-items: center; gap: 5px; }
  .pubtot-key b { width: 9px; height: 9px; border-radius: 2px;
    display: inline-block; flex-shrink: 0; }
  .pubtot-row .t { font-size: 12px; color: var(--txt2); line-height: 1.3; }
  /* Las ediciones van pegadas al rótulo y no al total, aunque sean el
     denominador de ese total: en la columna de la derecha hay 56px y "1.208 en
     19 ediciones" no entra sin partir el número. */
  .pubtot-row .t span { display: block; font-size: 11px; color: var(--muted2);
    font-weight: 400; }
  .pubtot-row .v { font-size: 13px; font-weight: 600; text-align: right;
    font-variant-numeric: tabular-nums; }
  .pubtot-bar { display: flex; height: 26px; border-radius: 6px;
    overflow: hidden; background: var(--row-alt); }
  .pubtot-bar > i { display: block; min-width: 1px;
    background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0) 55%, rgba(0,0,0,.12)); }
  .pubtot-prev { display: none; }
  .pubft-card.delta-on .pubtot-prev { display: block; }
  .pubbar .k { font-size: 10px; color: var(--muted2); white-space: nowrap;
    font-variant-numeric: tabular-nums; }
  /* Cinco barras con el mismo flex:1 de veintidós quedan de 160px cada una y
     el gráfico se lee como cinco columnas anchas sin relación entre sí. El
     tope de ancho las devuelve a proporción de barra; el gap más grande evita
     que se toquen al no llenar la fila. La etiqueta puede crecer porque acá
     sobra lugar: dice "5 o más", no "14/08". */
  .pubbars--pocas { gap: 20px; }
  .pubbars--pocas .pubbar { max-width: 96px; }
  .pubbars--pocas .pubbar .k { font-size: 13px; font-weight: 600;
    color: var(--txt2); }
  /* Filete entre filas y realce al pasar el mouse. Antes las filas se
     separaban solo por el aire, y en una lista de diez bancos con barras de
     distinto largo el ojo perdía el renglón a mitad de camino: el filete es lo
     que convierte una pila de barras en una tabla que se puede leer de
     corrido. Es el mismo recurso que ya usan la ficha del hero y los
     requisitos de la clave, así que no agrega vocabulario nuevo.
     El realce se pinta hacia afuera con un padding negativo, para que el
     renglón entero se ilumine y no solo la franja del texto. */
  .pubrow { display: grid; grid-template-columns: 62px 1fr 128px;
    align-items: center; gap: 12px; padding: 9px 8px; margin: 0 -8px;
    border-radius: 6px; border-bottom: 1px solid var(--border2);
    transition: background-color .12s ease; }
  .pubrow:last-child { border-bottom: 0; }
  /* :hover y no :focus-within: la fila no tiene controles, es solo lectura.
     Con el puntero sobre el renglón, el fondo dice cuál se está mirando. */
  .pubrow:hover { background: var(--row-hover); }
  /* En pantalla táctil no hay puntero que seguir, y el realce se queda pegado
     en la última fila tocada. */
  @media (hover: none) { .pubrow:hover { background: none; } }
  .pubrow .t { font-weight: 600; font-size: 14px; }
  .pubrow .track { background: var(--row-alt); border-radius: 6px; height: 22px;
    overflow: hidden; }
  .pubrow .fill { height: 100%; background-color: var(--accent);
    background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0) 55%, rgba(0,0,0,.12)); border-radius: 6px; }
  .pubrow .v { font-size: 13px; color: var(--muted); text-align: right;
    font-variant-numeric: tabular-nums; }
  .pubfoot { margin-top: 34px; padding-top: 18px;
    border-top: 1px solid var(--border2); color: var(--muted2); font-size: 12px;
    line-height: 1.7; }
  /* Variante de .pubrow con etiqueta más ancha para nombres de entidades. */
  .pubrow--wide { grid-template-columns: 140px 1fr 72px; }
  /* Línea de contexto del alcance total, al pie de la tira de números del
     período. No compite con los números grandes porque va fuera de las cajas,
     como una aclaración y no como un dato del mismo nivel. */
  .pubnums-foot { font-size: 12px; color: var(--muted2); margin-top: 10px;
    line-height: 1.55; }
  /* Grilla de tarjetas "Qué hay acá adentro". Seis tarjetas en tres columnas
     que colapsan a una en móvil. Cada tarjeta es autocontenida: título bold,
     descripción corta en --txt2 y viñetas en --muted. El prefijo .pubcat*
     evita colisiones con .pubrow, .pubsec y el resto de la hoja. */
  .pubcat { margin: 46px 0 0; padding-top: 26px;
    border-top: 1px solid var(--border2); }
  .pubcat > h2 { font-size: 18px; margin: 0 0 8px; letter-spacing: -.01em; }
  .pubcat > p { color: var(--muted); font-size: 13px; line-height: 1.65;
    margin: 0 0 24px; max-width: 68ch; }
  /* La banda del buscador: la tercera zona del boceto, ancho completo.
     Dos decisiones que la sostienen:

     1. BANDA PROPIA. Adentro de la columna izquierda heredaba la mitad del
        ancho y se leía como un apéndice de la bajada: la acción principal
        compitiendo por espacio con el texto que la explica. Con fondo, borde
        y aire propio pasa a leerse como el objeto con el que se interactúa.
     2. CONTENIDO ACOTADO (720px) Y CENTRADO. La banda es ancha para pesar,
        pero un campo de 1400px es incómodo de apuntar y de leer: el ojo
        pierde el punto de inicio. Ancho de banda, medida de lectura.

     El padding es de 24px y no de 30: son 12px que se le restan al presupuesto
     vertical de la primera pantalla, y ahí cada bloque cuenta. */
  .pubbuscador { margin: 12px 0 16px; padding: 32px;
    background: var(--card); border: 1px solid var(--border);
    border-radius: 14px; }
  .pubconsulta { max-width: 760px; margin: 0 auto; }
  .pubconsulta label { display: block; font-size: 11.5px; font-weight: 600;
    letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
    margin-bottom: 12px; }
  .pubconsulta > div { display: flex; gap: 12px; }
  /* 52px de alto: es el control más tocado del sitio y el piso cómodo para el
     pulgar. Un buscador que parece un input de formulario no se usa como
     buscador. */
  .pubconsulta input { flex: 1; min-width: 0; min-height: 56px;
    padding: 0 20px; font-size: 17px;
    background: var(--bg); color: var(--txt); border: 1px solid var(--border3);
    border-radius: 12px;
    transition: border-color .15s ease, box-shadow .15s ease; }
  .pubconsulta input::placeholder { color: var(--muted2); }
  .pubconsulta input:focus { outline: 2px solid var(--accent); outline-offset: 1px;
    border-color: var(--accent); }
  /* 56px: el único control del sitio que se sale de la escala, porque es el
     que tiene que leerse como invitación y no como botón de formulario. */
  .pubconsulta button { min-height: 56px; padding: 0 32px; font-size: 16px;
    font-weight: 600;
    background: var(--accent); color: var(--on-accent); border: 0;
    border-radius: 12px; cursor: pointer; white-space: nowrap; line-height: 1;
    transition: transform .15s ease, filter .15s ease; }
  .pubconsulta button:hover { transform: translateY(-1px); filter: brightness(1.06); }
  .pubconsulta button:focus-visible { outline: 2px solid var(--accent);
    outline-offset: 3px; }
  .pubconsulta-hint { color: var(--muted2); font-size: 12.5px; margin: 12px 0 0;
    line-height: 1.6; }
  /* "Con una cuenta" en el color del texto y no en el gris del resto: es el
     único pedazo de esta línea que tiene que saltar, porque es donde arranca
     la oferta. */
  .pubconsulta-hint strong { color: var(--txt2); font-weight: 600; }
  .pubconsulta-hint a { color: var(--accent); text-decoration: none;
    font-weight: 600; }
  .pubconsulta-hint a:hover { text-decoration: underline;
    text-underline-offset: 2px; }
  @media (max-width: 560px) {
    .pubbuscador { padding: 18px 14px; }
    .pubconsulta > div { flex-wrap: wrap; }
    .pubconsulta button { width: 100%; } }
  .pubmov-list { list-style: none; margin: 0; padding: 0; }
  .pubmov-list li { display: flex; justify-content: space-between; gap: 12px;
    align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--border2);
    font-size: 13px; }
  .pubmov-list li:last-child { border-bottom: 0; }
  .pubmov-list li > span { display: flex; flex-direction: column; min-width: 0; }
  .pubmov-list li > b { white-space: nowrap; font-variant-numeric: tabular-nums; }
  .pubmov-list small { color: var(--muted); font-size: 11px; }
  .pubmov-list em { font-style: normal; color: var(--muted); margin-left: 4px; }
  .pubmov-top { grid-column: span 2; }
  @media (max-width: 720px) { .pubmov-top { grid-column: auto; } }
  .pubcat-grid { display: grid;
    grid-template-columns: repeat(3, 1fr); gap: 14px; }
  .pubcat-card { background: var(--card); border: 1px solid var(--border);
    border-radius: 12px; padding: 20px 22px; }
  .pubcat-card h3 { font-size: 14px; font-weight: 700; margin: 0 0 6px;
    letter-spacing: -.01em; line-height: 1.3; }
  .pubcat-card .pubcat-desc { font-size: 13px; color: var(--txt2);
    line-height: 1.55; margin: 0 0 10px; }
  .pubcat-card ul { margin: 0; padding: 0 0 0 16px; }
  .pubexplora-card { display: block; text-decoration: none; color: inherit;
    transition: border-color .15s ease, transform .15s ease; }
  .pubexplora-card:hover, .pubexplora-card:focus-visible { border-color: var(--accent);
    transform: translateY(-2px); }
  .pubexplora-card h3 { color: var(--accent); }
  .pubexplora-pie { margin: 14px 0 0; font-size: 13px; color: var(--muted); }
  .pubexplora-pie a { color: var(--txt2); }
  .pubcat-card li { font-size: 12px; color: var(--muted); line-height: 1.6;
    margin-bottom: 2px; }
  /* Sección "De dónde salen los datos": mismo separador visual que .pubdiv
     pero sin la altura de .pubcat-grid arriba. Reutiliza .pubdiv a propósito:
     misma tipografía, mismo ancho, misma familia visual. */
  .pubsource { margin: 46px 0 0; padding-top: 26px;
    border-top: 1px solid var(--border2); }
  .pubsource > h2 { font-size: 20px; margin: 0 0 8px; letter-spacing: -.01em; }
  .pubsource > p { color: var(--muted); font-size: 13px; line-height: 1.65;
    margin: 0 0 14px; max-width: 68ch; }
  /* Separador entre los gráficos del período y los que miran la base entera.
     No es decorativo: son dos cosas distintas y hasta que estuvo la línea, la
     página daba a entender que todo lo de abajo también era de los últimos
     treinta días. */
  .pubdiv { margin: 46px 0 0; padding-top: 26px;
    border-top: 1px solid var(--border2); }
  .pubdiv h2 { font-size: 20px; margin: 0 0 8px; letter-spacing: -.01em; }
  .pubdiv p { color: var(--muted); font-size: 13px; line-height: 1.65;
    margin: 0; max-width: 68ch; }
  /* Histograma año por año de la edad al fundar: cincuenta y tres barras. Con
     el gap de 5px del calendario diario cada una queda en 10px y el gráfico se
     lee como una trama en vez de una curva. Van casi pegadas —es una
     distribución continua, no ediciones sueltas— y la etiqueta sale solo en los
     múltiplos de cinco, que es lo único que entra sin encimarse. */
  /* El padding lateral es para las etiquetas de las puntas: van centradas bajo
     una barra de 10px, así que "20" y "70" se salen del ancho del contenedor y
     quedan cortadas contra el borde de la página. */
  .pubbars--anios { gap: 1px; height: 170px; padding: 10px 12px 0; }
  .pubbars--anios .pubbar { gap: 5px; }
  .pubbars--anios .pubbar .fill { border-radius: 2px 2px 0 0; }
  .pubbars--anios .pubbar .k { font-size: 10px; color: var(--muted2); }
  /* El intervalo de confianza dibujado encima de la barra. No es decoración:
     dos bandas vecinas cuyos intervalos se pisan no son distinguibles, y sin el
     bigote el gráfico invita a leer una diferencia que no está medida. */
  .pubrow .track { position: relative; }
  .pubrow .ci { position: absolute; top: 3px; bottom: 3px;
    border-left: 2px solid var(--txt2); border-right: 2px solid var(--txt2); }
  .pubrow .ci::before { content: ""; position: absolute; top: 50%; left: 0;
    right: 0; border-top: 2px solid var(--txt2); }
  .pubrow .v .ci-txt { color: var(--muted2); font-size: 11px; }
  /* La porción de la barra que corresponde a los socios que no operan con
     ninguna otra entidad. Va adentro de la misma barra y no en un gráfico
     aparte porque es un subconjunto de ella: dos barras separadas invitan a
     leer 40% y 25% como dos poblaciones, cuando la segunda está adentro de la
     primera. Usa --accent2, que es el mismo matiz con otra luminosidad, así que
     se lee como una parte de la barra y no como otra serie. El filete de
     --card es el que separa las dos porciones cuando el contraste entre
     acentos es bajo. */
  .pubrow .fill--excl { display: none; position: absolute; left: 0; top: 0;
    height: 100%; background-color: var(--accent2);
    border-radius: 6px 0 0 6px; border-right: 2px solid var(--card); }
  /* Interruptor sin JavaScript: la portada se sirve con script-src 'self' y
     no carga ningún script propio, así que el estado vive en el :checked de un
     input real y el CSS lo lee por hermandad. Además es accesible por teclado
     y lo anuncia el lector de pantalla, que es más de lo que daría un div con
     un listener. */
  /* El input queda arriba de las barras aunque la casilla se vea abajo: el
     combinador de hermano general solo alcanza hermanos posteriores, así que
     mover el input al pie rompería el interruptor. No ocupa espacio ni se ve,
     y el atributo for deja poner la etiqueta donde se quiera. */
  .pubtick { position: absolute; opacity: 0; width: 0; height: 0; }
  .pubticklbl { display: inline-flex; align-items: center; gap: 9px;
    cursor: pointer; font-size: 13px; color: var(--txt2); margin: 14px 0 0;
    border: 1px solid var(--border); border-radius: 8px; padding: 8px 13px;
    background: var(--bg2); }
  .pubticklbl::before { content: ""; width: 15px; height: 15px; flex: none;
    border: 1px solid var(--border3); border-radius: 4px; background: var(--card); }
  .pubticklbl:hover { border-color: var(--border3); color: var(--txt); }
  .pubtick:checked ~ .pubticklbl::before { background: var(--accent2);
    border-color: var(--accent2); }
  .pubtick:focus-visible ~ .pubticklbl { outline: 2px solid var(--accent);
    outline-offset: 2px; }
  .pubtick:checked ~ .pubrows .fill--excl { display: block; }
  /* En bloque y no al lado: son dos números del mismo renglón y la columna de
     valor mide 72px. Puestos en línea, "36,6% 9,7%" se lee como un rango y
     además envuelve. Apilados y alineados a la derecha se leen como el total y
     su parte, que es lo que son. No llevan sufijo: la casilla de arriba ya dice
     qué es el segundo, y repetirlo diez veces convierte la columna en ruido. */
  .pubtick:checked ~ .pubrows .v .excl-txt { display: block; }
  .pubrow .v .excl-txt { display: none; color: var(--accent2);
    font-size: 11px; }
  /* En el gráfico destacado la porción de clientes únicos va siempre a la
     vista, sin interruptor. Arriba no hay nada más que mirar y el dato es la
     mitad de la historia: "opera con Galicia" y "opera SOLO con Galicia" son
     dos preguntas distintas. Abajo, en cambio, la sección compite con otras
     ocho y el interruptor evita que el renglón traiga dos números de entrada. */
  .pubrows--excl .fill--excl,
  .pubrows--excl .v .excl-txt { display: block; }
  /* La columna de valor del destacado es más ancha que los 72px del resto
     porque tiene que aguantar "36,6% −1,8 pts" en un solo renglón. Con 72px el
     delta caía a una tercera línea y cada fila crecía 15px al activar la
     comparativa: medido, el gráfico entero saltaba 146px y el ojo perdía la
     fila que venía siguiendo. El ancho es fijo y no depende del estado, así
     que las barras miden lo mismo con la comparativa prendida y apagada. */
  .pubrows--excl .pubrow--wide { grid-template-columns: 140px 1fr 118px; }
  /* Variante con etiqueta angosta y valor ancho: la banda de edad ("25-29")
     ocupa poco y el valor lleva el porcentaje y su margen. */
  .pubrow--band { grid-template-columns: 58px 1fr 118px; }
  /* Bloque de destaque para "no figuran": misma tarjeta que .pubcaptype pero
     ocupa el ancho completo y tiene más padding para dar peso visual. */
  .pubunbanked { background: var(--card); border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: 12px;
    padding: 22px 26px; }
  .pubunbanked .n { font-size: 38px; font-weight: 700; letter-spacing: -.03em;
    margin: 0 0 4px; }
  .pubunbanked .range { font-size: 16px; font-weight: 600; color: var(--accent);
    margin: 0 0 10px; }
  .pubunbanked .desc { font-size: 13px; color: var(--muted); line-height: 1.65;
    margin: 0; max-width: 68ch; }
  /* Formulario de buzón público. No lleva JS: el estado vive en el server.
     Va envuelto en .pubform para prefijarlo y no pisar nada de .login ni de
     los controles del dashboard que comparten el mismo STYLE.

     Antes era el último bloque de la portada y se separaba del gráfico de
     arriba con un filete; ahora es una pantalla propia, así que en vez de
     dividir tiene que centrarse. El ancho es el mismo de .pubthanks, que es
     la pantalla a la que lleva. */
  .pubform { max-width: 560px; margin: 56px auto 0; }
  .pubform h1 { font-size: 26px; margin: 0 0 12px; letter-spacing: -.02em; }
  .pubform > p { color: var(--muted); font-size: 13px; line-height: 1.65;
    margin: 0 0 22px; max-width: 68ch; }
  .pubform fieldset { border: none; padding: 0; margin: 0; }
  .pubform label { display: block; font-size: 13px; font-weight: 600;
    color: var(--txt2); margin-bottom: 6px; }
  .pubform label .opt { font-weight: 400; color: var(--muted);
    margin-left: 4px; }
  /* Selector de tipo: tres botones de radio disfrazados de chips. El estado
     vive en :checked, no en JS, para cumplir la CSP sin excepción. */
  .pubform-tipos { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
  .pubform-tipos input[type="radio"] { position: absolute; opacity: 0;
    width: 0; height: 0; }
  .pubform-tipos label { font-weight: 400; cursor: pointer;
    padding: 7px 16px; border: 1px solid var(--border3);
    border-radius: 20px; font-size: 14px; color: var(--txt2);
    background: var(--bg2); margin-bottom: 0; transition: none; }
  .pubform-tipos label:hover { border-color: var(--muted);
    color: var(--txt); }
  .pubform-tipos input[type="radio"]:checked + label {
    background: var(--accent); color: var(--on-accent);
    border-color: var(--accent); font-weight: 600; }
  .pubform-tipos input[type="radio"]:focus-visible + label {
    outline: 2px solid var(--accent); outline-offset: 2px; }
  .pubform textarea, .pubform input[type="email"] {
    width: 100%; background: var(--bg2); border: 1px solid var(--border3);
    border-radius: 10px; color: var(--txt); font-family: inherit;
    font-size: 14px; padding: 12px 14px; resize: vertical;
    box-sizing: border-box; }
  .pubform textarea { min-height: 120px; margin-bottom: 4px; }
  .pubform textarea:focus, .pubform input[type="email"]:focus {
    outline: 2px solid var(--accent); outline-offset: 1px;
    border-color: var(--accent); }
  .pubform .field { margin-bottom: 18px; }
  .pubform .privnote { font-size: 12px; color: var(--muted2);
    margin-top: 5px; line-height: 1.5; }
  .pubform button[type="submit"] { background: var(--accent);
    color: var(--on-accent); border: none; border-radius: 10px;
    padding: 11px 28px; font-size: 15px; font-weight: 600;
    cursor: pointer; font-family: inherit; }
  .pubform button[type="submit"]:hover { background: var(--accent2); }
  /* Pantalla de agradecimiento: un bloque centrado, mismo ancho que el
     formulario, que vuelve al flujo del .pub con su fondo y su fuente. */
  .pubthanks { max-width: 520px; margin: 80px auto 0; text-align: center; }
  .pubthanks h1 { font-size: 26px; margin: 0 0 12px; letter-spacing: -.02em; }
  .pubthanks p { color: var(--muted); font-size: 15px; line-height: 1.65;
    margin: 0 0 28px; }
  .pubthanks a { display: inline-block; background: var(--accent);
    color: var(--on-accent); text-decoration: none; border-radius: 10px;
    padding: 10px 26px; font-size: 14px; font-weight: 600; }
  .pubthanks a:hover { background: var(--accent2); }
  /* ----------------------------------------------------------------
     Bloque de gráfico destacado (/?g=<slug>).
     Prefijo .pubft* (featured) para no pisar ningún .pub* existente.

     Las pestañas son <a href> sin JS: cada slug es una URL real para
     que el bot de WhatsApp/LinkedIn pueda leer og:title propio.
     En móvil el contenedor scrollea horizontalmente para que las cinco
     pestañas quepan en 360px sin envolver ni recortar el texto.
     ---------------------------------------------------------------- */
  .pubft { border-bottom: 1px solid var(--border); margin-bottom: 0; }
  /* Pestañas en fila scrolleable. overflow-x:auto sin scrollbar visible:
     en escritorio caben todas; en móvil el usuario desliza. */
  .pubft-tabs { display: flex; gap: 0; overflow-x: auto;
    scrollbar-width: none; padding: 0; margin: 0; list-style: none; }
  .pubft-tabs::-webkit-scrollbar { display: none; }
  .pubft-tabs li { flex: none; }
  /* Solo para lectores de pantalla. Se saca de la vista recortándola a un
     píxel y no con display:none ni visibility:hidden, porque las dos la
     sacarían también del árbol de accesibilidad y no se anunciaría nada. */
  .pubsr { position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  /* Pestaña base: texto chico, sin relieve. El active lleva el acento
     y un filete inferior que marca cuál está seleccionada. */
  .pubft-tabs a { display: block; padding: 10px 16px; font-size: 13px;
    font-weight: 500; color: var(--muted); text-decoration: none;
    border-bottom: 2px solid transparent; white-space: nowrap;
    transition: color .15s; }
  .pubft-tabs a:hover { color: var(--txt); }
  .pubft-tabs a[aria-current="page"] { color: var(--accent);
    border-bottom-color: var(--accent); font-weight: 600; }
  /* El gráfico destacado: tarjeta con padding generoso. Este título fue un h1
     mientras .pubhero no tenía ninguno, y ahí estaba bien. Cuando el titular
     volvió al hero la página quedó con DOS h1 y con el orden h1, h1, h2, h2:
     para un lector de pantalla eso son dos documentos pegados, y el índice de
     encabezados —que es como se recorre una página larga sin ver— deja de
     servir. Es h2 como las otras secciones, y el h1 es uno solo: el del hero. */
  .pubft-card { padding: 30px 0 28px; }
  /* Fila con el título y el botón de comparativa al costado. */
  .pubft-head { display: flex; align-items: baseline; flex-wrap: wrap;
    gap: 10px; margin: 0 0 8px; }
  .pubft-card h2 { font-size: 26px; line-height: 1.2; margin: 0;
    letter-spacing: -.02em; }
  /* Botón "vs. período anterior": empieza oculto; public.js lo muestra
     solo cuando stats.delta no es null (sin JS = sin control muerto). */
  .pubdelta-btn { display: none; align-items: center; gap: 4px;
    padding: 4px 10px; border: 1px solid var(--border3);
    border-radius: 99px; font-size: 12px; font-weight: 600;
    color: var(--muted); background: var(--bg2);
    cursor: pointer; font-family: inherit; white-space: nowrap;
    transition: border-color .15s, color .15s, background .15s; }
  .pubdelta-btn:hover { color: var(--txt); border-color: var(--accent);
    background: var(--card); }
  .pubdelta-btn[aria-pressed="true"] { color: var(--accent);
    border-color: var(--accent); background: var(--card); }
  /* Línea con el período de referencia. Va arriba del subtítulo, así que
     aparecer y desaparecer movía 14px todo lo que sigue: las 21 barras
     saltaban al tocar el botón. Por eso no se oculta con display sino con
     visibility: ocupa su renglón siempre y el gráfico no se mueve.
     El renglón solo existe si public.js confirmó que hay comparativa
     (.delta-ready); sin datos no se reserva un hueco para nada. */
  .pubdelta-range { display: none; font-size: 12px; color: var(--muted2);
    margin: 6px 0 0; }
  .pubft-card.delta-ready .pubdelta-range { display: block;
    visibility: hidden; }
  .pubft-card.delta-ready.delta-on .pubdelta-range { visibility: visible; }
  /* Badge de delta en la fila de un banco: oculto hasta activar. */
  /* Lleva margen a los dos lados porque en "sin banco" el badge cae en medio
     de una frase ("entre 10,9% −0,7 pts y 17,1%"), no al final de una fila. */
  .pubdelta-badge { display: none; font-size: 11px; font-weight: 600;
    margin: 0 4px 0 6px; color: var(--muted2); }
  .pubft-card.delta-on .pubdelta-badge { display: inline; }
  /* Movimiento en la tabla de posiciones, al lado del nombre de la entidad.
     Va escondido con el resto de la comparativa: es dato del período anterior
     y no tiene sentido sin el encabezado que dice contra cuál se compara. */
  .pubmov { display: none; font-size: 11px; font-weight: 700;
    margin-left: 6px; letter-spacing: -.2px; }
  .pubft-card.delta-on .pubmov { display: inline; }
  .pubmov--sube { color: var(--sig-ok); }
  .pubmov--baja { color: var(--sig-bad); }
  .pubmov--nuevo { color: var(--accent); font-weight: 600;
    text-transform: uppercase; font-size: 10px; }
  /* Línea de delta al pie del gráfico, para los que no tienen filas donde
     colgar un badge: nacimientos y el semáforo crediticio. */
  .pubdelta-foot { display: none; font-size: 13px; color: var(--muted2);
    margin-top: 12px; }
  .pubft-card.delta-on .pubdelta-foot { display: block; }
  .pubft-card .sub { color: var(--muted2); font-size: 14px;
    margin: 0 0 20px; line-height: 1.55; max-width: 68ch; }
  /* El piso de 260px se fue con la placa de color. Existía para darle peso
     visual al destaque cuando el bloque no tenía fondo y el hueco de más no
     se veía; ahora el fondo lo tiene, y en el semáforo —que es una sola barra
     apilada de ~80px— quedaban 180px de plato vacío antes de los botones de
     compartir. El peso lo da la placa, no el aire. */
  /* Sección de compartir: encabezado chico y fila de botones con
     suficiente relieve para que se lean como controles accionables. */
  .pubft-share { margin-top: 28px; border-top: 1px solid var(--border);
    padding-top: 18px; }
  .pubft-share-label { font-size: 11px; font-weight: 600; letter-spacing: .06em;
    text-transform: uppercase; color: var(--muted2); margin-bottom: 10px; }
  .pubft-share-btns { display: flex; flex-wrap: wrap; gap: 8px; }
  .pubft-share a, .pubft-share button { display: inline-flex;
    align-items: center; gap: 7px; padding: 9px 16px;
    border: 1px solid var(--border3); border-radius: 8px;
    font-size: 13px; font-weight: 600; color: var(--txt2);
    background: var(--bg2); text-decoration: none; cursor: pointer;
    font-family: inherit; white-space: nowrap;
    transition: border-color .15s, color .15s, background .15s; }
  .pubft-share a:hover, .pubft-share button:hover {
    color: var(--txt); border-color: var(--accent);
    background: var(--card); }
  .pubft-share a svg, .pubft-share button svg {
    flex-shrink: 0; opacity: .8; }
  /* El botón de copia cambia de texto cuando JS confirma el éxito.
     El span .copied está oculto; JS lo muestra brevemente. */
  .pubft-share button .copied { display: none; }
  /* Y el botón entero nace apagado, igual que el de comparativa: es el único
     de los cuatro que no es un <a> y no funciona solo. Sin JS —o servido por
     HTTP plano, donde navigator.clipboard no existe— quedaba a la vista un
     control que al tocarlo no hace absolutamente nada. Los otros tres son
     enlaces de verdad y andan igual. Lo prende armarCopia, que es el mismo
     lugar donde ya se chequea que el portapapeles esté disponible.

     Va calificado con .pubft-share y no suelto: la regla de arriba pone el
     inline-flex con un selector de clase MÁS etiqueta, que le gana a una clase
     sola. Escrito como .pubcopy a secas el botón seguía viéndose y el arreglo
     no hacía nada. */
  .pubft-share .pubcopy { display: none; }
  .pubft-share .pubcopy.listo { display: inline-flex; }

  /* ================================================================
     PORTADA: PALETA, TIPOGRAFÍA Y ARMADO PROPIOS
     ================================================================
     Las variables se redefinen en body.pubbody y no en :root porque esta
     hoja es una sola y la comparten el dashboard, el login y la ayuda:
     tocar --bg arriba le cambiaba el fondo a la herramienta que usan los
     ejecutivos todos los días. Redefinirlas en el body funciona porque las
     custom properties heredan y el elemento más cercano gana, sin importar
     la especificidad del selector que las puso en el html.

     Solo se toca el tema claro. El reclamo era ese —el crema #faf6ec se
     lee amarillo— y el oscuro no tenía problema; cambiarlo igual sería
     mover algo que nadie pidió. */
  :root.light body.pubbody {
    --bg:        #fdfcfa;
    --bg2:       #ffffff;
    --card:      #ffffff;
    --border:    #e6e1d6;
    --border2:   #efebe3;
    --border3:   #cfc7b8;
    --row-alt:   #f4f1ea;
    /* Tinta, no marrón: #100d08 sobre el papel da 18,9:1 contra los 13,5:1
       del #241c11 anterior. El pedido era "letras un poco más oscuras" y
       esto es lo que lo cumple sin tocar el acento, que es la marca. */
    --txt:       #100d08;
    --txt2:      #33291b;
    --muted:     #4a4032;
    --muted2:    #625848;
  }
  /* Serif de titulares. No es una fuente cargada: la CSP no tiene font-src
     y cae en default-src 'self', así que un CDN estaba descartado y una
     .woff2 propia habría sumado un binario al repo por cinco titulares.
     Georgia está en Windows y en macOS, y la cadena cubre Linux y Android;
     el cuerpo sigue en la sans del sistema, que es lo que se lee mejor a
     13px. La mezcla serif-para-titular / sans-para-texto es lo que le da
     el aire de publicación y no de panel. */
  /* La cadena vivía acá, en body.pubbody, o sea existía **solo en las pantallas
     públicas**: cualquier var(--display) del dashboard no resolvía nada y caía
     en la sans heredada sin avisar. Se mudó a :root (arriba) porque es un token
     de tipografía y no una decisión de la portada; acá queda la nota para que no
     vuelva a definirse en un solo body. */
  .pubft-card h1, .pubcat > h2, .pubsource > h2, .pubform h1,
  .pubhero h1, .pubthanks h1 {
    font-family: var(--display); font-weight: 400; letter-spacing: -.015em; }
  /* Escala fluida: 62px en una pantalla de escritorio, 34px en un teléfono, y
     todo lo del medio sin saltos. A 34px fijos —como estaba— el titular medía
     lo mismo que el h2 de una sección de abajo, y un hero cuyo titular pesa
     igual que un subtítulo se lee como una introducción y no como una portada.
     Anthropic y Platzi usan ~64px en el mismo lugar; el tamaño ES la jerarquía.
     El tope de 1.02 en line-height solo aplica de grande: apretado en el
     teléfono, las tres líneas se tocarían. */
  /* 36px, no 62. Ver la cuenta completa en body.pubbody .pubhero: a 62px el
     titular solo se comía la mitad del presupuesto vertical de la primera
     pantalla. */
  .pubhero h1 { font-size: clamp(28px, 3.4vw, 44px); line-height: 1.08;
    margin: 0 0 18px; max-width: 26ch; letter-spacing: -.022em;
    /* Reparte las palabras entre los renglones en vez de llenar el primero y
       dejar dos palabras sueltas en el último. Es una línea de CSS que hace
       el trabajo que en un diario hace el editor. */
    text-wrap: balance; }

  /* Barra superior fija. Los márgenes negativos existen porque .pub tiene
     20px de padding lateral: sin ellos el fondo de la barra no llega al
     borde de la columna y el contenido pasa por el costado al scrollear. */
  /* La barra fija, con el MISMO padding en las tres pantallas públicas la use
     quien la use, esté o no dentro del contenedor .pub.
     Ese era el defecto que vio Romen el 2026-09-12 ("cuando estoy consultando
     el CUIT veo el header sin padding"): en la portada la barra vive adentro de
     .pub —que ya tiene 20px— y se estiraba con un margen negativo de 20px; en la
     consulta el header es hijo del body, ese margen negativo lo sacaba del
     borde y encima chocaba con el padding de 16 por 24 de la regla header.
     Dos reglas peleando y un resultado distinto por pantalla.
     La cuenta de abajo es la misma que usa el pie y funciona en los dos casos:
     el fondo llega siempre al borde de la ventana y el contenido queda siempre
     en la columna del sitio, sin importar de quién sea hijo el elemento. */
  .pubtop { position: sticky; top: 0; z-index: 30;
    margin-inline: calc(50% - 50vw);
    /* La barra va al MARGEN de la pantalla y no alineada con la columna del
       cuerpo. Romen (2026-09-12): "el padding del footer y header puede estar
       más al margen; el cuerpo sí se puede centralizar más, como está".
       Medido en Anthropic, que es la referencia que dio: en una ventana de
       1400px su contenido arranca a 65px del borde. La cuenta de acá da 63px
       a ese ancho, 20px en un teléfono y no pasa de 64px en un monitor
       grande, para que en 2560 el logo no termine en la otra punta.
       Va con vw y nunca con porcentajes: un % en padding se resuelve contra el
       ancho del padre, y el padre de esta barra es .pub en la portada y el
       body en la consulta —con % el logo quedaba 89px corrido, medido—. */
    padding: 14px clamp(20px, 4.5vw, 64px);
    background: var(--bg); box-shadow: 0 1px 0 var(--border2); }
  /* La regla header general le mete 16 por 24 a todo header del sitio: acá
     se anula, porque el padding de esta barra lo decide la cuenta de arriba. */
  header.pubtop { padding-block: 14px; }

  /* Hero en dos columnas: el titular manda y la ficha de números lo
     acompaña a la derecha, alineada al pie del titular. En una sola columna
     los tres números competían con el titular por el primer golpe de vista. */
  /* Las dos columnas del boceto, con los tamaños ajustados para que las TRES
     zonas —titular, cifra y buscador— entren juntas arriba del pliegue.
     Esta es la cuenta que manda, sobre el caso peor (notebook 1366x768, que
     deja ~650px de viewport). Alturas calculadas del CSS de acá abajo:

       header                                60
       aire del hero                         52
       eyebrow                               27
       titular 44px x 2 lineas               95 + 18 de margen
       bajada 16px x 3 lineas                82
       cierre del hero                       36
       ---------------------------------------
       el hero termina en                   370
       separacion                            12
       banda (32+24+58+24+32)               170
       ---------------------------------------
       el campo TERMINA en                  552   de 650

     Quedan ~100px de la seccion siguiente asomando, que es lo que le avisa al
     usuario que hay mas abajo sin obligarlo a adivinarlo. Ese asomo es el
     piso: si algun dia hay que sumar un renglon arriba, sale de aca y no del
     buscador.

     Antes el titular iba a 62px y la bajada en dos párrafos: el hero solo
     medía ~330px y las tres zonas terminaban a 785px, o sea que el buscador
     caía abajo del pliegue. El titular sigue siendo el elemento más grande de
     la página; lo que cambió es cuánto le gana al resto.
     Si algún día se agrega un renglón acá arriba, rehacer la cuenta: cada
     bloque que entra empuja el campo hacia abajo y ese es el presupuesto. */
  body.pubbody .pubhero { display: grid; gap: 28px 72px; align-items: start;
    grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr);
    padding: 52px 0 36px; }
  /* Compensa el alto del eyebrow para que la cifra arranque a la altura de la
     primera línea del titular y no del rótulo de arriba. */
  body.pubbody .pubhero > :last-child { padding-top: 28px; }
  @media (max-width: 860px) {
    body.pubbody .pubhero > :last-child { padding-top: 0; } }
  .pubhero-eyebrow { font-size: 11px; font-weight: 600; letter-spacing: .12em;
    text-transform: uppercase; color: var(--muted2); margin: 0 0 16px; }
  /* Dos pesos de bajada y no un párrafo de seis líneas: la primera oración
     acompaña al titular y el resto queda a media voz. Debajo de un titular de
     62px, un bloque gris de seis renglones es lo que hacía que el hero
     pareciera un prospecto. */
  .pubhero-lead { color: var(--txt2); font-size: 16px;
    line-height: 1.7; margin: 0; max-width: 50ch; font-weight: 400; }
  /* Los mismos tres números, pero como ficha de registro y no como tres
     cajas: rótulo a la izquierda, cifra a la derecha en tabulares y un
     filete entre renglones. Las cajas con borde ya las tiene el gráfico de
     abajo, y repetir la forma hacía que todo pesara lo mismo. */
  .pubhero .pubnums { display: block; margin: 0;
    border-top: 1px solid var(--border); }
  .pubhero .pubnum { display: flex; align-items: baseline;
    justify-content: space-between; gap: 14px; background: none;
    border: none; border-bottom: 1px solid var(--border); border-radius: 0;
    padding: 11px 0; }
  .pubhero .pubnum .n { font-size: 21px; order: 2; text-align: right;
    font-variant-numeric: tabular-nums; white-space: nowrap; }
  .pubhero .pubnum .l { order: 1; margin: 0; font-size: 12px; }
  .pubhero .pubnum--periodo .n { font-size: 13px; font-weight: 600;
    color: var(--txt2); }

  /* LA CIFRA. El dato más fuerte del sitio —cuántas sociedades nacieron en
     treinta días— estaba en un renglón de 21px al costado, tratado como nota
     al pie. Acá pasa a ser lo único que compite con el titular, y le da al
     hero algo que MIRAR: es el lugar que en Anthropic ocupa una pieza visual
     grande y que nosotros no podemos llenar con una foto, porque lo que
     tenemos son números.
     En la serif de titulares y con cifras tabulares: es una cifra de registro,
     no un contador de marketing. */
  .pubcifra { display: block; }
  /* Bajó junto con el titular: a 96px la cifra sola medía más que las tres
     líneas de texto de al lado y desbalanceaba las dos columnas. */
  .pubcifra .v { font-family: var(--display); font-weight: 400;
    font-size: clamp(42px, 4.7vw, 60px); line-height: .94;
    letter-spacing: -.03em; color: var(--txt);
    font-variant-numeric: tabular-nums; display: block; }
  .pubcifra .k { display: block; margin-top: 10px; font-size: 13px;
    color: var(--muted); max-width: 26ch; line-height: 1.5; }
  /* El filete arriba ata la cifra a la ficha de datos que sigue: son la misma
     lectura, una destacada y el resto al pie. */
  .pubcifra + .pubnums { margin-top: 22px; }

  /* El bloque destacado pasa a ser una placa con color propio por pestaña.
     El pedido era que los gráficos se distingan entre sí; el tinte hace eso
     sin tocar el color de las barras, que sigue siendo el acento de la marca
     en las cinco vistas y por eso se puede comparar una con otra.
     --row-alt se redefine en negro/blanco translúcido en vez de un color
     fijo: así el fondo de las pistas sigue al tinte de la placa en vez de
     quedar el crema de la página flotando sobre un plato azul. */
  .pubft { border: 1px solid var(--border); border-radius: 20px;
    background: var(--plate, var(--card)); padding: 6px 26px 4px;
    margin-bottom: 8px; }
  :root.light .pubft { --row-alt: rgba(0,0,0,.06); }
  :root:not(.light) .pubft { --row-alt: rgba(255,255,255,.07); }
  :root.light .pubft[data-g="bancos"]      { --plate: #eef3fb; }
  :root.light .pubft[data-g="paga-mejor"]  { --plate: #edf5ef; }
  :root.light .pubft[data-g="semaforo"]    { --plate: #fdf1e6; }
  :root.light .pubft[data-g="sin-banco"]   { --plate: #f3f0fa; }
  :root.light .pubft[data-g="nacimientos"] { --plate: #fbf3e2; }
  :root:not(.light) .pubft[data-g="bancos"]      { --plate: #101a2b; }
  :root:not(.light) .pubft[data-g="paga-mejor"]  { --plate: #0e1d18; }
  :root:not(.light) .pubft[data-g="semaforo"]    { --plate: #221a11; }
  :root:not(.light) .pubft[data-g="sin-banco"]   { --plate: #16142a; }
  :root:not(.light) .pubft[data-g="nacimientos"] { --plate: #1c1710; }
  .pubft-tabs { border-bottom: 1px solid var(--border); }
  .pubft-tabs a { padding: 12px 14px; }
  .pubft-card h1 { font-size: 30px; }

  /* Las seis tarjetas pasaron a tres. No se perdió nada de lo que decían:
     "el Boletín completo día por día" y "el texto original entero" no
     describen qué hay adentro sino cómo se arma, y bajaron a la lista de
     método, que es donde se leen cuando importan. Con seis, la grilla era
     una pared de viñetas que nadie termina.
     El filete de color arriba de cada una repite el tinte de las placas de
     los gráficos: liga las dos mitades de la página. */
  .pubcat-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .pubcat-card { border-radius: 14px; padding: 22px 22px 24px;
    border-top: 3px solid var(--tone, var(--accent)); }
  .pubcat-card:nth-child(1) { --tone: #4a76c4; }
  .pubcat-card:nth-child(2) { --tone: #3f8a63; }
  .pubcat-card:nth-child(3) { --tone: #b4761d; }
  .pubcat-card h3 { font-size: 15px; }
  .pubcat-card .pubcat-desc { color: var(--txt2); font-size: 13px; }

  /* Lista de método: reemplaza los dos párrafos que explicaban qué es el
     Boletín y qué es la Central. Nombrar las dos fuentes es una condición
     de reúso, así que eso queda; el resto era prosa. */
  .pubmeth { margin: 16px 0 0; padding: 0; list-style: none; }
  .pubmeth li { font-size: 13px; color: var(--muted); line-height: 1.6;
    padding: 9px 0 9px 22px; position: relative;
    border-top: 1px solid var(--border2); }
  .pubmeth li::before { content: ""; position: absolute; left: 4px; top: 16px;
    width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

  /* Pie oscuro en los dos temas. Es una placa con esquinas redondeadas y no
     una banda al ancho de la ventana: la columna mide 880px y una banda
     suelta con los costados claros se lee como un error de maquetación.
     Las variables se redefinen adentro por el mismo motivo que en el body:
     la tinta del tema claro sobre este fondo no se vería. */
  /* La franja de contacto, entre el último gráfico y el pie. Es la única pieza
     de la portada que pide algo, así que es la única con el fondo lleno de
     miel: todo lo de arriba informa y esto interrumpe. El botón invierte los
     colores en vez de llevar un borde, que sobre este fondo se perdería. */
  .pubcta { margin-top: 46px; padding: 30px 26px; border-radius: 20px;
    background: var(--accent); color: var(--on-accent);
    display: flex; align-items: center; justify-content: space-between;
    gap: 18px 26px; flex-wrap: wrap; }
  .pubcta h2 { font-family: var(--display); font-weight: 400; font-size: 26px;
    letter-spacing: -.015em; margin: 0 0 8px; color: inherit; }
  .pubcta p { margin: 0; font-size: 14px; line-height: 1.6; max-width: 58ch;
    opacity: .84; }
  .pubcta > a { display: inline-block; padding: 13px 24px; border-radius: 10px;
    background: var(--on-accent); color: var(--accent); text-decoration: none;
    font-weight: 700; font-size: 15px; white-space: nowrap; }
  .pubcta > a:hover { opacity: .86; }
  /* El pie va a sangre y pegado al fondo. Antes era una tarjeta: medido en una
     ventana de 1400px, ocupaba 840 —280 de aire a cada lado—, tenía 20px de
     radio y dejaba 64px de página debajo. Romen lo dijo con precisión el
     2026-09-12: "pareciera ser un contenedor flotante, no un footer".
     El truco de los márgenes negativos estira el FONDO hasta los bordes de la
     ventana mientras el contenido sigue alineado con la columna del sitio, que
     es lo que hace que un pie se lea como el cierre de la página y no como un
     bloque más. El padding lateral compensa el margen para que el texto no se
     vaya al borde. 100vw y no 100% porque el contenedor está limitado a 880. */
  .pubfoot { --txt: #f6efe4; --txt2: #d3c8b5; --muted: #a99c86;
    --muted2: #948a79; --border2: #2b2419; --accent: #e8b25c;
    /* Al margen, igual que la barra de arriba: el pie y el encabezado son el
       marco de la página y respiran contra el borde; el cuerpo es la lectura
       y sigue centrado en su columna. */
    margin: 64px calc(50% - 50vw) 0;
    padding: 48px clamp(20px, 4.5vw, 64px) 40px;
    border-top: none; background: #14110c; border-radius: 0;
    color: var(--muted); font-size: 13px; line-height: 1.7; }
  .pubfoot-grid { display: grid; gap: 28px 32px;
    grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr)); }
  .pubfoot .pubmark { color: var(--txt); font-size: 18px; }
  .pubfoot-tag { margin: 12px 0 0; font-size: 13px; color: var(--muted);
    max-width: 34ch; line-height: 1.6; }
  /* h3 y no h4: el pie viene después de una sección h2, y saltarse el h3 le
     abre un agujero al índice de encabezados, que es como se recorre una
     página larga sin verla. El tamaño lo pone esta regla, no la etiqueta. */
  .pubfoot h3 { font-size: 11px; font-weight: 600; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted2); margin: 0 0 16px; }
  .pubfoot ul { list-style: none; margin: 0; padding: 0; }
  .pubfoot li { margin-bottom: 7px; }
  .pubfoot a { color: var(--txt2); text-decoration: none; font-size: 13px; }
  .pubfoot a:hover { color: var(--accent); }
  /* Los dos controles del pie, alineados a la derecha del bloque legal para
     que no compitan con el texto y queden donde la mano ya está al terminar
     de leer. */
  .pubfoot-acciones { display: flex; gap: 10px; margin-top: 20px;
    flex-wrap: wrap; }
  .pubfoot-acciones .publink.ghost { border-color: var(--border2);
    color: var(--txt2); }
  .pubfoot-acciones .publink.ghost:hover { border-color: var(--accent);
    color: var(--accent); }
  .pubfoot-legal { margin: 32px 0 0; padding-top: 18px;
    border-top: 1px solid var(--border2); font-size: 12px;
    color: var(--muted2); line-height: 1.6; }

  @media (max-width: 900px) {
    body.pubbody .pubhero { grid-template-columns: 1fr; padding: 40px 0 32px; }
    .pubhero h1 { font-size: 30px; }
    .pubfoot-grid { grid-template-columns: 1fr 1fr; }
  }

  @media (max-width: 620px) {
    /* Con "Escribinos" son tres controles al lado del wordmark, y en 360px el
       contenido de la barra llegaba a 451: el documento entero se arrastraba de
       costado. Es el mismo problema que ya tuvo el encabezado del dashboard y se
       resuelve igual, envolviendo. La diferencia es que acá el separador no se
       esconde sino que se convierte en el corte: con flex-basis del 100% ocupa
       el renglón entero y empuja a los tres controles juntos abajo, en vez de
       dejar solo a "Ingresar" colgando en la segunda fila.
       Medido después: 92px de barra y 313 de contenido, que entra hasta en
       320px. No se saca ningún control porque ninguno sobra: el de tema es la
       única forma de contradecir al sistema operativo, y "Escribinos" es el
       único canal de contacto que no obliga a scrollear la página entera.

       El corte va condicionado con :has() porque el botón de contacto solo
       existe en la portada: /sugerencias y /sugerencias/gracias tienen el
       wordmark y "Ingresar", que entran de sobra en un renglón, y forzarles la
       segunda fila era regalar 31px de barra fija a cambio de nada. El costo de
       :has() que documenta la grilla no aplica acá —es un elemento, no 30.115
       celdas que se repintan—.
       Se pregunta por el href y no por la clase: cuál de los dos botones va
       hueco es una decisión de jerarquía que ya cambió una vez, y el corte de
       la barra no depende de eso sino de si hay tres controles o dos. */
    body.pubbody .pubtop { flex-wrap: wrap; gap: 8px; padding: 10px 20px; }
    .pubtop:has(a[href="/sugerencias"]) .grow { flex-basis: 100%; height: 0; }
    .pubcat-grid { grid-template-columns: 1fr; }
    body.pubbody .pubhero { padding: 32px 0 26px; }
    .pubhero h1 { font-size: 25px; }
    .pubhero-lead { font-size: 15px; }
    /* La placa pierde el radio grande y casi todo el padding: a 360px, 26px
       de aire a cada lado se comen el 15% del ancho del gráfico. */
    .pubft { padding: 4px 12px; border-radius: 14px; }
    /* A 360px el botón al lado del texto deja 90px para la frase. Se apila y
       ocupa el ancho: es el único control de la franja, no compite con nada. */
    .pubcta { padding: 24px 20px; margin-top: 34px; }
    .pubcta > a { width: 100%; text-align: center; }
    .pubfoot { padding: 30px 20px 26px; }
    .pubfoot-grid { grid-template-columns: 1fr; gap: 22px; }
    .pubbars { height: 150px; gap: 2px; }
    .pubbar .v { display: none; }
    /* Con veintidós barras en 320px útiles cada una mide 12px y solo entra el
       día. El mes se pierde y las fechas quedan ambiguas donde la ventana cruza
       de julio a agosto; el subtítulo y el período de arriba lo desambiguan, y
       el eje se lee como una secuencia, que es para lo que está. */
    .pubbar .k { font-size: 9px; }
    .pubbar .k .yr { display: none; }
    /* Las reglas de arriba son para las veintidás barras del calendario, que
       en el teléfono no tienen lugar para el valor. Con cinco sí lo hay, y el
       porcentaje es justamente lo que da sentido a la altura. */
    .pubbars--pocas { gap: 10px; }
    .pubbars--pocas .pubbar .v { display: block; font-size: 11px; }
    .pubbars--pocas .pubbar .k { font-size: 12px; }
    .pubtot-row { grid-template-columns: 80px 1fr 46px; gap: 4px 8px; }
    .pubtot-key { gap: 2px 10px; font-size: 10px; }
    .pubrow { grid-template-columns: 52px 1fr 92px; gap: 8px; }
    .pubrow--wide { grid-template-columns: 110px 1fr 64px; }
    /* Mismo motivo que en escritorio, con el reparto que entra en 360px. */
    .pubrows--excl .pubrow--wide { grid-template-columns: 100px 1fr 96px; }
    .pubrow--band { grid-template-columns: 46px 1fr 104px; }
    /* En 320px útiles las 53 barras del histograma miden 5px y una etiqueta de
       dos dígitos ocupa nueve: aunque salga una cada diez años, se desborda
       sobre las barras vecinas, que la tapan por orden de pintado. Se saca el
       eje entero; el subtítulo ya dice de qué edad a qué edad va. */
    .pubbars--anios { height: 130px; }
    .pubbars--anios .pubbar .k { display: none; }
    .pubunbanked .n { font-size: 30px; }
    .pubunbanked .range { font-size: 14px; }
    .pubft-card h1 { font-size: 21px; }
    .pubft-head { gap: 8px; }
    .pubdelta-btn { font-size: 11px; padding: 3px 8px; }
    .pubft-tabs a { padding: 9px 12px; font-size: 12px; }
  }

  /* ================================================================
     VISTA DE FICHA: reemplazo de la tabla en pantallas angostas
     ================================================================
     Por debajo de 780 px, las 19 columnas de la tabla miden ~2000 px
     y el dato central (el nombre del socio) queda fuera de la pantalla
     sin scrollear. La tabla se oculta y en su lugar se dibuja una lista
     de fichas (#card-list) donde cada fila del JS se convierte en un
     bloque apilado con la jerarquia correcta para decidir desde un
     telefono: nombre primero, score visible, sociedad como contexto.

     Lo que NO esta disponible en modo ficha:
     - Modo seleccion multiple (casillas): operar sobre lotes implica
       ver varias filas a la vez con su estado; en una lista de fichas
       que hace scroll no se puede mantener el contexto. Se oculta el
       toggle con CSS y el JS lo detecta con isNarrow().
     - Modo edicion (cel-edit flotante): el editor inline se posiciona
       sobre una celda con coordenadas de tabla; en fichas no hay celdas
       y el popup no tiene donde anclar. Se oculta #toolEdit en la barra
       lateral con CSS media query.
     - El colgroup y las columnas pegadas (.pin, --pin0, --pin1) no se
       aplican a las fichas: no hay tabla que los necesite y el CSS de
       la tabla no se dibuja. No es necesario resetearlos; simplemente
       no existen en el contexto de las fichas.

     El modo "Agrupar por sociedad" SI funciona: se muestra un encabezado
     de grupo antes del bloque de fichas de cada sociedad, con el mismo
     toggle que en la tabla. Los grupos arrancan cerrados como en la tabla.

     Que columnas aparecen en la ficha: exactamente las que devuelve
     visibleColumns(), respetando hiddenByMe y hiddenByAdmin. Si el dev
     apago "Situacion", la ficha tampoco la muestra. La funcion
     buildCardRow() en dashboard.js itera visibleColumns() igual que
     rowEl() lo hace para la tabla. */

  @media (max-width: 780px) {
    /* #drawer es off-canvas por translateX(100%): cerrado, su borde
       izquierdo cae a la derecha del viewport pero sigue existiendo ahí, y
       el motor lo cuenta para el scroll horizontal del documento. En
       escritorio hay algo real que scrollear de costado (la tabla, ver el
       comentario de .tablewrap unas líneas abajo), así que esto va adentro
       de este media query y no en html a secas: acá la tabla ya está
       oculta (display: none) y no hay nada legítimo que tapar.
       Va en html y no en body: body no es el elemento que scrollea la
       página (html lo es, ver document.scrollingElement), y ponerle
       overflow a body le arma su propia caja de scroll separada del
       viewport, que es justo el contenedor contra el que se calcula
       .testbar (position: sticky). Con el overflow en html el viewport
       sigue siendo el mismo de siempre y el sticky no se entera de nada. */
    html { overflow-x: hidden; }
    /* El encabezado era una sola fila sin envolver: medido en un viewport de
       390 px, su contenido llegaba a 642 px, así que "Salir" y el botón de tema
       quedaban fuera de la pantalla y el documento entero se podía arrastrar de
       costado. El separador flexible es lo que empuja: sin él los controles
       caen solos a una segunda fila. */
    header { flex-wrap: wrap; gap: 8px; padding: 10px 12px; }
    header .grow { display: none; }
    header h1, header .hdtitulo { font-size: 15px; flex: 1 1 auto; min-width: 0; }
  /* El rótulo del encabezado cuando NO es el título del documento: mismo
     aspecto que el h1 que reemplaza, sin ser un encabezado para el lector de
     pantalla. */
  header .hdtitulo { font-weight: 700; margin: 0; }
    .panellink { padding: 5px 9px; font-size: 12px; }
    /* /dev reparte columnas y perfiles sobre una tabla ancha: no es una pantalla
       que se pueda usar con el pulgar, y ocupa el lugar de lo que sí. Sigue
       alcanzable desde el menú de herramientas. */
    #devLink { display: none; }

    /* La tabla y su wrap desaparecen; las fichas toman su lugar. */
    .tablewrap { display: none; }
    /* Y con la tabla se va la leyenda, que explica el color de unas columnas
       que en esta pantalla no existen: la ficha no tiene bandas. El control
       "Leyenda" (pc-leyenda) es el que la despliega desde el 2026-09-17; se
       oculta entero y no solo el contenido, para no dejar un botón muerto. */
    .famkey { display: none; }
    .pc-leyenda { display: none; }
    #card-list { display: flex; flex-direction: column; gap: 0; }

    /* Ocultar controles que no funcionan en pantalla angosta. */
    #selectToggle { display: none !important; }
    #toolEdit { display: none !important; }

    /* Cada ficha es una tarjeta con borde inferior. No usa border-radius
       lateral: la lista se lee como una sola columna continua y las
       tarjetas con radio en los cuatro lados se leen como objetos
       independientes, lo que hace mas lento el escaneo vertical. */
    .mob-card { background: var(--card); border-bottom: 1px solid var(--border2);
      padding: 12px 16px; position: relative; }
    .mob-card.tomada { background: var(--tomada-bg); }
    /* Nombre del socio: el dato por el que existe la pantalla. Grande,
       primero, sin color muted: es la informacion principal. */
    .mob-name { font-size: 16px; font-weight: 700; color: var(--txt);
      line-height: 1.3; margin-bottom: 4px; }
    /* El score va al lado del nombre porque define el orden de la lista.
       Sin el score el ejecutivo no sabe por que este socio esta arriba. */
    .mob-score { display: inline-flex; align-items: center; gap: 5px;
      font-size: 13px; font-weight: 600; color: var(--txt); margin-bottom: 8px; }
    /* La sociedad es contexto: mas chica y con color muted para que el
       ojo llegue al nombre antes que al nombre de la empresa. */
    .mob-society { font-size: 13px; color: var(--muted); margin-bottom: 2px; }
    .mob-type { font-size: 11px; color: var(--muted2); margin-bottom: 8px; }
    /* La informacion bancaria se agrupa en una linea de chips: segmento,
       situacion BCRA y relacion con el banco. Van en una sola linea porque
       son tres datos que se leen de un vistazo para decidir si se llama. */
    .mob-bank { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px;
      align-items: center; }
    .mob-bank .sit { font-size: 12px; font-weight: 600; }
    /* El control de gestion va al pie de la ficha: es la unica accion que
       el ejecutivo hace desde la lista. El boton de detalle (drawer) va
       al lado porque cierre y notas viven ahi. */
    .mob-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
    .mob-actions select { flex: 1; min-width: 0; padding: 6px 8px;
      font-size: 13px; }
    /* Los botones de acción en la ficha tienen que llegar a los 44×44 px que
       marca la guía táctil de iOS/Android. button.small hereda 4px de padding
       vertical, que con la fuente de 12px da ~28px de alto: muy por debajo.
       El ancho pide su propia regla porque estos dos botones son un "..." y un
       "☰": con el padding solo quedaban en 36px, y lo que se toca acá es un
       glifo de un caracter, no una palabra. Se sobreescribe solo dentro de
       .mob-actions para no alterar el resto de la interfaz, donde .small sí
       está pensado para escritorio. */
    .mob-actions button { min-height: 44px; min-width: 44px; padding: 10px 12px; }
    /* La ficha del grupo en modo agrupado. El mismo token de color que
       tr.grp en la tabla, para que el ojo reconozca el patron. */
    .mob-grp { background: var(--row-alt); border-bottom: 1px solid var(--border);
      padding: 10px 16px; cursor: pointer; }
    .mob-grp-name { font-weight: 700; font-size: 14px; }
    .mob-grp-meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
    /* Fichas hijas del grupo: arrancan ocultas, igual que tr.child. */
    .mob-child { display: none; }
    .mob-child.show { display: block; }
    /* Indentacion visual para distinguir que la ficha es un hijo del grupo.
       No es padding en el card entero porque el borde izquierdo de la
       tarjeta tiene que llegar al borde de la pantalla. */
    .mob-child .mob-card { padding-left: 24px; }
    /* La nota de conteo y el paginador siguen visibles: van abajo de #card-list
       igual que debajo de la tabla. No necesitan reglas extra. */
  }

  /* En pantallas anchas, #card-list no existe en el DOM hasta que el JS lo
     crea, pero si existiera no tiene que aparecer. Se oculta por las dudas. */
  @media (min-width: 781px) {
    #card-list { display: none; }
  }

  .barra { position: fixed; top: 0; left: 0; bottom: 0; width: var(--barra-w, 220px);
    background: var(--bg2); border-right: 1px solid var(--border);
    z-index: 45; display: flex; flex-direction: column;
    transition: width .15s ease; overflow: hidden; }
  body.conbarra { padding-left: var(--barra-w, 220px); transition: padding-left .15s ease; }
  html.barra-cerrada .barra { width: 56px; }
  html.barra-cerrada body.conbarra { padding-left: 56px; }
  /* Mientras se arrastra el tirador (THEME_BOOT_JS) se saca la transición:
     con ella puesta, cada evento de mousemove competía con una animación de
     150ms y el ancho se veía a los saltos en vez de seguir al mouse. */
  html.barra-arrastrando .barra,
  html.barra-arrastrando body.conbarra,
  html.barra-arrastrando #sidebar { transition: none !important; }
  /* El cartel "NO ES PRODUCCIÓN" vive abajo a la izquierda y tapaba el botón
     Salir de la barra: se corre a la derecha de la barra, abierta o cerrada. */
  body.conbarra .envguard b { left: calc(var(--barra-w, 220px) + 12px); }
  html.barra-cerrada body.conbarra .envguard b { left: 68px; }

  .sr-only { position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
    border: 0; }

  .barra-top { display: flex; align-items: center; gap: 6px; padding: 14px 10px;
    border-bottom: 1px solid var(--border); }
  .barra-brand { display: flex; align-items: center; gap: 8px; flex: 1;
    min-width: 0; color: var(--txt); text-decoration: none; }
  .barra-logo { flex: none; }
  .barra-brand-txt { font-size: calc(var(--fs-base) - 1px); font-weight: 600; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; }
  html.barra-cerrada .barra-brand-txt { display: none; }
  .barra-toggle { flex: none; background: transparent; border: 1px solid var(--border3);
    color: var(--muted); border-radius: 6px; padding: 5px; cursor: pointer;
    display: flex; align-items: center; justify-content: center; }
  .barra-toggle:hover, .barra-toggle:focus-visible { color: var(--txt); border-color: var(--muted); }
  /* Colapsada, la marca (logo + texto) desaparece entera y no solo el texto:
     los 56px de cabecera no alcanzan para la marca y el botón sin que se
     pisen. Queda solo el botón, centrado. */
  html.barra-cerrada .barra-top { justify-content: center; }
  html.barra-cerrada .barra-brand { display: none; }

  /* Tirador del ancho de la barra: 6px sobre el borde derecho, arrastrable
     con mouse y con flechas de teclado (THEME_BOOT_JS). Colapsada no hay
     ancho que elegir, así que se oculta con la barra angosta. */
  .barra-resize { position: absolute; top: 0; right: 0; bottom: 0; width: 6px;
    cursor: col-resize; background: transparent; touch-action: none; }
  .barra-resize:hover, .barra-resize:focus-visible { background: var(--accent); opacity: .5; }
  .barra-resize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  html.barra-cerrada .barra-resize { display: none; }

  /* Foco visible parejo para todo lo que vive en esta barra: sin esto, cada
     control dependía de que el navegador le pusiera un anillo por su cuenta,
     y en algunos (los botones .barra-radio, por ejemplo) el default del
     navegador desaparecía detrás del propio fondo del botón. */
  .barra :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

  .barra-nav { display: flex; flex-direction: column; gap: 2px; padding: 10px 8px;
    flex: 1; overflow-y: auto; overflow-x: hidden; }
  .barra-link { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
    border-radius: 8px; color: var(--txt2); text-decoration: none;
    font-size: calc(var(--fs-base) - 1px); white-space: nowrap; }
  .barra-link:hover { background: var(--row-hover); color: var(--txt); }
  /* Acá el peso SÍ se puede cambiar con el estado y no rompe nada: cada
     enlace ocupa el ancho completo de la barra, así que el texto puede
     engordar sin correr a nadie. Es la excepción al criterio de .tab y
     .barra-radio, y por eso queda escrito. */
  .barra-link.on { background: var(--sel-bg); color: var(--accent); font-weight: 600; }
  .barra-ic { flex: none; width: 18px; height: 18px; }
  /* Colapsada, el texto de cada link se saca de la vista pero no del árbol de
     accesibilidad: con display:none el nombre accesible del link caía en el
     "title" del <a> (barra-link, barra-tool, barra-salir), que un lector de
     pantalla no siempre anuncia igual que el contenido. Con .sr-only el link
     conserva su nombre sin depender de eso. */
  html.barra-cerrada .barra-txt { position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
    white-space: nowrap; border: 0; }
  html.barra-cerrada .barra-link { justify-content: center; }

  /* "Más herramientas" y "Configuración": mismo grupo visual que .sidegroup
     adentro del cajón flotante (views.ts), con su propio prefijo porque viven
     en un archivo aparte. Un separador arriba (border-top) porque el grupo
     anterior es la navegación entre pantallas, y esto es otra cosa: acciones
     dentro de la pantalla en la que ya se está. */
  .barra-group { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted2); font-weight: 600; margin: 14px 0 4px; padding: 8px 10px 0;
    border-top: 1px solid var(--border); }
  .barra-tool { display: flex; align-items: center; gap: 10px; padding: 7px 10px;
    border-radius: 8px; color: var(--txt2); text-decoration: none;
    font-size: calc(var(--fs-base) - 1px); white-space: nowrap;
    justify-content: space-between; }
  .barra-tool:hover { background: var(--row-hover); color: var(--txt); }
  /* Colapsada, la barra vuelve a ser solo los cuatro íconos de siempre: estos
     dos grupos no tienen ícono propio y un texto que desaparece dejaría un
     link vacío, así que se ocultan enteros. Se recuperan al reabrir la barra. */
  html.barra-cerrada .barra-group,
  html.barra-cerrada .barra-tool,
  html.barra-cerrada .barra-config { display: none; }

  .barra-config { border-top: none; }
  .barra-config > summary { cursor: pointer; list-style: none; padding: 7px 10px;
    border-radius: 8px; color: var(--txt2); font-size: calc(var(--fs-base) - 1px); display: flex;
    align-items: center; justify-content: space-between; }
  .barra-config > summary::-webkit-details-marker { display: none; }
  .barra-config > summary::after { content: "+"; color: var(--muted2); }
  .barra-config[open] > summary::after { content: "−"; }
  .barra-config > summary:hover { background: var(--row-hover); color: var(--txt); }
  .barra-config-body { padding: 8px 10px 4px; display: flex; flex-direction: column;
    gap: 12px; }
  .barra-setlabel { display: block; font-size: 11px; color: var(--muted2);
    margin-bottom: 6px; }
  .barra-radiogroup { display: flex; flex-wrap: wrap; gap: 6px; }
  /* El peso va SIEMPRE en 600, marcado o no. Estaba solo en el estado
     marcado, y la negrita mide más ancho que el texto normal: al cambiar de
     "Oscuro" a "Claro" el que se prendía se ensanchaba, el que se apagaba se
     angostaba, y los tres botones del grupo se reacomodaban. Un cambio de
     estado no puede cambiar las métricas del texto.
     No se pierde nada: el marcado ya se distingue por borde, color y fondo
     —tres señales—, así que la cuarta era redundante y encima era la única
     que movía la interfaz. */
  .barra-radio { font: inherit; font-size: calc(var(--fs-base) - 2px);
    font-weight: 600; cursor: pointer;
    background: var(--bg2); border: 1px solid var(--border3); color: var(--txt2);
    border-radius: 6px; padding: 5px 9px; }
  .barra-radio:hover { border-color: var(--muted); color: var(--txt); }
  .barra-radio[aria-checked="true"] { border-color: var(--accent); color: var(--accent);
    background: var(--sel-bg); }
  .barra-radio:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  /* Los cuatro círculos de acento son un color fijo de muestra, no --accent:
     tienen que verse los cuatro a la vez sin importar cuál esté elegido. El
     botón sigue siendo ".barra-radio" (mismo elemento, mismo comportamiento
     de teclado); ".sw-*" le pone la forma y el color, con la misma
     especificidad que ".barra-radio[aria-checked]" (una clase + un atributo)
     para no perder el color propio cuando el círculo elegido es "Ámbar". */
  .barra-swatches { gap: 8px; }
  .barra-radio.sw-ambar, .barra-radio.sw-azul,
  .barra-radio.sw-verde, .barra-radio.sw-violeta {
    width: 22px; height: 22px; padding: 0; border-radius: 999px;
    border: 2px solid var(--border3); cursor: pointer;
  }
  .barra-radio.sw-ambar[aria-checked="true"], .barra-radio.sw-azul[aria-checked="true"],
  .barra-radio.sw-verde[aria-checked="true"], .barra-radio.sw-violeta[aria-checked="true"] {
    border-color: var(--txt);
  }
  .barra-radio.sw-ambar { background: #e8b25c; }
  .barra-radio.sw-azul { background: #3d7dd8; }
  .barra-radio.sw-verde { background: #2f9e63; }
  .barra-radio.sw-violeta { background: #8b5cf6; }
  /* En claro, los cuatro tonos de arriba quedan entre 1,9:1 y 4,2:1 contra el
     blanco de la barra: el ámbar y el azul no llegan al 3:1 que pide un
     control gráfico (WCAG 1.4.11). Se usa el mismo acento oscuro que ya
     aplica cada tema cuando ese color está elegido (:root.light[data-accent]
     más arriba), que ya está medido contra blanco. */
  :root.light .barra-radio.sw-ambar { background: #8a5a12; } /* 5,91:1 */
  :root.light .barra-radio.sw-azul { background: #1d6cc8; } /* 5,21:1 */
  :root.light .barra-radio.sw-verde { background: #1f7a4c; } /* 5,32:1 */
  :root.light .barra-radio.sw-violeta { background: #6b3fc0; } /* 6,78:1 */

  .barra-bottom { border-top: 1px solid var(--border); padding: 10px 10px;
    display: flex; flex-direction: column; gap: 8px; }
  .barra-help { font-size: calc(var(--fs-base) - 1px); color: var(--txt2);
    text-decoration: none; }
  .barra-help:hover { color: var(--txt); text-decoration: underline; }
  html.barra-cerrada .barra-help { display: none; }
  .barra-who { font-size: calc(var(--fs-base) - 2px); color: var(--muted); overflow: hidden;
    white-space: nowrap; text-overflow: ellipsis; }
  html.barra-cerrada .barra-who { display: none; }
  .barra-logout { margin: 0; }
  /* Enlace, pero con el mismo peso visual que tenía como texto: lo que ofrece
     está a un clic y no debería gritar más que "Salir", que es el control real
     de este pie. */
  a.barra-who { text-decoration: none; }
  a.barra-who:hover { color: var(--txt); text-decoration: underline; }
  .barra-salir { width: 100%; display: flex; align-items: center; gap: 8px;
    background: transparent; border: 1px solid var(--border3); color: var(--txt2);
    border-radius: 8px; padding: 7px 10px; cursor: pointer;
    font-size: calc(var(--fs-base) - 1px); font-family: inherit; }
  .barra-salir:hover { color: var(--txt); border-color: var(--muted); }
  html.barra-cerrada .barra-salir { justify-content: center; }

  /* Con el dedo, 22px de círculo (o los ~30px de un .barra-radio de texto) es
     un blanco chico y fácil de tocar al lado: 44px es el mínimo de WCAG
     2.5.5. El círculo visible crece a 32px y el resto del blanco de toque
     (hasta 44px) lo pone el padding, con el fondo recortado al contenido para
     que no se vea más grande de lo que es. */
  @media (pointer: coarse) {
    .barra-radio { min-height: 44px; padding: 10px 14px; }
    .barra-radio.sw-ambar, .barra-radio.sw-azul,
    .barra-radio.sw-verde, .barra-radio.sw-violeta {
      width: 32px; height: 32px; padding: 6px; background-clip: content-box;
    }
  }

  /* Sin JS: bajo los 900px la barra vive colapsada, para no comerse la
     mitad de una pantalla de teléfono. Se fuerza el ancho angosto sin tocar
     la clase de localStorage, así que al volver a un monitor grande la
     barra recupera el estado que el usuario había elegido. */
  @media (max-width: 900px) {
    .barra { width: 56px; }
    body.conbarra { padding-left: 56px; }
    #sidebar { left: 56px; }
    .barra-top { justify-content: center; }
    .barra-brand { display: none; }
    .barra-brand-txt, .barra-who { display: none; }
    /* Mismo criterio que arriba (html.barra-cerrada .barra-txt): sr-only y no
       display:none, para no perder el nombre accesible de los links. */
    .barra-txt { position: absolute; width: 1px; height: 1px; padding: 0;
      margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
      border: 0; }
    .barra-link, .barra-salir { justify-content: center; }
    .barra-toggle { display: none; }
    .barra-group, .barra-tool, .barra-config, .barra-help { display: none; }
    .barra-resize, .herr-resize { display: none; }
  }

  /* --herr-w es el ancho que el usuario dejó guardado la última vez que
     movió el tirador (hasta 640px, ver RESIZERS.herr en views.ts) y viaja
     como variable inline en <html>, así que sigue vivo en el celular aunque
     el tirador ya esté oculto arriba. Con el sidebar fijo en left: 56px
     (regla de arriba), 640px de ancho no entran ni en una tablet de 700px;
     en un teléfono de 390 sobran 300px de flotante fuera de la pantalla.
     Bajo los 600px el ancho se calcula desde el viewport y --herr-w se
     ignora del todo: no tiene sentido "acordarse" de una medida pensada
     para poder ver la tabla al lado, que a este ancho ni existe (ver
     .tablewrap en el media query de 780px). */
  @media (max-width: 600px) {
    #sidebar { width: calc(100vw - 56px); }
  }

  /* El ancho de .barra y el padding de body.conbarra se mueven juntos al
     abrir o cerrar la barra: quien pidió menos movimiento no quiere ese
     deslizamiento de 150ms, aunque el resultado final (abierta o cerrada)
     no cambia. */
  @media (prefers-reduced-motion: reduce) {
    .barra, body.conbarra { transition: none; }
  }


  .pubhoy { margin: 0 0 22px; padding: 18px 22px 12px; background: var(--card);
    border: 1px solid var(--border); border-radius: 14px; }
  .pubhoy-t { font-size: 11.5px; font-weight: 600; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); margin: 0 0 12px; }
  .pubhoy-inds { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
  .pubhoy-ind { min-width: 0; padding: 8px 0; border-top: 2px solid var(--border2); }
  .pubhoy-ind .n { display: block; font-size: 20px; font-weight: 700;
    letter-spacing: -.02em; white-space: nowrap; }
  .pubhoy-ind .l { display: block; font-size: 12px; color: var(--muted);
    margin-top: 2px; }
  .pubhoy-ind .l small { color: var(--muted2); font-size: 11px; }
  .pubhoy-news { margin-top: 14px; }
  .pubhoy-news h2 { font-size: 11.5px; font-weight: 600; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); margin: 0 0 6px; }
  .pubhoy-news ul { list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: 1fr 1fr; column-gap: 24px; }
  .pubhoy-news li { display: flex; justify-content: space-between; gap: 12px;
    align-items: baseline; padding: 7px 0; border-bottom: 1px solid var(--border2);
    font-size: 13.5px; line-height: 1.35; }
  .pubhoy-news a { color: var(--txt); text-decoration: none; min-width: 0; }
  .pubhoy-news a:hover { text-decoration: underline; text-underline-offset: 2px; }
  .pubhoy-news .f { color: var(--muted2); font-size: 11px; white-space: nowrap; }
  .pubhoy-src { font-size: 11px; color: var(--muted2); margin: 10px 0 0; }
  @media (max-width: 760px) {
    .pubhoy-inds { grid-template-columns: repeat(2, 1fr); }
    .pubhoy-news ul { grid-template-columns: 1fr; } }
  @media (max-width: 560px) {
    .pubhoy { padding: 14px 14px 10px; }
    .pubhoy-ind .n { font-size: 17px; } }


  /* Paleta de series. Son colores decorativos, no semánticos: por eso pueden
     cambiar entre temas, al revés que las señales de situación crediticia.
     Los del tema claro son más oscuros porque van sobre blanco.

     La paleta miel NO tocó estos seis, y vale la pena decir por qué porque el
     primer intento sí los tocó. La idea era llevar --c1 a la miel del acento,
     ya que es la serie que más aparece sola; para no dejar dos mieles juntas,
     eso empujaba a --c3 al azul. Pero --c3 no es decorativo aunque este
     comentario lo diga: SIT_COLORS en dashboard.js lo usa como el ámbar de
     "atraso", el escalón del medio entre el verde de "al día" y el rosa de
     "mora". El swap convertía ese escalón en azul y rompía una rampa de
     severidad. Lo único que se movió es --chart-grid, que es estructural y
     tenía que apoyarse en el marino nuevo. */
  :root {
    --c1: #6ea8fe; --c2: #4ec9a5; --c3: #e0b355;
    --c4: #b795f5; --c5: #f28ab2; --c6: #5fc7d9;
    --chart-grid: #1e2c46;
    /* Rampa propia para los gráficos de composición (dona, apilada), separada
       de --c1..--c6. Esos seis son un arcoíris saturado —azul, verde, ámbar,
       violeta, rosa, cian— que en la caja de arena se leía como los colores
       por defecto de una librería y no como este sitio; y no se pueden tocar
       porque --c3 es el ámbar de "atraso" en la grilla y forma parte de una
       rampa de severidad.
       Estos seis son terrosos y apagados, de la familia de la miel del acento,
       y alternan cálido y frío para que dos tramos vecinos de una dona se
       distingan sin gritar. El primero ES el acento: la serie que aparece sola
       queda del color de la marca. */
    --g1: #e8b25c; --g2: #5fbfa8; --g3: #c9834e;
    --g4: #8fa9c9; --g5: #bb9a77; --g6: #9db87e;
  }
  :root.light {
    --c1: #1d6cc8; --c2: #10806a; --c3: #9a6b12;
    --c4: #6d43c4; --c5: #b52a67; --c6: #10707f;
    --chart-grid: #e8ddc6;
    /* Los mismos seis, bajados de luminosidad para que contrasten contra el
       papel claro en vez de contra el marino. */
    --g1: #8a5a12; --g2: #1d6b60; --g3: #a35a28;
    --g4: #3d5f80; --g5: #7d5a3c; --g6: #556b3f;
  }
  .chart { display: block; width: 100%; height: auto; }
  .chart .grid { stroke: var(--chart-grid); stroke-width: 1; }
  .chart .axis { fill: var(--muted2); font-size: 11px;
    font-family: system-ui, sans-serif; }
  .chart .val { fill: var(--txt2); font-size: 11px;
    font-family: system-ui, sans-serif; }

  /* Ranking: la etiqueta es el dato, así que va en HTML y no dentro del SVG. */
  .rank { display: flex; flex-direction: column; gap: 6px; }
  /* 40% y no 30%: con 30 la columna cortaba etiquetas de tres palabras y la
     barra ganaba un espacio que no usa. El corte igual queda recuperable
     porque la fila lleva el texto completo en el title. */
  .rank .row { display: grid; grid-template-columns: minmax(90px, 40%) 1fr auto;
    align-items: center; gap: 10px; }
  .rank .t { font-size: 13px; color: var(--txt2); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
  .rank .track { background: var(--row-alt); border-radius: 5px; height: 18px;
    overflow: hidden; }
  .rank .track > i { display: block; height: 100%; border-radius: 5px; }

  /* Volumen sobre las barras de HTML. Va como `background-image` encima del
     `background-color` que pone la pantalla, y por eso el color inline se
     escribe como `background-color` y no con el atajo `background`: el atajo
     resetea la imagen y el degradé desaparecía sin dar error. */
  .rank .track > i, .stack > i { background-image: linear-gradient(180deg, rgba(255,255,255,.18), rgba(255,255,255,0) 55%, rgba(0,0,0,.12)); }
  .rank .v { font-size: 12px; color: var(--muted); font-variant-numeric:
    tabular-nums; white-space: nowrap; }

  /* Apilada de 100%: una sola barra que se reparte. Se usa para cobertura,
     donde lo que importa es cuánto falta, no cuánto hay. */
  .stack { display: flex; height: 20px; border-radius: 5px; overflow: hidden;
    background: var(--row-alt); }
  .stack > i { display: block; height: 100%; }
  .stack-key { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; }
  .stack-key > span { font-size: 12px; color: var(--muted);
    display: inline-flex; align-items: center; gap: 6px; }
  .stack-key b { width: 9px; height: 9px; border-radius: 2px; display: block; }

  /* Dos columnas: el aro a la izquierda con el total adentro, la leyenda-tabla
     a la derecha. Antes era una columna con el SVG limitado a 180px y la
     leyenda en una fila de puntitos: en el panel de 950px de la caja de arena
     el gráfico quedaba flotando en un vacío enorme y los rótulos no se podían
     comparar entre sí. */
  /* La leyenda tiene un ancho mínimo real (punto, cifra y porcentaje: unos
     150px) y el aro cede antes que ella: en la caja de 362px de la grilla de
     tres de /dev/datos, con el aro fijo en 180px la leyenda quedaba en 36px y
     la página entera scrolleaba de costado (medido el 2026-09-16). */
  .dona { display: grid; grid-template-columns: minmax(110px, 260px) minmax(150px, 1fr);
    gap: 28px 24px; align-items: center; }
  .dona-aro { justify-self: center; width: 100%; max-width: 260px; }
  .dona .chart { width: 100%; height: auto; }
  .dona-n { fill: var(--txt); font-size: 24px; font-weight: 700;
    font-family: system-ui, sans-serif;
    font-variant-numeric: tabular-nums; }
  .dona-t { fill: var(--muted2); font-size: 9px; letter-spacing: .1em;
    text-transform: uppercase; font-family: system-ui, sans-serif; }

  .dona-key { display: grid; align-content: center; }
  /* La cifra y el porcentaje en columnas fijas: sin eso, los números de
     distinto largo bailan y el ojo no puede recorrer la columna. */
  .donafila { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto 52px;
    align-items: center; gap: 10px; padding: 8px 0;
    border-bottom: 1px solid var(--border2); font-size: 13px; }
  .donafila:last-child { border-bottom: 0; }
  .donafila b { width: 10px; height: 10px; border-radius: 3px; }
  .donafila .k { color: var(--txt2); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
  .donafila .v { color: var(--txt); font-weight: 600;
    font-variant-numeric: tabular-nums; }
  .donafila .p { color: var(--muted); text-align: right;
    font-variant-numeric: tabular-nums; }
  .dona .chart-foot { grid-column: 1 / -1; }
  @media (max-width: 620px) {
    .dona { grid-template-columns: 1fr; gap: 18px; }
    .dona-aro { max-width: 200px; }
  }

  /* El denominador. Todo gráfico lleva uno: sin el "sobre cuántos", una barra
     alta y una barra de tres casos se ven igual. */
  .chart-foot { font-size: 12px; color: var(--muted2); margin-top: 8px; }


  /* El contenedor de las siete pantallas dev, definido una sola vez. Hasta el
     2026-09-16 cada archivo traía el suyo con un max-width distinto (900,
     1100, 1200, 1280, 1600) y el contenido pegaba un salto de ancho con cada
     cambio de pestaña. La grilla de columnas (dev-panel) es la única con
     motivo para ser más ancha y lo dice en su propio estilo. */
  .devmain, .mapa { padding: 20px 24px 60px; max-width: 1280px; margin: 0 auto; }
  .panel { background: var(--card); border: 1px solid var(--border);
    border-radius: 12px; padding: 14px 16px; margin-bottom: 18px; }
  .panel > h2 { font-size: 13px; margin: 0 0 2px; text-transform: uppercase;
    letter-spacing: .05em; color: var(--muted); }
  /* max-width y line-height en la base: un párrafo de 200 caracteres sin tope
     de ancho se lee mal en un monitor ancho, sin importar en qué pantalla. */
  .panel > .sub { font-size: 12px; color: var(--muted2); margin-bottom: 12px;
    max-width: 88ch; line-height: 1.5; }
  /* --btn-bg es más claro que --card en oscuro y más gris que blanco en claro:
     el botón se despega del panel por tono, no solo por borde. Con var(--card)
     lo único que lo separaba del panel era el 1px de borde.

     **El selector va acotado a las pantallas dev y eso es el arreglo del
     2026-09-13.** Este bloque declaraba "button" pelado, y desde que el CSS se
     unificó en /css/app.css (app-css.ts) viaja DESPUÉS de STYLE con la misma
     especificidad: le ganaba al botón primario de todo el dashboard. Medido
     antes de tocar: el "Guardar" de la pizarra y el "Lo contacté" de Mi día
     salían en var(--btn-bg) beige en vez del acento —tres botones del lado del
     ejecutivo—, mientras que los 50 de /dev sí querían ese tono. body:has()
     porque las páginas dev comparten body.conbarra con el resto y lo único que
     las distingue en el DOM es su propia barra; misma herramienta que ya usa
     .pubtop para su quiebre. */
  body:has(.devnav) button { font: inherit; cursor: pointer; border-radius: 8px;
    border: 1px solid var(--btn-bdr); background: var(--btn-bg);
    color: var(--txt); padding: 7px 14px; }
  body:has(.devnav) button:hover:not(:disabled) { border-color: var(--muted); }
  /* El realce de arriba es solo para el mouse: sin esto, los botones de las
     seis pantallas de dev quedaban con el contorno por defecto del navegador,
     que sobre el fondo oscuro casi no se ve. Es el mismo contorno que usan la
     barra y el resto de las pantallas. */
  body:has(.devnav) button:focus-visible { outline: 2px solid var(--accent);
    outline-offset: 2px; }


  /* El velo. Oscurece la portada sin taparla: el panel es un paso al costado
     de lo que estabas mirando, no una pantalla nueva. */
  .ing-velo { position: fixed; inset: 0; z-index: 60; background: rgba(12,9,4,.5);
    backdrop-filter: blur(2px); opacity: 0; transition: opacity .24s ease;
    pointer-events: none; }
  .ing-velo.abierto { opacity: 1; pointer-events: auto; }

  .ing-panel { position: fixed; top: 0; right: 0; bottom: 0; width: min(436px, 100vw);
    z-index: 61; background: var(--bg2);
    /* El filete de la marca, en el canto que da a la portada: el mismo gesto
       que las tarjetas de "Qué hay acá adentro", girado. Arriba —donde lo
       tienen las tarjetas— quedaba pegado al borde de la ventana y no se veía;
       de este lado se lee entero y además separa el panel de lo que tapa. */
    border-left: 3px solid var(--accent);
    box-shadow: -20px 0 50px rgba(12,9,4,.22);
    display: flex; flex-direction: column; overflow-y: auto;
    /* La serif de titulares se declara acá y no se hereda: el panel también
       vive en /login, donde no hay body.pubbody que la defina. */
    --display: Georgia, "Iowan Old Style", Charter, "Bitstream Charter",
      "Liberation Serif", serif;
    transform: translateX(100%);
    /* visibility además del translate: fuera de pantalla los controles siguen
       siendo tabulables, y con Tab desde la portada se llegaba a un formulario
       invisible. Se demora al cerrar para no cortar la salida. */
    visibility: hidden;
    transition: transform .3s cubic-bezier(.22,.9,.3,1), visibility 0s .3s; }
  .ing-panel.abierto { transform: translateX(0); visibility: visible;
    transition: transform .3s cubic-bezier(.22,.9,.3,1), visibility 0s; }

  .ing-cuerpo { position: relative; padding: 16px 32px 28px; flex: 1;
    display: flex; flex-direction: column; }
  /* El paso ocupa el alto que sobra, y eso es lo que manda el pie al fondo del
     panel: sin esto, margin-top:auto computa 0 —el padre es block— y el aviso
     legal queda pegado al último botón, a media pantalla. */
  .ing-paso { display: flex; flex-direction: column; flex: 1; }
  .ing-barra { display: flex; align-items: center; gap: 4px; margin: 0 -8px 20px; }
  .ing-barra .sep { flex: 1; }
  /* 44px de área táctil: son los dos únicos controles del borde superior y en
     el teléfono se aprietan con el pulgar. */
  .ing-icono { width: 44px; height: 44px; display: grid; place-items: center;
    background: none; border: 0; border-radius: 10px; color: var(--muted);
    cursor: pointer; padding: 0; transition: background .15s, color .15s; }
  .ing-icono:hover { background: var(--row-hover); color: var(--txt); }
  .ing-icono svg { width: 19px; height: 19px; }

  /* La marca, en el tamaño del pie y no en el del encabezado: acá no es el
     título de la pantalla, es la firma de quién pregunta. */
  .ing-marca { margin-bottom: 28px; }
  .ing-marca .pubmark { font-size: 17px; }

  .ing-eyebrow { font-size: 10.5px; font-weight: 600; letter-spacing: .14em;
    text-transform: uppercase; color: var(--muted2); margin: 0 0 10px; }
  .ing-titulo { font-family: var(--display); font-weight: 400;
    font-size: 29px; line-height: 1.14; letter-spacing: -.015em;
    margin: 0 0 8px; color: var(--txt); }
  .ing-bajada { color: var(--txt2); font-size: 13.5px; margin: 0 0 24px;
    line-height: 1.6; max-width: 34ch; }
  .ing-bajada a, .ing-pie a { color: var(--accent); text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
    transition: border-color .15s; }
  .ing-bajada a:hover, .ing-pie a:hover { border-bottom-color: var(--accent); }

  /* Quién dijo ser, en el segundo paso. Ficha y no campo: ya no se edita acá
     —para eso está la flecha— y como input invitaría a corregirlo justo donde
     no se puede. Filete arriba y abajo, como los renglones del hero. */
  .ing-quien { display: flex; align-items: baseline; gap: 12px;
    margin: 0 0 24px; padding: 11px 0;
    border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
    font-size: 13.5px; }
  .ing-quien b { font-weight: 600; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; }
  .ing-quien button { margin-left: auto; background: none; border: 0; padding: 0;
    color: var(--accent); font-size: 12.5px; cursor: pointer; font-weight: 600;
    font-family: inherit; }
  .ing-quien button:hover, .ing-quien button:focus-visible { text-decoration: underline; }

  /* Etiqueta visible arriba de cada campo, como la del buscador de la portada.
     El placeholder solo no alcanza: al escribir desaparece, y en un formulario
     de tres campos iguales el que se distrajo ya no sabe cuál es cuál. */
  .ing-campo { position: relative; margin-bottom: 16px; }
  .ing-campo > label { display: block; font-size: 12px; font-weight: 600;
    color: var(--txt2); margin-bottom: 6px; }
  .ing-campo input { width: 100%; box-sizing: border-box;
    min-height: var(--btn-h-lg);
    padding: 0 14px; background: var(--card); color: var(--txt);
    border: 1px solid var(--border3); border-radius: var(--btn-r-lg);
    font-size: var(--btn-fs-lg);
    font-family: inherit; transition: border-color .15s, box-shadow .15s; }
  /* El textarea comparte todo con el input menos el alto: sin esto salía con
     la fuente del navegador y un ancho propio, y en la página de baja quedaba
     una caja angosta al lado de campos anchos. */
  .ing-campo textarea { width: 100%; box-sizing: border-box; min-height: 108px;
    padding: 11px 13px; background: var(--card); color: var(--txt);
    border: 1px solid var(--border3); border-radius: 8px; font-size: 14.5px;
    font-family: inherit; line-height: 1.5; resize: vertical;
    transition: border-color .15s, box-shadow .15s; }
  .ing-campo textarea:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  .ing-campo input::placeholder,
  .ing-campo textarea::placeholder { color: var(--muted2); }
  /* El anillo va con box-shadow y no con outline: outline no sigue el
     border-radius en todos los navegadores y quedaba un rectángulo sobre un
     campo redondeado. */
  .ing-campo input:focus { outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  .ing-campo.conojo input { padding-right: 46px; }
  .ing-ojo { position: absolute; right: 2px; bottom: 1px;
    width: 42px; height: 42px; display: grid; place-items: center;
    background: none; border: 0; border-radius: 7px; color: var(--muted2);
    cursor: pointer; padding: 0; transition: color .15s; }
  .ing-ojo:hover, .ing-ojo:focus-visible { color: var(--txt); }
  .ing-ojo svg { width: 18px; height: 18px; }

  /* text-decoration: none porque el botón de Google es un <a> —es una
     navegación a otro sitio, no el envío de este formulario— y el navegador
     le subraya el texto. El resto de las propiedades ya venían por la clase. */
  /* La escala del sitio (views.ts, --btn-*): es la acción principal de la
     pantalla, así que lg. Tenía 44px propios, que quedaban 4px por debajo de
     todo lo demás. */
  .ing-btn { width: 100%; min-height: var(--btn-h-lg);
    border-radius: var(--btn-r-lg); font-size: var(--btn-fs-lg);
    font-weight: 600; margin-top: 8px; border: 0; cursor: pointer;
    font-family: inherit; background: var(--accent); color: var(--on-accent);
    letter-spacing: .005em; text-decoration: none;
    transition: background .15s, transform .1s; }
  /* Un <a> no acepta box-sizing del botón: sin esto, el de Google mide 44px de
     alto más el padding y queda un pelo más grande que el de arriba. */
  a.ing-btn { box-sizing: border-box; }
  .ing-btn:hover { background: var(--accent2); }
  .ing-btn:active { transform: translateY(1px); }
  .ing-btn.sec { background: transparent; color: var(--txt);
    border: 1px solid var(--border3); display: flex; align-items: center;
    justify-content: center; gap: 9px; }
  .ing-btn.sec:hover { background: var(--row-hover); border-color: var(--accent);
    color: var(--accent); }
  .ing-btn.sec svg { width: 17px; height: 17px; }
  /* Apagado pero legible: el botón tiene que seguir diciendo qué va a hacer
     cuando se prenda. Baja el fondo, no el texto. */
  .ing-btn[disabled] { cursor: default;
    background: color-mix(in srgb, var(--accent) 30%, var(--bg2));
    color: var(--muted2); }
  .ing-btn[disabled]:hover { background: color-mix(in srgb, var(--accent) 30%, var(--bg2)); }
  .ing-btn[disabled]:active { transform: none; }

  /* "o bien" con el filete a los costados. El pseudo-elemento evita los dos
     span vacíos que harían falta si no. */
  .ing-obien { display: flex; align-items: center; gap: 12px; margin: 22px 0 14px;
    color: var(--muted2); font-size: 11px; letter-spacing: .1em;
    text-transform: uppercase; }
  .ing-obien::before, .ing-obien::after { content: ""; flex: 1; height: 1px;
    background: var(--border); }

  /* Los requisitos de la clave, como la ficha de cifras del hero: rótulo
     arriba, un renglón por requisito, filete entre renglones. Se lee de un
     barrido mientras se escribe y no compite con los campos. */
  .ing-reqs { margin: 0 0 18px; font-size: 12.5px; }
  .ing-reqs .t { display: block; font-size: 10.5px; font-weight: 600;
    letter-spacing: .12em; text-transform: uppercase; color: var(--muted2);
    margin-bottom: 8px; }
  .ing-reqs ul { list-style: none; margin: 0; padding: 0;
    border-top: 1px solid var(--border2); }
  .ing-reqs li { display: flex; align-items: center; gap: 9px;
    padding: 6px 0; border-bottom: 1px solid var(--border2);
    color: var(--muted); transition: color .2s ease; }
  .ing-reqs svg { width: 14px; height: 14px; flex: none; }
  /* Cumplido toma el color del acento, que en este tablero ES el color de
     "listo". Un verde nuevo para cuatro renglones agregaría una familia de
     color que no existe en ninguna otra pantalla. */
  .ing-reqs li.ok { color: var(--txt2); }
  .ing-reqs li.ok svg { color: var(--accent); }
  .ing-reqs .aviso { display: block; margin-top: 8px; color: var(--muted); }

  .ing-err { background: color-mix(in srgb, #e5484d 12%, transparent);
    border-left: 3px solid #e5484d; color: var(--txt);
    border-radius: 0 6px 6px 0; padding: 9px 12px; font-size: 13px;
    margin-bottom: 16px; line-height: 1.5; }

  /* El pie, separado por filete y anclado abajo. Es donde viven el aviso legal
     y la salida para el que no se acuerda la clave. */
  .ing-pie { margin-top: auto; padding-top: 22px; border-top: 1px solid var(--border);
    color: var(--muted2); font-size: 11.5px; line-height: 1.65; }
  .ing-pie + .ing-pie { margin-top: 0; border-top: 0; padding-top: 6px; }
  /* El color NO cambia entre reposo y hover: lo que cambia es el subrayado.
     Romen lo señaló el 2026-09-12 ("cuando pongo olvidé la clave cambia de
     color"): este enlace nacía gris apagado y se ponía casi negro al pasar el
     mouse, mientras todos los demás enlaces del sitio son del color de acento.
     Eran dos problemas en uno —no parecía un enlace, y cambiaba de familia de
     color al tocarlo—. */
  .ing-olvide { display: inline-block; font-size: 13px; color: var(--accent);
    text-decoration: none; border-bottom: 1px solid transparent; }
  .ing-olvide:hover, .ing-olvide:focus-visible { border-bottom-color: var(--accent); }

  /* La entrada escalonada. Un solo momento coreografiado —el panel que entra y
     el contenido que aparece detrás— rinde más que diez microanimaciones
     sueltas, y son 90 ms: se siente, no se espera. El paso también se anima al
     cambiar, así que avanzar y volver tienen peso propio. */
  .ing-paso > * { animation: ing-sube .34s cubic-bezier(.22,.9,.3,1) backwards; }
  .ing-paso > *:nth-child(1) { animation-delay: .03s; }
  .ing-paso > *:nth-child(2) { animation-delay: .06s; }
  .ing-paso > *:nth-child(3) { animation-delay: .09s; }
  .ing-paso > *:nth-child(4) { animation-delay: .12s; }
  .ing-paso > *:nth-child(5) { animation-delay: .15s; }
  .ing-paso > *:nth-child(n+6) { animation-delay: .18s; }
  @keyframes ing-sube {
    from { opacity: 0; transform: translate3d(10px, 4px, 0); }
    to   { opacity: 1; transform: none; }
  }

  /* ---------------------------------------------------------------------
     La pantalla completa de credenciales (auth-shell.ts): la que se ve al
     entrar a /login directo, sin pasar por la portada. Dos columnas como el
     hero —argumento a la izquierda, formulario a la derecha— y no una tarjeta
     de 320px flotando en el medio: esa tarjeta era lo que hacía que el ingreso
     pareciera de otro sitio.
     --------------------------------------------------------------------- */
  body.authbody { background: var(--bg); min-height: 100vh;
    --display: Georgia, "Iowan Old Style", Charter, "Bitstream Charter",
      "Liberation Serif", serif; }
  /* El grid ocupa el ancho REAL de la ventana y la columna del panel es fija:
     así el panel toca el borde derecho en cualquier pantalla, sin trucos.
     Antes el wrap se centraba con max-width y el fondo se estiraba con una
     sombra de 1200px; a 2560px —o sea, con el zoom del navegador en chico—
     esa sombra no llegaba y quedaba una franja de crema a la derecha del
     blanco. Romen lo vio así: "se rompe cuando se hace zoom (chiquito)".
     Lo que se centra es el CONTENIDO de la columna izquierda, con un padding
     que crece con la pantalla (ver .authbrand). */
  .authwrap { display: grid; grid-template-columns: minmax(0, 1fr) 436px;
    min-height: 100vh; }
  .authwrap.ancha { grid-template-columns: minmax(0, 1fr) 620px; }

  /* El padding izquierdo crece con la ventana para que el bloque quede en el
     centro óptico de su columna en vez de pegado al borde. Con max() no hace
     falta media query: en una pantalla chica gana el 28px y en una de 2560
     gana la cuenta, que deja el contenido a la misma distancia del panel que
     del borde izquierdo. */
  .authbrand { padding: 56px 56px 40px max(28px, calc((100% - 480px) / 2));
    display: flex; flex-direction: column; align-items: flex-start; }
  .authbrand > * { max-width: 430px; }
  .authbrand .pubmark { font-size: 19px; margin-bottom: 40px;
    color: var(--txt); text-decoration: none; display: inline-block;
    font-weight: 700; letter-spacing: -.005em; }
  .authbrand-lead { font-family: var(--display); font-weight: 400;
    font-size: 27px; line-height: 1.2; letter-spacing: -.015em;
    color: var(--txt); margin: 0 0 32px; max-width: 22ch; }

  /* Los tres argumentos como renglones de registro: rótulo en negrita, texto
     debajo, filete entre uno y otro. El mismo patrón que la ficha de cifras
     del hero, que es de donde sale la idea de que este sitio "informa". */
  .authargs { list-style: none; margin: 0; padding: 0; max-width: 38ch;
    border-top: 1px solid var(--border); }
  .authargs li { padding: 13px 0; border-bottom: 1px solid var(--border);
    display: grid; gap: 3px; }
  .authargs b { font-size: 13px; font-weight: 600; color: var(--txt); }
  .authargs span { font-size: 12.5px; color: var(--muted); line-height: 1.55; }
  .authbrand-pie { margin: auto 0 0; padding-top: 28px; font-size: 12px;
    color: var(--muted2); }
  /* El pie de la columna de marca sí es gris: son enlaces de servicio —volver,
     términos— y no acciones del trámite. Lo que no hace es cambiar de tono al
     pasar el mouse; se subraya, como el resto. */
  .authbrand-pie a { color: var(--muted); text-decoration: none;
    border-bottom: 1px solid transparent; }
  .authbrand-pie a:hover, .authbrand-pie a:focus-visible { border-bottom-color: var(--muted); }

  /* El panel del formulario: mismo ancho, mismo filete y mismo fondo que el
     panel lateral de la portada. Son la misma pieza vista de dos maneras. */
  /* Sin sombra ni pseudo-elemento: el panel es una columna del grid y llega al
     borde derecho por su propia geometría. */
  .authpanel { background: var(--bg2); border-left: 3px solid var(--accent);
    padding: 48px 44px 36px; display: flex; }
  .authform, .authpanel > .authcol { width: 100%; }
  /* El formulario centrado y el pie al fondo. Con todo arriba quedaban 300px
     de hueco entre el último botón y el pie; centrado, el aire se reparte y la
     columna se lee como una sola pieza. El centrado y el margen automático del
     pie no se pisan: el pie se lleva lo que sobra abajo y el resto queda en el
     medio de lo que queda. */
  .authcol { display: flex; flex-direction: column; min-height: 100%;
    justify-content: center; }
  .authcol .ing-titulo { font-size: 29px; margin-bottom: 10px; }
  /* Sin el tope de medida del panel lateral: acá la columna ya es angosta, y
     34ch cortaba "Creá tu cuenta" en dos renglones. */
  .authcol .ing-bajada { margin-bottom: 26px; max-width: none; }
  .authcol .ing-pie { margin-top: auto; padding-top: 26px; }
  /* Los enlaces sueltos del pie no llevan el subrayado de borde que sí llevan
     los enlaces dentro de un párrafo: como son display:block, ese borde se
     estiraba a lo ancho de la columna y parecía un separador. */
  .authcol .ing-pie a.suelto { border-bottom: 0; }
  /* Los enlaces sueltos del pie van en columna y con aire: apilados sin
     separación se leían como un párrafo partido. */
  /* Mismo criterio que .ing-olvide: el acento es el color de los enlaces en
     todo el sitio, y el hover marca con el subrayado y no con otro tono. */
  .authcol .ing-pie a.suelto { display: block; margin-top: 8px;
    font-size: 13px; color: var(--accent); text-decoration: none; }
  .authcol .ing-pie a.suelto:hover, .authcol .ing-pie a.suelto:focus-visible { text-decoration: underline; }

  /* Los términos: texto para leer, no formulario. Más ancho de medida y
     encabezados que separan sin gritar. */
  .authprosa h2 { font-family: var(--display); font-weight: 400;
    font-size: 19px; margin: 26px 0 8px; letter-spacing: -.01em; }
  .authprosa p, .authprosa li { color: var(--txt2); line-height: 1.65;
    font-size: 13.5px; margin: 0 0 9px; max-width: 62ch; }
  .authprosa ul { padding-left: 18px; margin: 0 0 12px; }
  .authprosa b { color: var(--txt); font-weight: 600; }
  /* La frase que hay que copiar en la nota, en un bloque aparte: es la parte
     del trámite que más se hace mal, y suelta en un párrafo obliga a
     transcribirla a mano. Con la serif y el filete se lee como una cita, que
     es lo que es. */
  .bajafrase { font-family: var(--display); font-size: 14px; line-height: 1.6;
    color: var(--txt); background: var(--card);
    border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0;
    padding: 14px 16px; margin: 0 0 12px; user-select: all; }

  @media (max-width: 900px) {
    /* Una columna: la marca arriba, compacta, y el formulario debajo. Los tres
       argumentos se van —en el teléfono, quien abre /login ya decidió entrar y
       tres párrafos de por qué son tres pantallazos de scroll—. */
    .authwrap, .authwrap.ancha { grid-template-columns: 1fr; min-height: 0; }
    .authbrand { padding: 24px 20px 0; }
    .authbrand .pubmark { margin-bottom: 18px; }
    .authbrand-lead { font-size: 21px; margin-bottom: 0; }
    .authargs, .authbrand-pie { display: none; }
    .authpanel { border-left: 0; border-top: 3px solid var(--accent);
      margin-top: 28px; padding: 30px 20px 36px; background: none; }
  }

  /* En el teléfono el panel es la pantalla entera: 436px de ancho fijo sobre
     360px de viewport dejaba la portada asomando 0px y el velo sin sentido. */
  @media (max-width: 520px) {
    /* En el teléfono el filete se va arriba: de costado, sobre una pantalla
       completa, sería una franja pegada al marco que no separa de nada. */
    .ing-panel { width: 100vw; border-left: 0; border-top: 3px solid var(--accent); }
    .ing-cuerpo { padding: 12px 20px 24px; }
    .ing-titulo { font-size: 26px; }
  }

  @media (prefers-reduced-motion: reduce) {
    .ing-velo, .ing-panel, .ing-icono, .ing-ojo, .ing-btn,
    .ing-bajada a, .ing-pie a, .ing-campo input, .ing-campo textarea,
    .ing-reqs li { transition: none; }
    .ing-paso > * { animation: none; }
  }
