/* ============================================================
   Lisa · Portal web — desktop-first
   Forma: cabeza corta (brief) + cola larga (multifuente).
   El chrome fijo (CTA, player, chat) orbita y nunca se va.
   Patrones de Particle en 15-PARTICLE-PATRONES-WEB.md
   ============================================================ */

/* ═══ INTER · AUTO-HOSPEDADA ══════════════════════════════════
   🔴 NO SE VUELVE A GOOGLE FONTS. Hasta el 22-ago-2026 esta portada
   —y SOLO esta portada— le pedia Inter a fonts.googleapis.com, mientras las
   otras 31 paginas la servian desde /assets/fonts. Eso mandaba la IP de cada
   visitante a Google en cada carga, y la Politica de Privacidad dice por
   escrito «no usamos cookies de sesion, ni cookies publicitarias, ni de
   analitica, ni de terceros». El encabezado de styles.css ya lo prohibia; lo
   que faltaba era que la portada lo cumpliera.

   Son los MISMOS dos @font-face de styles.css: mismo archivo, mismo eje
   100..900, mismos unicode-range. La italica no se carga (no hay <i> aca).
   🔴 El eje entero hace falta: la portada usa 650 y 750, que las
   instancias estaticas de Google Fonts (400/500/600/700/800/900) no tienen
   y el navegador tenia que redondear. Con la variable salen exactas.
   `font-display:swap` y no `block`: block esconde el titular hasta que llegue
   el archivo. El <link rel="preload" ... crossorigin> va en index.html; sin
   el `crossorigin` el preload se descarta y el .woff2 se baja dos veces. */
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url('/assets/fonts/inter-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,
    U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url('/assets/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
    U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,
    U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

:root{
  --bg:#FFFFFF;
  --fg:#040404;
  --muted:#6E6E6E;
  --hair:#ECECEC;
  /* Lo que la barra del navegador tapa abajo. En iOS Safari un
     `position:fixed; bottom:0` se ancla al viewport de LAYOUT, que sigue
     por debajo de esa barra: los controles del dock quedan tapados e
     intocables. `seguirViewport()` mide la diferencia contra
     `visualViewport` y la escribe acá. En escritorio da 0. */
  --vv-bottom:0px;
  /* El máximo de `--vv-bottom` visto, que sólo crece. Lo escribe
     `seguirViewport()` y lo lee UNA sola cosa: el reserve del documento.
     Ver el comentario de esa función — el resumen es que lo fijo tiene que
     seguir a la barra del navegador al píxel, y el largo de la página no
     puede ir y venir mientras scrolleás. */
  --vv-doc:0px;
  --ai:#6E3AED;

  /* EL canal lateral, uno solo. Antes el feed arrancaba a 26px del borde de
     su columna y la sidebar a 14px del suyo, así que las dos columnas no
     compartían una grilla: es la desalineación de 12px que no se puede
     nombrar pero se siente. En mobile lo pisa el bloque de @media con 16.
     La masthead se queda en 20px a propósito: es chrome de ancho completo,
     no una columna del contenido. */
  --gut:26px;
  --mast-h:64px;         /* barra fija de arriba */
  --band-h:92px;         /* título de sección + rail */
  --side-w:244px;        /* sidebar de categorías */
  /* De esto depende el asomo del mosaico. No es un número suelto: le
     RESERVA 250px al mosaico, pase lo que pase con el alto de pantalla. */
  /* Le resta el dock además de la masthead: el reproductor está en pantalla
     desde que carga la página, así que el alto útil del feed es
     `100vh - masthead - dock`. Sin restarlo, el hero se dibujaba 72px más
     alto que su contenedor y el CTA de abajo quedaba tapado por el dock. */
  --orb:132px;
  --dock-h:72px;
  --chat-w:420px;
  --max:1320px;

  --tint:#6E3AED;
  --spring:cubic-bezier(.34,1.56,.64,1);
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
/* el `display` de la hoja de autor le gana al `[hidden]{display:none}` del
   navegador: sin esto, `el.hidden = true` no esconde nada que tenga flex/grid */
[hidden]{display:none!important}
:focus-visible{outline:2px solid var(--ai);outline-offset:3px;border-radius:6px}
.card > a:focus-visible{outline-offset:4px}
.feed:focus{outline:none}
.feed:focus-visible{outline:2px solid var(--ai);outline-offset:-2px}
html,body{
  margin:0;height:100%;overflow:hidden;          /* la página NO scrollea */
  background:var(--bg);color:var(--fg);
  font-family:'Inter','Helvetica Neue',Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;line-height:1.45;
}
button{font:inherit;color:inherit;border:none;background:none;cursor:pointer}
a{color:inherit;text-decoration:none}
::-webkit-scrollbar{width:0;height:0}

.tint{position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:0;transition:opacity .5s linear}

.eq{display:none;align-items:flex-end;gap:2.5px;height:11px}
.eq i{width:2.5px;border-radius:2px;background:currentColor;display:block}
.eq i:nth-child(1){height:40%;animation:eqw .85s ease-in-out infinite}
.eq i:nth-child(2){height:95%;animation:eqw .85s ease-in-out .16s infinite}
.eq i:nth-child(3){height:60%;animation:eqw .85s ease-in-out .32s infinite}
@keyframes eqw{0%,100%{transform:scaleY(.5)}50%{transform:scaleY(1)}}
body.playing .eq{display:inline-flex}
body.playing .ic-play{display:none}

.brand-orb{
  width:22px;height:22px;border-radius:50%;flex:0 0 auto;
  background:conic-gradient(from 210deg,#7C5CFF,#B14BE8,#5B4DBE,#7C5CFF);
  box-shadow:0 0 0 1px rgba(4,4,4,.06),inset 0 0 6px rgba(255,255,255,.5);
}
/* la marca es el orbe de verdad, recortado de una captura de la app
   (tools/ lo genera). El degradé de arriba queda para las piezas chicas. */
.brand-mark{
  width:34px;height:34px;flex:0 0 auto;
  display:grid;place-items:center;overflow:visible;
  background:none;box-shadow:none;border-radius:0;
}
.brand-mark img{width:100%;height:100%;object-fit:contain;display:block}

/* ═══ 1 · MASTHEAD — los CTA viven acá, siempre ═══════════════ */
.masthead{
  position:fixed;top:0;left:0;right:0;height:var(--mast-h);z-index:60;
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:14px;padding:0 20px;
  /* .84 dejaba leer las cards de color pasando por debajo del logo y de
     los CTA, y `saturate(180%)` les subía el color encima. Mismo criterio
     que la banda: casi opaco y con la saturación baja. */
  background:rgba(255,255,255,.94);
  backdrop-filter:saturate(125%) blur(16px);
  -webkit-backdrop-filter:saturate(125%) blur(16px);
  box-shadow:0 1px 0 0 var(--hair);
  transition:right .28s var(--spring);
}
.brand{display:flex;align-items:center;gap:8px}
.mast-left{display:flex;align-items:center;gap:12px;min-width:0}
.brand-name{font-size:17px;font-weight:800;letter-spacing:-.025em}
.edition{
  font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:#767676;
}

/* el player que orbita */
.player-chip{
  justify-self:center;transform:translateY(-8px) scale(.96);
  display:inline-flex;align-items:center;gap:9px;
  padding:8px 8px 8px 16px;border-radius:999px;
  background:var(--fg);color:#fff;font-size:13px;font-weight:700;letter-spacing:-.01em;
  /* Visible desde el arranque y para siempre. Antes entraba con
     `past-brief` y —esto es lo que se sentía mal— VOLVÍA A IRSE al subir
     al hero: el reproductor del brief se cerraba solo cada vez que
     alguien volvía arriba. Acá no cuesta nada tenerlo siempre: la
     masthead de escritorio es `1fr auto 1fr` y el chip ya tenía su
     columna del medio reservada, apagado o prendido. */
  opacity:1;pointer-events:auto;transform:none;
  transition:opacity .24s ease-out,transform .24s var(--spring);
}
.player-chip:hover{background:#1e1e1e}
.player-chip .dur{opacity:.5;font-variant-numeric:tabular-nums}
.player-chip .ic-play{width:10px;height:10px;fill:currentColor}
.player-chip .ic-caret{
  width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;
  opacity:.55;margin-left:2px;transition:transform .22s var(--spring);
}
body.panel-open .player-chip .ic-caret{transform:rotate(180deg)}

.masthead-cta{justify-self:end;display:flex;align-items:center;gap:9px}
@media (max-width:1180px){.cta-line{display:none}}
.cta-line{
  font-size:12.5px;font-weight:600;color:var(--ai);
  padding:6px 12px;border-radius:999px;
  background:color-mix(in srgb,var(--ai) 9%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ai) 20%,transparent);
}
.store{
  display:block;width:34px;height:34px;flex:0 0 auto;
  transition:transform .14s var(--spring);
}
.store:hover{transform:translateY(-2px)}
.store:active{transform:scale(.95)}
.store img{width:100%;height:100%;display:block;object-fit:contain}

.player-scrim{position:fixed;inset:0;z-index:58;opacity:0;pointer-events:none;transition:opacity .2s}
body.panel-open .player-scrim{opacity:1;pointer-events:auto}

/* ═══ SIDEBAR — se enciende al entrar al portal ═══════════════ */
.sidebar{
  position:fixed;top:var(--mast-h);left:0;bottom:0;width:var(--side-w);z-index:30;
  display:flex;flex-direction:column;padding:16px 0 0 var(--gut);
  pointer-events:none;                 /* el hueco de arriba NO come clicks */
  transition:opacity .4s ease-out;
}
.sidebar-foot,.side-social,.side-links{pointer-events:auto}

/* El pie (redes + links obligatorios) se ve desde el arranque. Lo que entra
   al scrollear es la lista de categorías, que en modo landing no tiene
   sentido todavía. */
.topic-list{opacity:0;pointer-events:none;transition:opacity .4s ease-out}
body.past-brief .topic-list{opacity:1;pointer-events:auto}

.topic-list li{transform:translateX(-14px);opacity:0;transition:transform .42s var(--spring),opacity .3s ease-out}
body.past-brief .topic-list li{transform:none;opacity:1}
.topic-list li:nth-child(1){transition-delay:.02s}
.topic-list li:nth-child(2){transition-delay:.05s}
.topic-list li:nth-child(3){transition-delay:.08s}
.topic-list li:nth-child(4){transition-delay:.11s}
.topic-list li:nth-child(5){transition-delay:.14s}
.topic-list li:nth-child(6){transition-delay:.17s}
.topic-list li:nth-child(n+7){transition-delay:.2s}

/* La sidebar ya es una columna con la lista flexible y el pie anclado
   abajo (`flex:1 1 auto` + `min-height:0` acá, `flex:0 0 auto` en el pie):
   eso es lo mismo que pide el `grid-template-rows: 1fr auto` del doc 01
   §6, y convertirlo a grid sólo movería las transiciones de entrada de los
   ítems. Lo que estaba mal era otra cosa, y son las dos de abajo. */
.topic-list{
  list-style:none;margin:0;padding:0 8px 12px 0;display:flex;flex-direction:column;gap:2px;
  overflow-y:auto;flex:1 1 auto;min-height:0;scrollbar-width:none;
  /* 🔴 LOS MISMOS 40px QUE MIDE EL FADE, Y NO ES CASUALIDAD.
     `updateHeader()` trae la categoría actual a la vista con
     `scrollIntoView({block:'nearest'})`, que la deja pegada al borde de
     abajo… que es justo donde el degradé la borra. Entrabas a Campo y Agro
     y en la barra veías «Campo y Agro» a medio desvanecer: la fila que
     estabas mirando era la única ilegible.
     Con `scroll-padding-bottom` el scroll la deja 40px más arriba, o sea
     fuera del fade. Y cuando la fila es la última, empuja la lista hasta
     el final — donde el fade se apaga solo, porque ya no hay nada abajo
     que anunciar (`marcarScrollSidebar()`). Si se cambia el 40 del
     `mask-image`, se cambia acá. */
  scroll-padding-bottom:40px;
}
.topic-list::-webkit-scrollbar{display:none}
/* 🔴 EL FADE, SÓLO SI LA LISTA SCROLLEA DE VERDAD.
   Antes estaba siempre puesto, así que los últimos 40px se desvanecían
   aunque la lista entrara completa. Con "Campo y Agro" de última fila y
   "Ayuda" justo debajo, lo que se veía era la categoría medio borrada
   pegada al pie — se lee como si las dos cosas estuvieran encimadas, que
   es exactamente lo que se reportó. Un fade permanente sobre una lista que
   entra entera no avisa nada: hace parecer que falta contenido.
   La clase la pone `marcarScrollSidebar()`. */
.topic-list.is-scrolling{
  mask-image:linear-gradient(#000 calc(100% - 40px),transparent);
  -webkit-mask-image:linear-gradient(#000 calc(100% - 40px),transparent);
}
.topic-list .sep{font-size:10.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;opacity:.32;padding:12px 12px 5px}
/* Fila de 34px y no de 38 (24 de ficha + 5+5 de padding), tipografía de
   13,5 y contador de 12.

   🔴 LA CUENTA CAMBIÓ: arriba del separador ahora van DOS filas —«Lo
   último» y «Lo más cubierto»—, así que son 14 y no 13. A 34+2 por fila
   dan 546px con separador y padding, y entran sin scroll hasta ~795px de
   alto de ventana. Abajo de eso las aprieta el bloque de
   `max-height:800px`, que las devuelve a entrar hasta 760, igual que antes.
   Más abajo todavía, la lista scrollea con su fade — que es para lo que
   está, y ahora sí tiene algo que anunciar. */
.topic-list a{
  --pill:color-mix(in srgb,var(--c) 13%,transparent);
  display:flex;align-items:center;gap:10px;padding:5px 12px;border-radius:13px;
  font-size:13.5px;font-weight:600;letter-spacing:-.012em;opacity:.5;white-space:nowrap;
  transition:opacity .12s ease-out,color .12s ease-out,background-color .12s ease-out;
}
/* La red de contención del rótulo. Con `white-space:nowrap` y sin esto, un
   nombre largo —«Lo más cubierto» es el más largo de la barra— empuja al
   contador fuera de los 244px de la columna en vez de recortarse. */
.topic-list a .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* la ficha de color con el icono de la categoría, igual que en la app */
.topic-list a .tic{
  width:24px;height:24px;flex:0 0 auto;border-radius:7px;
  display:grid;place-items:center;color:#fff;
  background:linear-gradient(160deg,color-mix(in srgb,var(--c) 86%,#fff),var(--c));
  box-shadow:0 1px 3px color-mix(in srgb,var(--c) 34%,transparent);
  transition:transform .18s var(--spring),box-shadow .18s;
}
.topic-list a .tic svg{width:14px;height:14px}
.topic-list a:hover .tic{transform:scale(1.06)}
.topic-list a.current .tic{box-shadow:0 2px 8px color-mix(in srgb,var(--c) 46%,transparent)}
.topic-list a .count{margin-left:auto;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;opacity:.5}
.topic-list a:hover{opacity:.8}
.topic-list a.current{
  opacity:1;color:var(--c);background:var(--pill);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--c) 24%,transparent);
}
.topic-list a.empty{opacity:.26;pointer-events:none}
.topic-list a.empty .tic{filter:grayscale(1);box-shadow:none}

/* Fondo sólido. Era transparente, así que cuando la lista scrollea su
   última fila se lee POR DETRÁS del pie — el otro medio del "Campo y Agro
   encimado con Ayuda". El sangrado a la izquierda tapa los 14px de padding
   de la sidebar. Arriba no lleva sombra a propósito: el fade de la lista
   ya marca el corte, y sólo cuando de verdad hay algo más abajo. */
.sidebar-foot{
  padding:10px 14px 18px var(--gut);margin-left:calc(-1 * var(--gut));
  flex:0 0 auto;margin-top:auto;
  display:flex;flex-direction:column;gap:12px;
  position:relative;z-index:1;background:var(--bg);
}
.side-social{display:flex;flex-wrap:wrap;gap:4px;padding-left:2px}
.side-social a{
  width:26px;height:26px;border-radius:7px;display:grid;place-items:center;
  color:#9A97A8;transition:color .12s,background-color .12s;
}
.side-social a:hover{color:var(--fg);background:#F2F1F6}
.side-social svg{width:14px;height:14px;fill:currentColor}
.side-social svg.ln{fill:none;stroke:currentColor;stroke-width:1.9}
/* dos columnas: en una sola el bloque medía 300px y se comía la franja
   donde vive el hero */
.side-links{display:grid;grid-template-columns:1fr 1fr;gap:1px 10px;padding-left:2px}
.side-links a{
  font-size:11.5px;line-height:17px;color:#9A97A8;
  transition:color .12s;width:fit-content;
}
.side-links a:hover{color:var(--fg)}
.side-links b{font-weight:700;color:#7E7A8C}
.side-links a:hover b{color:var(--fg)}
.side-c{grid-column:1/-1;margin-top:8px;font-size:10.5px;line-height:15px;color:#B4B1BE}

/* ═══ BANDA DE SECCIÓN ═══════════════════════════════════════ */
.section-band{
  position:fixed;top:var(--mast-h);left:var(--side-w);right:0;height:var(--band-h);z-index:20;
  pointer-events:none;opacity:0;transform:translateY(-10px);
  transition:opacity .35s ease-out,transform .45s var(--spring),right .28s var(--spring);
}
body.past-brief .section-band{transform:none}
/* El fondo va en una capa aparte (si la máscara va en el contenedor se
   lleva puestos los chips). Y es OPACO en TODO el alto de la banda: si
   el degradé empieza a transparentar donde están los chips, las cards
   pasan por debajo y se ve el texto de las dos cosas encimado.
   El fade ocurre 28px MÁS ABAJO, ya fuera de la banda. */
.section-band::before{
  content:"";position:absolute;left:0;right:0;top:0;bottom:-28px;z-index:-1;
  /* Opaco, y `saturate` bajo. El 3% que transparentaba antes sería
     invisible por sí solo, pero `saturate(180%)` AMPLIFICABA el color de
     lo que pasara por debajo: con cards de color a pantalla completa, el
     título y los chips se lavaban de rosa o de verde según qué card
     estuviera pasando. El vidrio sigue —los 28px de fade de abajo son
     los que lo muestran— sin teñir el texto. */
  background:#fff;
  -webkit-backdrop-filter:blur(14px) saturate(125%);backdrop-filter:blur(14px) saturate(125%);
  -webkit-mask-image:linear-gradient(#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(#000 calc(100% - 28px),transparent);
}
body.past-brief .section-band{opacity:1}
.band-title{position:relative;height:50px;margin:6px 0 0}
.band-title h1{
  position:absolute;inset:0;margin:0;padding:0 var(--gut);
  font-size:clamp(24px,2.4vw,34px);font-weight:800;letter-spacing:-.032em;line-height:1.15;
  display:flex;align-items:center;gap:11px;transition:opacity .5s linear;
  /* El h1 es `position:absolute` adentro de un contenedor de 50px: un
     título largo, o el zoom en 125%, lo cortaban. "Campo y Agro" a 1280 ya
     quedaba al filo. El `clamp` lo achica antes de que eso pase, y el
     recorte con puntos suspensivos de abajo es la red por si aún así no
     entra: eso se lee como un título largo; cortado contra el borde se lee
     como un bug. */
  overflow:hidden;
}
.band-title h1 .tt{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.band-title h1 .when{flex:0 0 auto;font-size:12.5px;font-weight:600;letter-spacing:-.01em;color:var(--muted);opacity:.8;padding-bottom:2px}
/* ── LA FILA DE CATEGORÍAS ─────────────────────────────────────
   Antes: `overflow:hidden` acá y `li.offscreen{opacity:0}` desde JS. A
   1440 y a 1280 eso desaparecía siempre los dos últimos chips
   —Tecnología y Campo y Agro— sin flecha, sin fade y sin drag. Dos
   categorías a las que no se podía llegar desde la barra, en silencio.

   El scroller es cada `ul` y NO el riel: los `ul` están apilados en
   absoluto para el crossfade entre categorías, y un contenedor cuyos
   hijos son todos absolutos no tiene ancho que scrollear. Con `inset:0`
   cada uno toma el ancho del riel y scrollea el suyo.               */
.filter-rail{position:relative;height:32px;pointer-events:auto}
.filter-rail ul{
  position:absolute;inset:0;margin:0;padding:0 var(--gut);list-style:none;
  display:flex;flex-direction:row;align-items:center;gap:6px;
  opacity:0;pointer-events:none;transition:opacity .5s linear;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x proximity;      /* acomoda al soltar; no es un carrusel */
  /* 🔴 EL SNAP TIENE QUE RESPETAR EL CANAL LATERAL.
     Los chips llevan `scroll-snap-align:start` y el riel un
     `padding:0 var(--gut)`. Sin decirle dónde empieza de verdad el
     scrollport, el snap alinea el borde del primer chip con el borde del
     riel y se COME los 26px de padding: al cargar, el `ul` quedaba en
     `scrollLeft:26` y «Todo» arrancaba en x=244 mientras el título
     «Popular» y las cards arrancaban en 270. Veintiséis píxeles de
     desalineación en la primera pantalla, y de las que no se pueden
     nombrar pero se sienten — que es literalmente lo que pedía el doc 01
     §3 sobre el `--gut`.
     Es el mismo error que tenía la lista de categorías con su fade: un
     `scroll-*` que no sabe del padding. La regla es la misma —
     `scroll-padding` acompaña a `padding` cada vez que hay snap. */
  scroll-padding-inline:var(--gut);
  scrollbar-width:none;-ms-overflow-style:none;
  /* sin esto, llegar al final del riel dispara el "atrás" del navegador */
  overscroll-behavior-x:contain;
}
.filter-rail ul::-webkit-scrollbar{display:none}
.filter-rail ul.current{opacity:1;pointer-events:auto}
.filter-rail li{flex:0 0 auto;scroll-snap-align:start}
/* Mientras se arrastra, el texto no se selecciona ni el chip se "aprieta" */
.filter-rail.arrastrando ul.current{scroll-snap-type:none;user-select:none;cursor:grabbing}
.filter-rail.arrastrando button{pointer-events:none}

/* El aviso de que hay más: 28px de fade contra el borde, y sólo del lado
   que de verdad tiene algo afuera (las clases las pone `marcarDesborde()`).
   Va con `mask-image` y no con un degradé encima porque así no hay que
   saber de qué color es el fondo de la banda — el mismo recurso que usa
   el riel de categorías en mobile. */
.filter-rail.mas-der ul.current{
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);
}
.filter-rail.mas-izq ul.current{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 28px);
  mask-image:linear-gradient(90deg,transparent,#000 28px);
}
.filter-rail.mas-izq.mas-der ul.current{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent);
}
/* 🔴 LOS TRECE TIENEN QUE ENTRAR A 1440.
   El riel scrollea y avisa con un fade, asi que nada se pierde — pero el
   ultimo chip quedaba SIEMPRE cortado al medio en la primera pantalla, y un
   rotulo partido se lee como un error de maquetado, no como "hay mas".
   Con 13 rubros, 10px de padding y 12px de letra entran los trece en 1440
   con aire de sobra. Mas angosto que eso vuelve a scrollear, que es lo
   correcto: apretarlos hasta que entren en 1024 los volveria ilegibles.
   Si algun dia se agrega un rubro numero 14, vuelve a cortarse — y ahi la
   respuesta es el "Mas ▾" del doc 02 §E, no seguir achicando la letra. */
.filter-rail button{
  --chip:var(--tint);
  padding:6px 10px;border-radius:999px;white-space:nowrap;
  font-size:12px;font-weight:600;letter-spacing:-.01em;color:var(--chip);
  background:color-mix(in srgb,var(--chip) 11%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--chip) 16%,transparent);
  transition:background-color .12s ease-out,color .35s linear,transform .1s ease-out;
}
.filter-rail button:hover{background:color-mix(in srgb,var(--chip) 20%,transparent)}
.filter-rail button:active{transform:scale(.96)}
.filter-rail button.on{color:#fff;background:var(--chip);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--chip) 60%,transparent)}

/* ═══ 2 · EL ÚNICO SCROLLER ══════════════════════════════════ */
.feed{
  position:fixed;inset:var(--mast-h) 0 0 0;
  overflow-y:auto;overscroll-behavior:contain;
  /* +22 para que el título de la sección respire debajo del fade
     de la banda cuando el snap engancha la primera card */
  /* Dos puntos de anclaje y nada más: el hero y el arranque de Popular.
     Así el primer scroll aterriza justo ahí en vez de dejarte a mitad de
     camino. `proximity` y no `mandatory`: con mandatory no podés parar
     entre dos anclajes y el scroll largo se vuelve un secuestro. */
  scroll-snap-type:y proximity;scroll-padding-top:calc(var(--band-h) + 22px);
  scrollbar-width:none;transition:opacity .5s linear,right .28s var(--spring);
}
.feed::-webkit-scrollbar{display:none}

/* 🔴 EL FEED ARRANCA DEBAJO DE LA BANDA, NO ABAJO DE ELLA.
   `.section-band` es `position:fixed` y ocupa de 64 a 156 (`--mast-h` +
   `--band-h`). `.feed` arranca en 64. O sea que los primeros 92px del
   scroller viven TAPADOS, y ese espacio no era de nadie: el título de la
   sección —«Lo último · 23:42 HS · EDICIÓN DE HOY», que es lo único que
   dice que el sitio se actualizó hoy— quedaba 100% abajo de la banda y no
   se veía NUNCA en escritorio. La primera card arrancaba en 134, o sea 18px
   ARRIBA de donde terminan los chips: no faltaba aire, había superposición.

   Esto no siempre estuvo roto: mientras existía el hero, la banda entraba
   con `past-brief` recién después de scrollear, y para entonces ya había
   dónde. Cuando el hero se fue («La portada abre en el portal») la banda
   pasó a estar prendida desde el primer cuadro y nadie le reservó el alto.

   Regla: ningún contenido puede quedar debajo de la banda en reposo. Si
   algo se ve a través del header está mal, aunque se lea. Los 48px son el
   aire de llegada; el título de la sección cae en 204 y la primera card en
   250. En mobile el reserve lo hace `.feed{padding-top}`, porque ahí el
   scroller es el documento y hay que sumar también la masthead. */
.feed-inner{width:100%;padding-top:calc(var(--band-h) + 48px)}
.portal,.ultimo,.foot{
  padding-left:calc(var(--side-w) + var(--gut));padding-right:var(--gut);
  max-width:calc(var(--max) + var(--side-w));
}


/* `.legales-m` y `ubicarLegales()` se fueron el 24-ago: en mobile los
   botones obligatorios viven en el footer y nada más. Ver el bloque «EL
   PIE LEGAL YA NO SUBE» del media query de mobile. */

/* Estas dos `display:none` son de ESCRITORIO ANGOSTO: entre 900 y 1320px
   la columna derecha no entra y el hero cede el teléfono. Abajo de 900px
   el bloque de mobile las vuelve a prender, porque ahí no hay columna
   derecha que ceder: es el único CTA de descarga que queda.
   Si tocás estas reglas, mirá también el reset del bloque de 900px: la
   causa del bug era exactamente que nadie declaraba `display` de vuelta. */


.kicker{
  margin:0 0 22px;height:16px;
  font-size:12px;font-weight:700;line-height:16px;letter-spacing:1.6px;
  text-transform:uppercase;color:#767676;
  display:flex;align-items:center;gap:9px;
}
.kicker .dot{width:7px;height:7px;border-radius:50%;background:#22C55E;animation:livep 1.8s infinite}
@keyframes livep{0%,100%{opacity:1}50%{opacity:.3}}
@keyframes halo{
  0%,100%{transform:scale(1);opacity:.75}
  50%{transform:scale(1.12);opacity:.25}
}
@keyframes lnIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* pill de capítulo */
.chapter-pill{
  display:inline-flex;align-items:center;gap:9px;max-width:100%;
  padding:8px 15px 8px 8px;border-radius:999px;
  background:color-mix(in srgb,var(--ai) 10%,transparent);
  font-size:15px;letter-spacing:-.015em;
  transition:background-color .14s ease-out,transform .1s ease-out;
}
.chapter-pill:hover{background:color-mix(in srgb,var(--ai) 17%,transparent)}
.chapter-pill:active{transform:scale(.98)}
.chapter-pill b{color:var(--ai);font-weight:700}
.chapter-pill > span:not(.ch-ic){color:#B79BFF;font-weight:500}
.ch-ic{
  width:28px;height:28px;border-radius:9px;flex:0 0 auto;display:grid;place-items:center;
  background:var(--c,var(--ai));color:#fff;
}
.ch-ic svg{width:15px;height:15px;fill:currentColor}
.chapter-pill .ic-caret{width:16px;height:16px;fill:none;stroke:var(--ai);stroke-width:2.4;stroke-linecap:round;opacity:.5}
@keyframes cue{0%,100%{transform:translateY(0)}50%{transform:translateY(4px)}}

/* ── CAPÍTULOS ── */
.chapters{
  position:fixed;top:calc(var(--mast-h) + 10px);left:50%;
  width:min(560px,calc(100vw - 40px));max-height:calc(100vh - var(--mast-h) - 40px - var(--dock-h));
  transform:translate(-50%,-12px);opacity:0;pointer-events:none;z-index:59;
  display:flex;flex-direction:column;
  background:#fff;border-radius:22px;overflow:hidden;
  box-shadow:0 30px 80px rgba(4,4,4,.24),0 0 0 1px var(--hair);
  transition:opacity .2s ease-out,transform .26s var(--spring);
}
body.panel-open .chapters{opacity:1;transform:translate(-50%,0);pointer-events:auto}
.ch-head{display:flex;align-items:flex-start;gap:12px;padding:20px 22px 14px}
.ch-head b{display:block;font-size:22px;font-weight:800;letter-spacing:-.03em}
.ch-head small{display:block;font-size:12.5px;color:var(--muted);margin-top:2px}
.ch-close{margin-left:auto;width:32px;height:32px;border-radius:50%;display:grid;place-items:center;background:#F2F1F6;color:var(--muted);flex:0 0 auto}
.ch-close:hover{background:#E8E6F0;color:var(--fg)}
.ch-close svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
.ch-body{flex:1 1 auto;overflow-y:auto;padding:0 14px 8px;scrollbar-width:none}
.ch-body::-webkit-scrollbar{display:none}
.ch-sec{padding:12px 8px 4px;display:flex;align-items:center;gap:10px}
.ch-sec .ch-ic{width:26px;height:26px;border-radius:8px}
.ch-sec b{font-size:16px;font-weight:800;letter-spacing:-.025em}
.ch-sec .badge{
  font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  padding:3px 8px;border-radius:999px;background:color-mix(in srgb,var(--ai) 12%,transparent);color:var(--ai);
}
.ch-sec .cnt{margin-left:auto;font-size:12.5px;color:var(--muted);font-weight:600}
.ch-item{
  display:flex;align-items:center;gap:12px;padding:10px 8px;border-radius:12px;cursor:pointer;
  transition:background-color .12s;
}
.ch-item + .ch-item{box-shadow:inset 0 1px 0 0 var(--hair)}
.ch-item:hover{background:#FAFAFC}
.ch-item.on{background:color-mix(in srgb,var(--ai) 8%,transparent)}
.ch-item .idx{width:16px;flex:0 0 auto;font-size:12.5px;font-weight:700;color:var(--muted);text-align:center}
.ch-item.on .idx{color:var(--ai)}
.ch-item .tx{flex:1 1 auto;min-width:0}
.ch-item .tt{font-size:14.5px;font-weight:650;letter-spacing:-.018em;line-height:1.28;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.ch-item.on .tt{color:var(--ai)}
.ch-item .src{font-size:12px;color:var(--muted);margin-top:2px}
.ch-item .d{flex:0 0 auto;font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.ch-item .go{width:30px;height:30px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;background:#F2F1F6;color:var(--muted)}
.ch-item .go svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round}
.ch-foot{
  flex:0 0 auto;display:flex;align-items:center;justify-content:center;gap:8px;
  margin:8px 14px 14px;padding:11px;border-radius:999px;
  background:color-mix(in srgb,var(--ai) 8%,transparent);color:var(--ai);
  font-size:12px;font-weight:700;letter-spacing:.05em;
}
.ch-foot svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.player-scrim{position:fixed;inset:0;z-index:58;opacity:0;pointer-events:none;transition:opacity .2s;background:rgba(4,4,4,.28)}
body.panel-open .player-scrim{opacity:1;pointer-events:auto}

/* ── PORTAL: acá muere el scroll ──
   padding chico para que el mosaico se asome apenas termina el brief.
   El aire de la banda se resuelve con scroll-margin, no con padding:
   así arriba no hay hueco y al saltar por sidebar/snap igual se respeta. */
/* 64 y no 26: es la misma separación que hay entre dos secciones del
   portal (`.section{margin-bottom}`), y las tres tienen que ser el mismo
   número o el feed se lee a saltos. Ver el bloque de `.row`. */
.portal{padding-top:64px}
/* TODAS las secciones anclan: es lo que hace que el scroll "caiga" en cada
   categoría en vez de pasarlas de largo (doc 01 §7).

   El comentario que estaba acá decía "sólo la primera ancla: las otras 11
   harían dead zones", y tenía razón a medias — la dead zone no la hace
   `scroll-snap-align`, la hace `scroll-snap-stop:always`, que OBLIGA a
   frenar en cada ancla aunque vengas con impulso. Con `proximity` y sin
   `stop`, el snap sólo acomoda cuando ya soltaste cerca de un título: no
   secuestra el scroll largo.

   `scroll-snap-stop:always` se queda sólo en los dos puntos de llegada —el
   hero y el arranque del portal—, que son los que sí tienen que atajarte.

   El `scroll-margin-top` va en coordenadas del scroller: `.feed` arranca
   DEBAJO de la masthead (`inset:var(--mast-h) 0 0 0`), así que acá no hay
   que sumarle los 64px — con la banda alcanza. */
/* `flow-root` para que el `margin-bottom:-18px` de `.row` NO se junte con
   el margen de abajo de la sección. Sin esto los dos márgenes colapsan
   —64 y −18 dan 46— y la separación entre secciones salía distinta según
   si la sección terminaba en la fila o en el botón «Ver las N», que es el
   peor de los dos mundos: mal Y desparejo. Medido: 46 contra 64. */
.section{display:flow-root;margin:0 0 64px;scroll-margin-top:calc(var(--band-h) + 10px);scroll-snap-align:start}
.ultimo{display:flow-root;scroll-snap-align:start;scroll-margin-top:calc(var(--band-h) + 10px)}
.portal > .section:first-child{scroll-snap-stop:always}
.section.hidden{display:none}
.section-head{display:flex;align-items:baseline;gap:12px;margin:0 0 12px;padding:0 2px}
.section-head h2{margin:0;font-size:21px;font-weight:800;letter-spacing:-.028em;color:var(--c)}
.section-head .n{font-size:11.5px;font-weight:700;color:#767676}
.section-head .rule{flex:1 1 auto;height:1px;background:var(--hair)}
/* 🔴 EL GAP VERTICAL NO ES `row-gap`, Y NO ES UN CAPRICHO.
   Los rellenos de abajo (`.rellena`) miden 0 de alto, pero cuando el
   último renglón cerraba justo se iban solos a un renglón nuevo — vacío,
   de alto cero, y aun así con sus 18px de `row-gap` arriba. Cada sección
   que tilaba perfecto se comía 18px de aire fantasma, y no hay margen
   negativo que cancele un `row-gap` (el navegador clampea el alto de línea
   en cero antes de aplicarlo).
   Con el gap vertical puesto como `margin-bottom` de la card, el renglón
   fantasma no aporta nada: los rellenos no llevan margen. El
   `margin-bottom` negativo de la fila devuelve los 18 del último renglón
   de verdad, así que la separación con lo que sigue es la de siempre. */
.row{display:flex;flex-flow:row wrap;column-gap:18px;row-gap:0;margin-bottom:-18px}

/* ── LA DENSIDAD ────────────────────────────────────────────────
   Medido a 1440 contra Particle, mismo navegador: Lisa ponía SEIS cards
   por fila de 179px de ancho; ellos ponen 2 o 3 de 510–626. En 179px el
   titular entraba en tres renglones de veinte caracteres y la bajada se
   cortaba con puntos suspensivos — de ahí la sensación de amontonado. No
   era un problema de paddings: había el doble de columnas de las que el
   contenido aguanta.

   🔴 LA ESCALA DE SEIS TAMAÑOS SE QUEDA, Y NO ES CAPRICHO. El ancho de la
   card lo decide `sizeFor(n, pos)` por cantidad de medios: un hecho que
   contaron nueve medios ocupa más que uno que contó uno solo, y ESE es el
   argumento del producto dibujado en la grilla. Lo que la rompió no fue la
   escala sino el dato: con el 97,5% del portal en n=1 (doc 04 §1) todas
   las cards caen en `sm`, y `sm` medía 164. Una grilla de
   `repeat(auto-fill, minmax(300px,1fr))` —que es lo que pedía el handoff—
   arregla el ancho pero deja las seis medidas iguales para siempre, o sea
   tira la escala justo antes de que el backend la vuelva a llenar.

   Entonces: se sube el PISO, no se cambia el mecanismo. Con `sm` en 300 y
   gap 18, sobre los 1144px útiles de 1440 entran tres (3×300+36=936) y no
   cuatro (4×300+54=1254), y crecen a ~369. A 1920 el `--max` corta en
   1268: entran cuatro de ~303. A 1280 quedan tres de ~316. Nunca seis, y
   sin escribir un breakpoint. */
.card{position:relative;height:300px;margin-bottom:18px;overflow:visible;transition:transform .1s ease-out}
.card-sm  {flex:1   6 300px}
.card-med {flex:1.4 5 330px}
.card-lg  {flex:1.9 4 360px}
.card-xl  {flex:2.4 3 400px}
.card-xxl {flex:3   2 450px}
.card-xxxl{flex:3.6 1 620px}
/* Los rellenos del último renglón (`rellenarFila()` en app.js). Mismo
   `flex` que `.card-sm`, alto cero. El `margin-top` negativo cancela el
   `row-gap` para el caso en que el renglón cerraba justo y los tres se van
   solos a un renglón nuevo: sin eso, cada sección que tila perfecto se
   comía 18px de aire fantasma abajo. */
.rellena{flex:1 6 300px;height:0;margin:0;padding:0;border:0}
.card.hidden{height:0;margin-bottom:0;overflow:hidden}
.card.hidden *{height:0}
.card > a{
  position:absolute;inset:0;display:flex;flex-direction:column;justify-content:flex-end;
  border-radius:22px;overflow:hidden;color:#fff;
  background:color-mix(in srgb,var(--c) 42%,#0B0B14);
  box-shadow:0 8px 26px rgba(4,4,4,.09);
  transition:transform .15s var(--spring),box-shadow .2s ease-out;
}
.card > a::before{content:"";position:absolute;inset:0;border-radius:22px;z-index:3;pointer-events:none;box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
.card:hover > a{transform:scale(1.025);box-shadow:0 16px 42px rgba(4,4,4,.19)}
.card:active > a{transform:scale(1)}
.card img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:opacity .35s ease-out}
.card .veil{
  position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,rgba(8,8,14,0) 34%,
    color-mix(in srgb,var(--c) 44%,rgba(10,10,18,.30)) 62%,
    color-mix(in srgb,var(--c) 82%,#0B0B14) 100%);
}
.card .body{position:relative;z-index:2;padding:0 14px 13px}
.card .meta{
  display:flex;align-items:center;gap:7px;margin-bottom:6px;flex-wrap:nowrap;overflow:hidden;white-space:nowrap;
  font-size:9.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
}
/* Con 2 medios va el badge de vidrio de siempre; con 3 o más va en el color
   de la categoría, que es cuando el dato dice algo. Con 1 no se dibuja
   —lo decide `badgeMedios()`— y de paso desaparece el "1 MEDIO" en gris
   sobre foto oscura, que estaba al límite del contraste AA. */
.card .meta .n{flex:0 0 auto;padding:3px 8px;border-radius:999px;background:rgba(255,255,255,.2);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}
.card .meta .n.fuerte{background:var(--c);color:#fff;box-shadow:0 1px 4px rgba(6,6,12,.28)}
.card .meta .cat{opacity:.78;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:0 1 auto}
.card .meta .time{margin-left:auto;opacity:.62;font-variant-numeric:tabular-nums;flex:0 0 auto;padding-left:4px}
/* En 179px de ancho, `sm` tenía que esconder el medio y la hora y clampear
   el titular en cuatro renglones para que entrara algo. Con el piso en 300
   eso deja de ser cierto: la card más chica mide ~316 y le entra el
   titular, la bajada, el medio y la hora. Que es exactamente lo que hace
   que Particle se lea tranquilo — no es que ellos pongan menos cosas, es
   que las suyas caben.

   🔴 TRES RENGLONES Y NO DOS, Y EL NÚMERO SALE DE CONTAR.
   Con dos, medido sobre las 94 cards del portal del 22-ago: se cortaba el
   21% de los titulares y el 88% de las bajadas —que promedian 243
   caracteres y en dos renglones de 364px entran unos 90—. Y mientras
   tanto el bloque de texto usaba 117px de una card de 300: sobraban 183
   sin usar. Cortar texto teniendo el espacio al lado es la peor
   combinación posible.
   Con tres y tres el bloque queda en ~155px y siguen sobrando ~145 de
   foto, que es lo que la card necesita para seguir siendo una card y no
   un párrafo con una imagen atrás. */
.card h3{
  margin:0;font-size:15px;font-weight:700;letter-spacing:-.02em;line-height:1.24;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
  text-shadow:0 1px 12px rgba(0,0,0,.3);
}
.card .sub{margin-top:5px;font-size:11.5px;line-height:1.35;opacity:.74;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.card-lg h3{font-size:16px}
.card-xl h3{font-size:17.5px}
.card-xxl h3{font-size:19px}
.card-xxxl h3{font-size:23px;line-height:1.16}
.card-xxl .sub,.card-xxxl .sub{font-size:12.5px;-webkit-line-clamp:4}
/* ── «VER LAS 23 DE DEPORTES» ──────────────────────────────────
   El botón que abre la categoría entera. Va abajo de la fila, alineado
   con las cards, y en el color del rubro: es parte de la sección, no un
   control del chrome. Discreto a propósito — el que quiere las 23 lo
   busca; el que no, no tiene que esquivarlo. */
.ver-todas{
  display:inline-flex;align-items:center;gap:8px;
  margin-top:16px;padding:10px 18px;min-height:40px;
  border:1px solid color-mix(in srgb,var(--c) 32%,transparent);
  border-radius:999px;background:color-mix(in srgb,var(--c) 7%,transparent);
  color:color-mix(in srgb,var(--c) 78%,#0B0B14);
  font:inherit;font-size:12.5px;font-weight:700;letter-spacing:-.01em;
  cursor:pointer;transition:background .15s ease-out,transform .1s ease-out;
}
.ver-todas::after{content:"↓";font-weight:800;opacity:.7}
.ver-todas:hover{background:color-mix(in srgb,var(--c) 13%,transparent)}
.ver-todas:active{transform:scale(.98)}
.ver-todas:focus-visible{outline:2px solid var(--c);outline-offset:3px}

/* ── EL ESQUELETO ──────────────────────────────────────────────
   Lo dibuja `dibujarEsqueleto()` mientras la API no contestó. No agrega
   una pieza visual nueva: son las mismas `.card.loading` de siempre, con
   una barra gris en lugar del título. La sección no lleva color de
   categoría —todavía no se sabe cuál es— así que se apoya en `--fg`, que
   es lo mismo que usa la card. */
.section.esqueleto{--c:transparent}
.section-head .hbar{
  width:180px;max-width:52%;height:15px;border-radius:5px;
  background:color-mix(in srgb,var(--fg) 11%,transparent);
  animation:pulse 1.6s ease-in-out infinite alternate;
}
.card.loading > a{background:color-mix(in srgb,var(--fg) 8%,transparent);animation:pulse 1.6s ease-in-out infinite alternate}
.card.loading img,.card.loading .body,.card.loading .veil{opacity:0}
@keyframes pulse{0%{opacity:.55}100%{opacity:1}}

/* ── SEO ── */
/* OJO: `padding` a secas acá pisaba el `padding-left` de arriba y la
   sección de SEO se dibujaba DEBAJO de la sidebar. Sólo el eje vertical. */
/* "Lo último" ABRE el feed, así que el aire de llegada ya se lo dio
   `.feed-inner` de una vez —sumar acá otro `padding-top` es contar dos
   veces lo mismo y fue exactamente lo que corría el título fuera de su
   lugar—. Abajo no lleva nada: los 64px hasta la primera categoría los
   pone `.portal{padding-top}`, y así el número vive en un solo lado. */
.ultimo{padding-top:0;padding-bottom:0}
/* Acá había un `.ultimo .row{margin-bottom:0}`. Cuando se escribió, `.row`
   no llevaba margen y la regla no hacía nada; ahora `.row` lleva
   `margin-bottom:-18px` para devolver el gap del último renglón, y ponerlo
   en 0 le dejaba a «Lo último» 18px de más contra la primera categoría.
   Una regla muerta que resucita mal es peor que una regla de más. */
.ultimo h2{
  margin:0 0 14px;display:flex;align-items:baseline;gap:11px;
  font-size:21px;font-weight:800;letter-spacing:-.028em;
}
.ultimo h2 small{font-size:11.5px;font-weight:700;color:#767676;letter-spacing:0}


/* El footer legal, igual al de lisaaiapp.com. El botón de arrepentimiento
   y el de baja de servicio son obligatorios en Argentina — no son decoración. */
/* Los 40px de siempre + el alto del FAB: "Preguntale a Lisa" es `fixed` en
   la esquina inferior derecha y tapaba el final del feed. El dock ya está
   resuelto aparte (`body.docked .feed{bottom:var(--dock-h)}`), el FAB no lo
   estaba. */
.foot{padding-top:0;padding-bottom:calc(40px + 64px);font-size:12.5px;color:var(--muted)}
.foot-top{
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  padding:20px 0;border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);
}
.foot-legal{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.foot-legal b{font-size:12.5px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--fg)}
.foot-legal a:hover b{color:var(--ai)}
.foot-legal .sep{opacity:.4}
.foot-nav{display:flex;gap:20px;margin-left:auto;flex-wrap:wrap}
.foot-nav a{font-size:13px}
.foot-nav a:hover{color:var(--fg)}
.foot-bot{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);gap:18px 40px;padding-top:18px}
.foot-bot p{margin:0 0 8px;font-size:12px;line-height:18px;color:#8A8794;max-width:62ch}
.foot-bot a{text-decoration:underline;text-underline-offset:2px}
.foot-bot a:hover{color:var(--fg)}
.foot-right{grid-column:2;grid-row:1;text-align:right;justify-self:end}
.foot-def{display:inline-block;margin-bottom:6px}

/* ═══ DOCK — el reproductor fijo abajo, como Spotify ══════════ */
.dock{
  position:fixed;left:0;right:0;bottom:0;height:var(--dock-h);z-index:62;
  display:flex;align-items:center;gap:18px;padding:0 20px;
  background:rgba(255,255,255,.9);
  -webkit-backdrop-filter:saturate(180%) blur(18px);backdrop-filter:saturate(180%) blur(18px);
  box-shadow:0 -1px 0 0 var(--hair),0 -12px 34px rgba(4,4,4,.05);
  transform:translateY(100%);transition:transform .3s var(--spring),right .28s var(--spring);
}
body.docked .dock{transform:none}
.dock-prog{position:absolute;top:0;left:0;right:0;height:3px;background:var(--hair);cursor:pointer}
.dock-prog i{display:block;height:100%;width:var(--dp,0%);background:var(--ai);transition:width .18s linear}
.dock-left{display:flex;align-items:center;gap:11px;flex:0 0 clamp(200px,24%,320px);min-width:0}
/* El restante en el subtítulo es sólo de mobile: acá el tiempo ya vive en
   `.dock-time`, y mostrarlo dos veces es ruido. */
.dock-mini{display:none}
/* la ficha del bloque que suena, con su color y su icono */
.dock-orb{
  width:40px;height:40px;border-radius:11px;flex:0 0 auto;
  display:grid;place-items:center;color:#fff;
  background:linear-gradient(160deg,color-mix(in srgb,var(--c,#7C5CFF) 86%,#fff),var(--c,#5B4DBE));
  box-shadow:0 2px 8px color-mix(in srgb,var(--c,#6E3AED) 38%,transparent);
  transition:background .4s linear,box-shadow .4s linear;
}
.dock-orb svg{width:21px;height:21px}
body.playing .dock-orb{animation:breathe 2.6s ease-in-out infinite}
.dock-tx{min-width:0}
.dock-tx b{display:block;font-size:13.5px;font-weight:700;letter-spacing:-.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dock-tx small{display:block;font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dock-mid{flex:1 1 auto;display:flex;align-items:center;justify-content:center;gap:9px}
.dock-right{flex:0 0 auto;display:flex;align-items:center;gap:8px}
.dock-time{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;margin-right:4px}
.dctl{
  width:36px;height:36px;border-radius:50%;display:grid;place-items:center;color:var(--fg);
  transition:background-color .12s,transform .1s var(--spring);
}
.dctl:hover{background:#F0EFF5}
.dctl:active{transform:scale(.92)}
/* Un control desactivado tiene que VERSE desactivado. Ahora que el dock
   está en pantalla desde que carga la página, "Leer esta nota" pasa buena
   parte del programa sin nota que abrir —los 50 conectores— y un botón que
   se ve igual pero no hace nada se lee como roto. */
.dctl:disabled{opacity:.3;pointer-events:none}
.dctl:disabled:hover{background:none}
.dctl svg{width:18px;height:18px;fill:currentColor}
.dctl-sm{width:32px;height:32px;font-size:12px;font-weight:800;color:var(--muted)}
/* 🔴 LA VELOCIDAD ES EL ÚNICO CONTROL CON TEXTO, Y «1,25×» NO ENTRA EN LOS
   32px DE `.dctl-sm`: medido contra el .woff2 de Inter instanciado en 800,
   son 33,0px. Se desbordaba por uno — y ya se desbordaba antes de este
   cambio, con «1.25×», que en Inter mide exactamente lo mismo (el punto y la
   coma tienen el mismo avance).
   Así que la caja la manda el rótulo: `min-width` para el piso táctil y
   `width:auto` para los cuatro estados. Píldora y no círculo, porque un
   círculo de 43×32 es una elipse; la píldora además es lo que pide el
   sistema visual para los botones.
   `tabular-nums` va acá y no sólo en mobile: es lo que evita que el ancho
   baile entre «1,25×» y «1,5×». */
#dSpeed{width:auto;min-width:32px;padding:0 6px;border-radius:99px;font-variant-numeric:tabular-nums}
.dctl-sm:hover{color:var(--fg)}
.dctl-sm svg{width:15px;height:15px}
.dctl-play{width:44px;height:44px;background:var(--ai);color:#fff}
.dctl-play:hover{background:#5F2FDD}
.dctl-play svg{width:19px;height:19px}
.dctl-play .ic-pause{display:none}
body.playing .dctl-play .ic-play{display:none}
body.playing .dctl-play .ic-pause{display:block}
/* el feed le deja lugar al dock */
body.docked .feed,body.docked .sidebar{bottom:var(--dock-h)}
body.docked .fab{bottom:calc(var(--dock-h) + 18px)}
body.docked .chat{bottom:var(--dock-h)}

/* ═══ LECTOR DE NOTA ══════════════════════════════════════════
   Es una SECCIÓN, no un cajón que se desliza encima del feed. Ocupa
   toda la ventana debajo de la masthead (los CTA de descarga nunca se
   van), tiene su propia barra de "volver" y una sola columna centrada.
   El CTA de Lisa va al final, en el flujo: pegajoso arriba del texto
   tapaba el título. */
.reader{
  position:fixed;inset:var(--mast-h) 0 0 0;z-index:70;
  background:var(--bg);display:flex;flex-direction:column;
  opacity:0;pointer-events:none;transform:translateY(12px);
  transition:opacity .26s ease-out,transform .34s var(--spring);
}
body.reading .reader{opacity:1;transform:none;pointer-events:auto}
body.docked .reader{bottom:var(--dock-h)}
.reader-scrim{display:none}

.rd-bar{
  flex:0 0 auto;display:flex;align-items:center;gap:14px;
  height:52px;padding:0 22px;
  background:rgba(255,255,255,.9);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  box-shadow:0 1px 0 0 var(--hair);
}
.rd-back{
  display:inline-flex;align-items:center;gap:8px;padding:7px 14px 7px 10px;border-radius:999px;
  font-size:13.5px;font-weight:650;letter-spacing:-.015em;color:var(--fg);
  background:#F2F1F6;transition:background-color .12s,transform .1s var(--spring);
}
.rd-back:hover{background:#E8E6F0}
.rd-back:active{transform:scale(.97)}
.rd-back svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.rd-bar-t{
  font-size:11px;font-weight:800;letter-spacing:.11em;text-transform:uppercase;
  color:var(--c,var(--ai));overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.rd-esc{margin-left:auto;font-size:11.5px;color:var(--muted);font-weight:500}
.rd-esc kbd{
  font:inherit;font-weight:700;padding:2px 6px;border-radius:6px;
  background:#F2F1F6;box-shadow:inset 0 0 0 1px var(--hair);
}

.rd-scroll{flex:1 1 auto;overflow-y:auto;scrollbar-width:none;padding:0 var(--gut) 90px}
.rd-scroll::-webkit-scrollbar{display:none}
.rd-art{max-width:720px;margin:0 auto;padding-top:38px}
.rd-hero{
  position:relative;margin:0 0 26px;aspect-ratio:16/9;max-height:46vh;
  background:#EFEEF4;overflow:hidden;border-radius:20px;
}
.rd-hero img{width:100%;height:100%;object-fit:cover;display:block}
.rd-hero figcaption{
  position:absolute;left:0;right:0;bottom:0;padding:30px 18px 12px;color:#fff;
  font-size:11px;font-weight:600;letter-spacing:.04em;
  background:linear-gradient(transparent,rgba(6,6,12,.72));
}
.rd-body{padding:0 0 40px}
.rd-meta{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 14px;
  font-size:10.5px;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);
}
.rd-meta .cat{color:var(--c)}
.rd-meta .sep{opacity:.4}
.rd-meta .fu{color:var(--ai);padding:3px 9px;border-radius:999px;background:color-mix(in srgb,var(--ai) 10%,transparent)}
.rd-body h1{margin:0 0 14px;font-size:38px;font-weight:800;letter-spacing:-.034em;line-height:1.1;text-wrap:balance}
.rd-dek{margin:0 0 26px;font-size:18.5px;line-height:1.52;color:#3E3E4A}
.rd-body h2{
  margin:30px 0 10px;font-size:11.5px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--ai);
}
.rd-body p{margin:0 0 15px;font-size:16.5px;line-height:1.62}
.rd-body p b{font-weight:700}
.rd-note{
  margin:8px 0 0;padding:14px 16px;border-radius:14px;
  background:#FFF8E6;box-shadow:inset 0 0 0 1px #F2E3B8;
  font-size:13px;line-height:1.5;color:#6B5518;
}
/* Lo que dicen los medios: la lista REAL, con el título de cada nota y
   su link. Nunca inventar medios ni pegar el cuerpo del artículo. */
.rd-sources{
  margin:34px 0 0;padding:20px 20px 12px;border-radius:20px;
  background:#FAFAFC;box-shadow:inset 0 0 0 1px var(--hair);
}
.rd-sources h3{margin:0 0 4px;font-size:15px;font-weight:800;letter-spacing:-.024em;display:flex;align-items:center;gap:10px}
.rd-sources .sub{margin:0 0 12px;font-size:12.5px;color:var(--muted)}
.rd-avs{display:flex;margin-right:2px}
.rd-avs span{
  width:26px;height:26px;border-radius:50%;display:grid;place-items:center;
  font-size:10px;font-weight:800;color:#fff;box-shadow:0 0 0 2px #FAFAFC;margin-left:-8px;
}
.rd-avs span:first-child{margin-left:0}
/* ── DE DÓNDE SALE CADA COSA ───────────────────────────────────
   `articles.resumen_factual`: cada afirmación del resumen con la cita
   textual del medio que la sostiene. Es «Lisa no inventa noticias» hecho
   interfaz, y por eso va con `<details>`: cerrado, la nota se lee igual;
   abierto, ves el respaldo. Sin JavaScript, o sea sin nada que re-atar.
   Lo dibuja `claimsHTML()` sólo si la API manda `claims` — hoy no las
   manda (doc 04 §2) y este bloque no existe en el DOM. */
.rd-claims{margin:26px 0 0}
.rd-claim{
  border-top:1px solid var(--hair);
  font-size:15.5px;line-height:1.5;
}
.rd-claims .rd-claim:last-child{border-bottom:1px solid var(--hair)}
.rd-claim summary{
  list-style:none;cursor:pointer;padding:13px 26px 13px 0;position:relative;
  color:var(--fg);transition:color .12s;
}
.rd-claim summary::-webkit-details-marker{display:none}
.rd-claim summary:hover{color:var(--ai)}
/* La flecha, y su giro al abrir. Va en el pseudo para que el área
   clickeable siga siendo el renglón entero y no un icono de 12px. */
.rd-claim summary::after{
  content:"";position:absolute;right:4px;top:50%;width:8px;height:8px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-70%) rotate(45deg);opacity:.4;
  transition:transform .18s var(--spring),opacity .12s;
}
.rd-claim[open] summary::after{transform:translateY(-25%) rotate(-135deg);opacity:.75}
.rd-claim summary:focus-visible{outline:2px solid var(--ai);outline-offset:2px;border-radius:6px}
.rd-claim blockquote{
  margin:0 0 15px;padding:0 0 0 14px;border-left:2px solid var(--ai);
  font-size:14.5px;line-height:1.55;color:#4A4A57;
}
.rd-claim cite{display:block;margin-top:7px;font-size:11.5px;font-style:normal;
  font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#767676}

.rd-src{
  display:flex;align-items:flex-start;gap:12px;padding:11px 8px;margin:0 -8px;
  border-radius:12px;transition:background-color .12s;
}
.rd-src + .rd-src{box-shadow:inset 0 1px 0 0 var(--hair)}
a.rd-src:hover{background:#F2F1F6}
a.rd-src:hover .tt{color:var(--ai)}
.rd-src .av{width:28px;height:28px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;font-size:11px;font-weight:800;color:#fff;margin-top:1px}
.rd-src .tx{flex:1 1 auto;min-width:0}
.rd-src .nm{display:flex;align-items:center;gap:7px;font-size:11px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
.rd-src .nm .can{
  padding:2px 7px;border-radius:999px;letter-spacing:.06em;
  background:color-mix(in srgb,var(--ai) 11%,transparent);color:var(--ai);
}
.rd-src .tt{margin-top:3px;font-size:14px;font-weight:600;letter-spacing:-.015em;line-height:1.34;transition:color .12s}
.rd-src .go{flex:0 0 auto;width:15px;height:15px;margin-top:14px;fill:none;stroke:var(--muted);stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:.5}

.rd-ask{
  margin:26px 0 0;width:100%;
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:15px 20px;border-radius:16px;color:#fff;
  background:linear-gradient(120deg,#6E3AED,#A24BE8);
  box-shadow:0 10px 30px rgba(110,58,237,.28);
  font-size:14.5px;font-weight:700;
  transition:transform .14s var(--spring),box-shadow .16s;
}
.rd-ask:hover{transform:translateY(-1px);box-shadow:0 14px 36px rgba(110,58,237,.36)}
.rd-ask svg{width:16px;height:16px;fill:currentColor}
.rd-more{
  margin:16px 0 0;display:flex;align-items:center;justify-content:center;gap:8px;
  width:100%;padding:13px;border-radius:14px;
  font-size:13.5px;font-weight:650;color:var(--muted);
  box-shadow:inset 0 0 0 1px var(--hair);transition:color .12s,box-shadow .12s;
}
.rd-more:hover{color:var(--fg);box-shadow:inset 0 0 0 1px #D8D8D8}
.rd-more svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}

/* ═══ 3 · CHAT FLOTANTE ══════════════════════════════════════ */
.fab{
  position:fixed;right:24px;bottom:24px;z-index:70;
  display:inline-flex;align-items:center;gap:9px;padding:13px 20px;border-radius:999px;color:#fff;
  background:linear-gradient(120deg,#6E3AED,#A24BE8);
  box-shadow:0 10px 30px rgba(110,58,237,.4);
  font-size:14px;font-weight:700;letter-spacing:-.01em;
  transition:transform .14s var(--spring),box-shadow .2s,opacity .2s;
}
.fab:hover{transform:scale(1.04);box-shadow:0 14px 38px rgba(110,58,237,.5)}
.fab .ic-spark{width:16px;height:16px;fill:currentColor}
/* leyendo, el CTA de Lisa ya está en el flujo de la nota: dos botones
   idénticos a la vez es ruido */
body.chat-open .fab,body.reading .fab{opacity:0;pointer-events:none;transform:scale(.9)}
/* Y en la landing tampoco. Es el mismo criterio que ya siguen la sidebar y
   la banda de sección —"en la landing no hay nada de esto: la primera
   pantalla es el hero y nada más"—, sólo que el FAB nunca se había sumado.

   Con el dock permanente dejó de ser un detalle: el FAB se apoya arriba del
   dock (`body.docked .fab{bottom:calc(var(--dock-h) + 18px)}`), así que
   subió 120px y quedó justo encima del CTA "Descargá Lisa, gratis"
   —medido a 500×710—, tapándole el botón de Google Play.

   Y aparte no tenía sentido: pregunta "sobre las noticias de hoy" en la
   pantalla donde todavía no viste ninguna, compitiendo con los tres CTA que
   el hero ya tiene. El reproductor del brief sí se queda, que es lo pedido;
   el FAB no es el reproductor. */
body:not(.past-brief) .fab{opacity:0;pointer-events:none;transform:scale(.9)}

/* El chat es una PANTALLA, no un cajón al costado. Mismo patrón que el
   lector: ocupa la ventana debajo de la masthead, tiene barra propia con
   "volver", y adentro dos vistas (inicio / hilo). Como Clickie y Roomix. */
.chat{
  position:fixed;inset:var(--mast-h) 0 0 0;z-index:70;
  background:var(--bg);display:flex;flex-direction:column;
  opacity:0;pointer-events:none;transform:translateY(12px);
  transition:opacity .26s ease-out,transform .34s var(--spring);
}
body.chat-open .chat{opacity:1;transform:none;pointer-events:auto}
body.docked .chat{bottom:var(--dock-h)}
.chat-head{max-width:none}
.chat-head{
  flex:0 0 auto;display:flex;align-items:center;gap:12px;height:52px;
  /* el fondo va a sangre pero el contenido se alinea con la columna */
  padding:0 max(22px, calc((100% - 820px) / 2));
  background:rgba(255,255,255,.9);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  box-shadow:0 1px 0 0 var(--hair);
}
.chat-head > *{max-width:none}
@keyframes breathe{0%,100%{transform:scale(1);opacity:.92}50%{transform:scale(1.07);opacity:1}}
.chat-head b{display:block;font-size:14.5px;font-weight:700;letter-spacing:-.02em}
.chat-head small{display:block;font-size:11.5px;color:var(--muted)}
.chat-close{
  margin-left:auto;display:inline-flex;align-items:center;gap:7px;
  padding:7px 13px;border-radius:999px;
  font-size:12.5px;font-weight:600;color:var(--muted);
  transition:background-color .12s,color .12s;
}
.chat-close:hover{background:#F2F1F6;color:var(--fg)}
.chat-close svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
/* deck: consultas que se prescriben solas, armadas con las noticias de hoy.
   Se van rotando; al primer mensaje del usuario desaparece. */
.chat-back{
  display:inline-flex;align-items:center;gap:7px;padding:7px 13px 7px 9px;border-radius:999px;
  flex:0 0 auto;font-size:13px;font-weight:650;letter-spacing:-.015em;color:var(--fg);
  background:#F2F1F6;transition:background-color .12s,transform .1s var(--spring);
}
.chat-back:hover{background:#E8E6F0}
.chat-back:active{transform:scale(.97)}
.chat-back svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.chat-title{
  flex:1 1 auto;min-width:0;text-align:center;
  font-size:14px;font-weight:700;letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.chat-hist-btn{
  position:relative;flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;
  padding:7px 12px;border-radius:999px;color:var(--muted);
  font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;
  transition:background-color .12s,color .12s;
}
.chat-hist-btn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.chat-hist-btn:hover{background:#F2F1F6;color:var(--fg)}
.chat-hist-btn.on{background:color-mix(in srgb,var(--ai) 11%,transparent);color:var(--ai)}
.chat-hist-btn.vacio{opacity:.35;pointer-events:none}

/* ── INICIO: casi todo aire. El orbe, el saludo y tres sugerencias. ── */
.chat-home{
  flex:1 1 auto;overflow-y:auto;scrollbar-width:none;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:20px 22px;gap:clamp(26px,5vh,52px);
}
.chat-home::-webkit-scrollbar{display:none}
.chat-hero{text-align:center;max-width:460px}
/* 220px de display sobre 440 de backing: la proporción del orbe canónico.
   Con menos backing las partículas se apelmazan y se ve una bola sólida. */
/* OJO: nada de background ni border-radius acá. Había una regla vieja con
   `conic-gradient` (era el orbito de 30px del header del chat lateral) que
   pintaba una bola sólida DETRÁS del canvas y se comía las partículas. */
.chat-orb{
  width:220px;height:220px;display:block;margin:0 auto 18px;
  background:none;border-radius:0;opacity:1;animation:none;
}
@media (max-height:800px){.chat-orb{width:176px;height:176px;margin-bottom:14px}}
.chat-hero h2{
  margin:0 0 9px;font-size:27px;font-weight:800;letter-spacing:-.032em;line-height:1.15;
}
.chat-hero p{margin:0;font-size:15.5px;line-height:22px;color:var(--muted);text-wrap:pretty}

.chat-deck{width:100%;max-width:460px;display:flex;flex-direction:column;gap:9px}
.chat-deck > p{
  margin:0 0 3px;font-size:11px;font-weight:800;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted);opacity:.7;
}
.deck-card{
  display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  padding:12px 14px;border-radius:14px;background:#F5F4F9;
  transition:background-color .14s,transform .12s var(--spring);
}
.deck-card:hover{background:#EEECF6;transform:translateY(-1px)}
.deck-card:active{transform:scale(.99)}
.dic{
  width:26px;height:26px;flex:0 0 auto;border-radius:8px;display:grid;place-items:center;color:#fff;
  background:linear-gradient(160deg,color-mix(in srgb,var(--c) 86%,#fff),var(--c));
}
.dic svg{width:15px;height:15px}
.deck-card .qt{
  flex:1 1 auto;min-width:0;font-size:14.5px;font-weight:600;letter-spacing:-.015em;line-height:1.3;
}
.deck-card .dn{
  flex:0 0 auto;font-size:11px;font-weight:700;letter-spacing:.03em;color:var(--muted);opacity:.75;
}

/* ── HISTORIAL: no vive en el inicio, se abre desde la barra ── */
.chat-hist{
  flex:1 1 auto;overflow-y:auto;scrollbar-width:none;
  padding:18px 22px;display:flex;flex-direction:column;gap:2px;
  max-width:560px;margin:0 auto;width:100%;
}
.chat-hist::-webkit-scrollbar{display:none}
.hist-row{display:flex;align-items:stretch;gap:4px;border-radius:12px;transition:background-color .12s}
.hist-row:hover{background:#F7F6FB}
.hist-open{flex:1 1 auto;min-width:0;text-align:left;padding:11px 10px;border-radius:12px}
.hist-open .ht{
  display:block;font-size:14px;font-weight:600;letter-spacing:-.015em;line-height:1.3;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.hist-open .hm{display:block;margin-top:2px;font-size:11.5px;color:var(--muted)}
.hist-del{
  width:32px;flex:0 0 auto;display:grid;place-items:center;border-radius:10px;
  color:var(--muted);opacity:0;transition:opacity .12s,color .12s;
}
.hist-row:hover .hist-del{opacity:1}
.hist-del:hover{color:#DD2749}
.hist-del svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}

.chat-body{
  flex:1 1 auto;overflow-y:auto;padding:26px 22px;display:flex;flex-direction:column;gap:12px;
  scrollbar-width:none;max-width:720px;margin:0 auto;width:100%;
}
.chat-body::-webkit-scrollbar{display:none}
.msg{max-width:76%;padding:11px 15px;border-radius:18px;font-size:14.5px;line-height:1.5;animation:slideUp .28s var(--spring)}
.msg.quieto{animation:none}
.msg.me{align-self:flex-end;background:var(--ai);color:#fff;border-bottom-right-radius:6px}
.msg.lisa{align-self:flex-start;background:#F4F2FB;border-bottom-left-radius:6px}
/* El markdown acotado de la respuesta: parrafos, listas y negrita/cursiva.
   El primero y el ultimo sin margen, para que la burbuja no crezca de alto:
   el padding de `.msg` ya pone el aire. */
.msg-p{margin:0 0 9px}
.msg-p:last-child{margin-bottom:0}
.msg-ul{margin:0 0 9px;padding-left:18px}
.msg-ul:last-child{margin-bottom:0}
.msg-ul li{margin:0 0 4px}
.msg-ul li:last-child{margin-bottom:0}
.msg b{font-weight:700}
.msg i{font-style:italic}
.msg.lisa .src{display:block;margin-top:7px;font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ai);opacity:.8}
.msg.think{display:flex;gap:4px;align-items:center;padding:14px 16px}
.msg.think i{width:6px;height:6px;border-radius:50%;background:#B7ADD6;display:block;animation:tdot 1.1s infinite}
.msg.think i:nth-child(2){animation-delay:.16s}
.msg.think i:nth-child(3){animation-delay:.32s}
@keyframes tdot{0%,100%{opacity:.3;transform:translateY(0)}50%{opacity:1;transform:translateY(-3px)}}

.chat-form{flex:0 0 auto;display:flex;gap:9px;padding:14px 22px 24px;max-width:720px;margin:0 auto;width:100%}
.chat-form input{
  flex:1 1 auto;min-width:0;font:inherit;font-size:14px;padding:11px 16px;border-radius:999px;
  border:1px solid var(--hair);background:#FAFAFC;outline:none;transition:border-color .12s,background-color .12s;
}
.chat-form input:focus{border-color:color-mix(in srgb,var(--ai) 40%,transparent);background:#fff}
.chat-form button{
  width:42px;height:42px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;color:#fff;
  background:linear-gradient(120deg,#6E3AED,#A24BE8);transition:transform .12s var(--spring);
}
.chat-form button:active{transform:scale(.93)}
.chat-form button svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}

.toast{
  position:fixed;left:50%;bottom:26px;transform:translate(-50%,14px);z-index:80;
  padding:11px 20px;border-radius:999px;background:var(--fg);color:#fff;font-size:13px;font-weight:600;
  opacity:0;pointer-events:none;transition:opacity .2s ease-out,transform .2s var(--spring);
}
.toast.on{opacity:1;transform:translate(-50%,0)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
}

/* ── pantallas bajas ── */
/* Las 14 filas de la barra, apretadas para que entren sin scroll en más
   ventanas. 32 de fila y 1 de gap: 14×33 + 28 de separador + 12 de padding
   = 502px.

   🔴 EL BREAKPOINT ES 880 Y NO 800, Y ESE NÚMERO SALE DE UNA MEDICIÓN.
   Lo que le queda a la lista es la ventana menos TODO lo demás de la
   columna, y ese «todo lo demás» son 331px:

       masthead      64   (--mast-h)
       padding-top   16   (.sidebar)
       el pie       179   (redes 26 + los 9 links en dos columnas 113 + gaps)
       el dock       72   (--dock-h · `body.docked .sidebar{bottom:...}`)

   El dock es el que se olvida siempre —no está arriba de la lista, está
   abajo, y se la come igual— y por eso la primera cuenta daba 512px libres
   a 760 de ventana cuando en realidad hay 429.

   Con 331 fijos: apretada entra desde 833px de ventana, sin apretar desde
   877. Con el breakpoint en 800 quedaba un hueco entre 800 y 877 donde la
   lista NO se apretaba y tampoco entraba. En 880 no hay hueco, y el umbral
   queda en 833 — mejor que los 841 de las 13 filas de antes.

   Abajo de 833 scrollea con su fade, que es para lo que está. No hay
   geometría de filas que lo arregle: lo que habría que achicar es el pie. */
@media (max-height:880px){
  .topic-list a{padding:4px 12px}
  .topic-list{gap:1px}
  .topic-list .sep{padding:9px 12px 4px}
}

/* La card baja, pero no vuelve a los 248 de antes: con dos líneas de
   titular y dos de bajada, abajo de ~250 la bajada se corta y la card
   pierde justo lo que este cambio le vino a dar. */
@media (max-height:760px){
  :root{--band-h:84px}
  .card{height:272px}
}
@media (max-height:660px){
  .card{height:252px}
}

/* ── mobile ── */
/* ═══════════════════════════════════════════════════════════════
   MOBILE · ≤900px
   ───────────────────────────────────────────────────────────────
   El shell de escritorio se desarma: la sidebar deja de ser columna,
   `.feed` deja de ser el scroller y scrollea el documento. Eso último
   es una decisión, no una consecuencia: un scroller interno en un
   teléfono se come el colapso de la barra de URL —unos 90px de
   viewport en cada pantalla— y rompe el overscroll nativo.

   Como el scroller cambia, `bindFeed()` en app.js ata el handler a los
   dos. Antes lo ataba sólo a `.feed`, que en mobile no scrollea nunca:
   por eso `body.past-brief` no se prendía, el scroll-spy estaba muerto
   y la banda tenía que quedar `position:static` —arriba del hero— para
   verse. Buena parte de lo que había acá abajo era compensación de ese
   bug. Ya no hace falta.

   Lo que NO cambia respecto de escritorio, a propósito:
     · el CTA de descarga nunca se va (masthead sticky)
     · nunca hay login, ni onboarding, ni modal de bienvenida
     · los botones de arrepentimiento y baja son visibles en la home
   ═══════════════════════════════════════════════════════════════ */
@media (max-width:900px){

  /* ── tokens ──────────────────────────────────────────────────
     `--band-h` en mobile es TODO el chrome que va debajo de la
     masthead: el riel de categorías (48) + la banda (40). De ahí
     cuelgan `scroll-padding-top` y el `scroll-margin-top` de las
     secciones, así que tiene que ser el total, no sólo la banda.
     `--dock-h` lo escribe `sincronizarDock()` con el alto medido.
     `--chat-w` y `--orb` no las usa nadie: no se redeclaran.        */
  :root{
    --side-w:0px;
    --band-h:92px;
    --gut:16px;                       /* un solo canal lateral, antes había tres */
    /* Dos filas (título + transporte). Es sólo el respaldo hasta que
       `sincronizarDock()` mida el alto REAL: ya pasó que la variable decía
       106, el dock medía 108, y tapaba 2px del mosaico. */
    --dock-h:108px;
    /* Lo que el dock FLOTANTE reserva abajo: su alto, más el margen de
       flotación, más el indicador de home, más lo que tape la barra del
       navegador. Todo lo que se corre para dejarle lugar —el feed, el
       lector, el chat, la hoja de capítulos y el FAB— lee esto y no
       `--dock-h` a secas, que ahora se queda corto por el margen. */
    --dock-res:calc(var(--dock-h) + var(--vv-bottom) + env(safe-area-inset-bottom) + 26px);
    /* Lo mismo, pero para el `padding-bottom` del <body>: igual que
       `--dock-res` salvo que lee `--vv-doc` en vez de `--vv-bottom`, y esa
       única diferencia es todo el punto. Lo fijo puede moverse en cada
       cuadro; el largo del documento, no. */
    --dock-doc:calc(var(--dock-h) + var(--vv-doc) + env(safe-area-inset-bottom) + 26px);
  }

  html{scroll-padding-top:calc(var(--mast-h) + var(--band-h) + 12px)}
  html,body{overflow:visible;height:auto}
  body{overscroll-behavior-y:none}

  /* El fondo NO se mueve con el lector, el chat o los capítulos abiertos.
     En escritorio esto sale gratis porque el body es `overflow:hidden`;
     acá scrollea el documento y sin esto abrías una nota, scrolleabas,
     cerrabas y estabas en otro lado. La posición la guarda y restaura
     `trabarFondo()`. */
  body.trabado{position:fixed;left:0;right:0;width:100%;overflow:hidden}

  /* el dock es fijo: sin esto tapa el final del footer, que es
     justo donde viven los botones obligatorios */
  body.docked{padding-bottom:var(--dock-doc)}
  /* la sidebar es un riel fijo arriba: el `bottom:var(--dock-h)` de
     escritorio (línea 853) no tiene nada que hacer acá */
  body.docked .sidebar{bottom:auto}

  /* ── masthead ────────────────────────────────────────────────
     TRES columnas siempre, porque el chip del player ahora también está
     siempre. Esto reemplaza el arreglo anterior —dos pistas en la landing
     y tres en el portal— que existía justamente porque el chip aparecía y
     desaparecía: con la pista siempre ocupada, desaparece de paso el salto
     de 8px que daban los botones de tienda al entrar al portal.

     El aire es poco y hay que respetarlo: en 360px las tres columnas suman
     ~346px de contenido sobre 328 de ancho útil. La válvula es el nombre
     de la marca, que es lo único que puede achicarse sin perder función
     (el logo queda, y es lo que identifica). Sin esta válvula el chip
     empujaba los botones de tienda fuera de la pantalla — que es el
     problema que el comentario viejo describía. */
  /* 🔴 LA TERCERA PISTA ES `1fr`, NO `auto`, Y AHÍ ESTABA EL CHIP CORRIDO.
     Con `minmax(0,1fr) auto auto` la pista de la derecha medía lo que
     miden las dos tiendas y TODO el sobrante caía en la primera, o sea que
     el chip quedaba centrado en el espacio que sobraba y no en la
     pantalla. Medido a 375px: el centro del chip daba 208 contra los 187,5
     del viewport — 20px a la derecha, y como es negro sobre blanco la
     asimetría salta a la vista.

     Con las dos pistas laterales en `1fr` reparten el sobrante en partes
     iguales y la del medio queda centrada en el viewport por construcción.
     Es la misma fórmula que escritorio (línea 130), que nunca tuvo el
     problema. No se usa `position:absolute` para centrarlo —que era el
     arreglo obvio— porque el chip saldría del flujo y en 360px se
     superpondría con las tiendas: acá el aire sobra por 14px, no más.

     La válvula sigue siendo `.brand-name`, que se recorta con ellipsis
     antes de empujar a nadie: `minmax(0,…)` es lo que la habilita. */
  .masthead{
    position:fixed;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
    padding:0 var(--gut);gap:8px;
  }
  .brand-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* 🔴 EL RESERVE ES TODO EL CHROME FIJO, NO SÓLO LA MASTHEAD.
     Arriba hay tres cosas fijas, una abajo de la otra: la masthead
     (`--mast-h`), el riel de categorías (52) y la banda de sección (40).
     Las dos últimas son justo lo que vale `--band-h` en mobile. Reservar
     sólo `--mast-h` dejaba 92px de feed abajo de la banda.
     No se veía porque el bloque legal que subía a mobile —133px de links,
     arriba de todo— hacía de espaciador sin querer. Al sacarlo (era el bug
     de orden del doc 03 §1) el agujero quedó a la vista, así que el
     reserve va explícito y no depende de que arriba haya algo.

     Los 24 son el aire de llegada, el equivalente de los 48 de escritorio:
     acá sobra menos alto y la banda mide 40 en vez de 92. Con 12 el título
     de la sección quedaba pegado al borde de abajo de la banda — se leía,
     pero apretado, y era aire que hasta ayer ponía de rebote el bloque
     legal que subía. */
  .feed{padding-top:calc(var(--mast-h) + var(--band-h) + 24px)}
  .edition,.cta-line{display:none}
  /* 34px era el ícono entero y también el área táctil. Ahora el área
     es 44 (mínimo de Apple) y el ícono sigue midiendo 34. */
  .store{width:44px;height:44px;padding:5px}
  .masthead-cta{gap:2px}

  /* El chip del player, también desde el arranque. En mobile el
     reproductor que importa es el dock —ancho completo, abajo, siempre—,
     así que el chip acá es un atajo de vuelta al brief, no el control
     principal: por eso va sin etiqueta, sólo con el tiempo que queda.
     `min-height:44px` es el mínimo táctil de Apple y no se negocia. */
  .player-chip{display:inline-flex;padding:9px 13px;min-height:44px;flex:0 0 auto}
  .player-chip .lbl{display:none}
  .player-chip .dur{font-size:12px}

  /* ── el chrome del portal: dos rieles fijos bajo la masthead ──
     Sólo con `past-brief`. En la landing no hay nada de esto: la
     primera pantalla es el hero y nada más. Ese era el bug de la
     banda dibujándose arriba del título.                            */
  .sidebar{
    position:fixed;top:var(--mast-h);left:0;right:0;bottom:auto;
    width:auto;height:52px;z-index:40;
    display:block;padding:0;
    background:rgba(255,255,255,.97);
    -webkit-backdrop-filter:blur(14px) saturate(180%);backdrop-filter:blur(14px) saturate(180%);
    box-shadow:0 1px 0 0 var(--hair);
    opacity:0;pointer-events:none;
  }
  body.past-brief .sidebar{opacity:1;pointer-events:auto}
  /* En mobile la sidebar es un riel horizontal de 52px: el pie (redes +
     los diez links) no entra ahí ni tendría sentido. Se apaga y punto —
     el bloque legal completo, con los dos botones obligatorios arriba de
     todo, vive en `.foot`. */
  .sidebar .sidebar-foot{display:none}

  /* el riel de categorías: horizontal, con scroll propio */
  .topic-list{
    flex-direction:row;align-items:center;gap:2px;height:52px;
    padding:0 var(--gut);
    overflow:auto hidden;                    /* antes quedaba auto/auto y scrolleaba vertical */
    scroll-snap-type:none;                   /* mandatory se peleaba con scrollIntoView() */
    -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);
    mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);
  }
  /* la entrada escalonada es de la columna vertical: acá no va */
  .topic-list li{transform:none;opacity:1;transition:none;flex:0 0 auto}
  .topic-list li{transition-delay:0s!important}
  .topic-list .sep,.topic-list a .count{display:none}
  .topic-list a{min-height:44px;padding:0 10px;font-size:14px;border-radius:12px}
  .topic-list a .tic{width:24px;height:24px}
  .topic-list a .tic svg{width:14px;height:14px}

  /* ── la banda ────────────────────────────────────────────────
     Queda FIJA, como en escritorio, debajo del riel de categorías.
     El `position:static` que tenía era el parche que la dibujaba
     arriba del hero. */
  .section-band{
    top:calc(var(--mast-h) + 52px);left:0;right:0;height:40px;
    transform:none;
  }
  .section-band::before{bottom:-20px}
  .section-band::before{
    -webkit-mask-image:linear-gradient(#000 calc(100% - 20px),transparent);
    mask-image:linear-gradient(#000 calc(100% - 20px),transparent);
  }
  /* Título y filtros comparten el alto: en Popular el riel de filtros
     repetiría las mismas categorías que el riel de arriba, así que ahí
     se muestra el título con la fecha, que sí agrega algo. Adentro de
     una categoría manda el riel de subcategorías.
     `body.en-popular` lo pone `updateHeader()`. */
  .band-title{height:40px;margin:0}
  .band-title h1{padding:0 var(--gut);font-size:19px;letter-spacing:-.02em}
  .band-title h1 .when{font-size:11.5px}
  /* El scroller es el `ul`, igual que en escritorio. Antes acá scrolleaba
     el RIEL y el `ul` iba en absoluto con ancho de contenido; ahora que el
     `ul` también scrollea, dejar los dos daba dos scrollers anidados y el
     arrastre movía el de afuera. El centrado vertical tampoco hace falta:
     lo hace el `align-items:center` de la regla de escritorio. */
  .filter-rail{position:absolute;inset:0;height:40px;padding:0}
  .filter-rail ul{padding:0 var(--gut)}
  .filter-rail ul:not(.current){display:none}
  .filter-rail button{padding:8px 13px;min-height:36px}
  body.en-popular .filter-rail{display:none}
  body:not(.en-popular) .band-title{display:none}

  /* ── el scroller ─────────────────────────────────────────────
     `.feed` deja de ser fijo y de scrollear: scrollea el documento. */
  .feed{position:static;inset:auto;overflow:visible;scroll-snap-type:none}
  .feed-inner{padding:0 var(--gut)}

  /* ── EL PIE LEGAL YA NO SUBE ─────────────────────────────────
     🔴 ACÁ HABÍA UN BLOQUE DE 133px CON DIEZ LINKS, ARRIBA DE LA PRIMERA
     NOTICIA. Este era el orden de `.feed-inner` en un teléfono:

         1 · .legales-m   133px   ← Sobre Lisa · Ayuda · Contacto · Prensa ·
                                    Anunciantes · Términos · Privacidad ·
                                    Eliminar cuenta · Arrepentimiento · Baja
         2 · .ultimo      2.572px
         3 · .portal      56.318px
         4 · .foot          528px ← LOS MISMOS DIEZ LINKS OTRA VEZ

     O sea: apenas terminaba el header, y antes de ver una sola noticia,
     aparecía el aviso legal. Y los diez volvían abajo.

     Se llegó a probar una versión intermedia —una tira de una línea con
     sólo Arrepentimiento y Baja— y también se sacó: en un teléfono
     cualquier cosa entre el header y la primera noticia es el producto
     empezando con letra chica. Los dos botones van en el footer y nada
     más, que es donde los espera cualquiera que los busque.

     Con esto se fue `ubicarLegales()` de app.js. El pie de la sidebar se
     queda donde nació, adentro de `#sidebar`, y lo apaga
     `.sidebar .sidebar-foot{display:none}` unas líneas más arriba. */

  /* ── el portal ───────────────────────────────────────────────── */
  .portal,.ultimo,.foot{padding-left:0;padding-right:0;max-width:none}
  .portal{padding-top:10px}
  .row{flex-flow:column}

  /* ── VIRTUALIZAR, PERO QUE LO HAGA EL NAVEGADOR ────────────────
     `content-visibility:auto` saltea estilo, layout y pintado de las
     secciones que están fuera de la pantalla: el navegador las dibuja
     recién cuando te acercás. Es la virtualización de siempre —renderizar
     lo visible ±1— pero nativa, sin sacar y poner nodos a mano desde el
     scroll, que es de donde salen los saltos y el scroll que se va solo.

     Va SÓLO en mobile, y por dos razones: es donde el feed medía 59.000px,
     y `content-visibility` implica `contain:paint`, o sea que recorta lo
     que se salga de la sección. En escritorio eso comería la sombra de
     hover de las cards del borde (`0 16px 42px`); en un teléfono no hay
     hover que comer.

     🔴 `contain-intrinsic-size` es una ESTIMACIÓN, y de ahí sale el único
     efecto raro: mientras una sección no se dibujó, el alto del documento
     es aproximado y la barra de scroll se reacomoda al pasar por ella. La
     palabra `auto` es lo que lo mantiene chico —el navegador se acuerda
     del alto real de lo que ya dibujó y sólo estima lo que nunca vio—, y
     el salto entre categorías se afina solo (`afinarSalto()` en app.js).
     Los 2000px son una sección de asomo: encabezado + seis cards de 300 +
     los gaps + el botón. */
  /* `--alto-est` lo escribe `estimarAlto()` por sección, con la cuenta de
     sus cards. El 2000 de respaldo es una sección de asomo (6 cards + el
     botón) y sólo se usa si el JS no llegó a correr. Ver el comentario de
     `estimarAlto()`: un estimado que miente 2.500px hace que el documento
     crezca de golpe abajo del dedo mientras scrolleás. */
  .section{content-visibility:auto;contain-intrinsic-size:auto var(--alto-est, 2000px)}

  /* De ancho completo: en un teléfono un botón de 200px al borde
     izquierdo de una columna de cards de ancho completo se lee como que
     quedó suelto. */
  .ver-todas{display:flex;justify-content:center;width:100%;min-height:48px;margin-top:14px}
  .card,.card-sm,.card-med,.card-lg,.card-xl,.card-xxl,.card-xxxl{flex:none;height:300px}
  /* En una columna no hay último renglón que emparejar: los rellenos no
     tienen nada que hacer y ocuparían tres slots vacíos. */
  .rellena{display:none}
  /* Acá vivían tres reglas que volvían a prender la bajada, el medio y la
     hora en `sm`: en mobile la card es de ancho completo y esconderlos no
     tenía sentido. Ahora tampoco lo tiene en escritorio —el piso de la
     escala es 300px— así que la regla base ya no los esconde y estas eran
     un `display` repitiendo el `display` que ya estaba. */

  /* ── el footer ───────────────────────────────────────────────
     La grilla de dos columnas nunca se reseteaba: a 358px dejaba el
     link de Defensa del Consumidor alineado a la derecha en 133px. */
  /* `align-items:flex-start` dejaba a `.foot-nav` con el ancho de su
     contenido y entonces se desbordaba en vez de envolver: "Contacto"
     salía cortado por el borde derecho. */
  .foot-top{flex-direction:column;align-items:stretch;gap:6px}
  .foot-legal{flex-direction:column;align-items:flex-start;gap:0}
  .foot-legal .sep{display:none}          /* apilados no separa nada, queda huérfano */
  .foot-nav{margin-left:0;gap:6px 18px}
  .foot-bot{display:block;padding-top:16px}
  .foot-right{text-align:left;justify-self:start;margin-top:12px}
  .foot-legal a,.foot-nav a{display:inline-flex;align-items:center;min-height:44px}
  .foot-bot a{display:inline-flex;align-items:center;min-height:36px}

  /* ═══ EL DOCK: FLOTA, Y CUÁNTO SE LO DICE `visualViewport` ══════
     ⚠ Esto sólo se ve en un iPhone real, nunca en un panel de escritorio.

     En iOS Safari un `position:fixed; bottom:0` se ancla al viewport de
     LAYOUT, que se extiende por DEBAJO de la barra de herramientas del
     navegador: los controles quedan tapados y no se pueden tocar.
     `env(safe-area-inset-bottom)` NO alcanza —eso cubre el indicador de
     home, no la barra— y un margen fijo tampoco, porque la barra aparece,
     desaparece y cambia de alto según cómo scrolleás. La única medida
     confiable es `--vv-bottom`, que escribe `seguirViewport()` (app.js).

     Encima el dock deja de estar pegado al borde: flota a 10px de los
     lados y 14px del piso. Es defensa en profundidad — si `visualViewport`
     se equivoca por unos pixeles, el margen los absorbe.

     Y se muestran los SIETE controles, en dos filas: título arriba,
     transporte abajo. Sacar controles del reproductor para que entre el
     layout es resolver el problema equivocado. Las dos filas ya no
     desfasan `--dock-h`: `sincronizarDock()` mide el alto real. */
  .dock{
    left:10px;right:10px;
    bottom:calc(var(--vv-bottom) + env(safe-area-inset-bottom) + 14px);
    /* El alto lo manda el CONTENIDO, y `sincronizarDock()` lo mide y lo
       escribe en `--dock-h`. Un `min-height:var(--dock-h)` acá sería un
       lazo: la variable saldría de sí misma y el dock quedaría clavado en
       el valor de respaldo en vez de en el real. */
    height:auto;
    flex-wrap:wrap;gap:6px;padding:9px 12px;
    border-radius:20px;
    background:rgba(255,255,255,.96);
    box-shadow:0 8px 30px rgba(4,4,4,.18),0 0 0 1px rgba(4,4,4,.06);
    /* Por encima del lector y del chat, que están en 70. En escritorio el
       dock va abajo (62) porque esas dos capas le dejan su franja libre con
       `bottom:var(--dock-h)`; acá llegan hasta el piso y el dock les pasa
       por arriba, como el mini-player de cualquier app de audio. */
    z-index:71;
  }
  /* para entrar desde abajo sin quedar cortado no alcanza el 100%: hay que
     sumarle todo lo que ahora lo separa del piso */
  .dock{transform:translateY(calc(100% + var(--vv-bottom) + env(safe-area-inset-bottom) + 24px))}
  body.docked .dock{transform:none}
  /* el riel de progreso entra adentro de la tarjeta y sigue su filo */
  .dock-prog{top:6px;left:12px;right:12px;height:4px;border-radius:99px;overflow:hidden}

  /* el título se lleva la primera fila entera; los controles, la segunda.
     Tocar la ficha abre los capítulos — el patrón de mini-player de
     cualquier app de audio. El handler está en `bindChrome()`. */
  .dock-left{flex:1 1 100%;min-width:0;gap:9px;cursor:pointer}
  .dock-orb{width:34px;height:34px}
  .dock-orb svg{width:18px;height:18px}
  .dock-tx b{font-size:13.5px}
  .dock-tx small{font-size:11px}
  /* El tiempo no entra en la fila de controles: se muda al subtítulo, y en
     RESTANTES. El miedo que salió del test de 10 usuarios no era el audio,
     era no saber cuánto falta. */
  .dock-mini{display:inline}
  .dock-time{display:none}
  /* 🔴 LA VELOCIDAD ENTRA EN EL LUGAR DE «LEER ESTA NOTA», Y ESE CAMBIO SE
     GANÓ CON NÚMEROS. De las 63 piezas del programa sólo 12 tienen hecho
     que abrir: el 34% del tiempo ese botón no tiene a dónde ir, y el
     capítulo de Titulares NUNCA lo tiene —sus `fuentes_del_hecho` son cinco
     notas de cinco categorías, no un hecho—, que es justo donde arranca el
     dock al cargar la página. O sea que el primer botón que ve el visitante
     es el único que no hace nada. La velocidad sirve siempre.
     Sigue estando en escritorio, donde la fila tiene lugar para los dos.
     «Capítulos» se queda: la ficha abre el mismo panel, pero eso es un
     gesto sin cartel — el ícono es la única señal de que existe. */
  #dRead{display:none}
  /* `width` lo pisa la regla de arriba (un id le gana a `.dctl-sm`), así que
     el piso de 44px hay que pedirlo por `min-width` o el botón se achica al
     ancho del texto y pierde el blanco táctil. */
  #dSpeed{order:1;min-width:44px}

  /* `flex:1 1 0` + `min-width:0`: el transporte cede ancho antes de mandar
     `.dock-right` a una TERCERA fila — esos dos botones terminarían
     apoyados contra el piso, justo donde pega la barra del navegador. */
  .dock-mid{flex:1 1 0;min-width:0;justify-content:space-between;gap:0}
  .dock-right{flex:0 0 auto;margin-left:10px;gap:5px}
  /* 44px es el mínimo táctil de Apple, sin excepciones: había seis de siete
     por debajo (en 36 y 32). 7 × 44 = 308px de fila y el dock dispone de
     331 en el iPhone más chico en circulación (375px de ancho). */
  .dctl,.dctl-sm{width:44px;height:44px}
  .dctl-play{width:46px;height:46px}
  .dctl svg{width:22px;height:22px}
  .dctl-sm svg{width:20px;height:20px}
  /* El riel de progreso mide 3px: imposible de agarrar con el dedo. Se
     agranda el área, no la línea, y el handler no cambia.
     OJO: el área tiene que quedar ADENTRO del dock y POR DEBAJO de los
     controles. Con `top:-9px;height:22px` se comía los primeros 7px de
     play, capítulos y leer — y como el click de `#dockProg` es un
     `seekTime()`, tocar el borde de arriba de Pausa saltaba el audio a la
     mitad del brief en vez de pausar. */
  .dock-prog::after{content:"";position:absolute;left:0;right:0;top:-6px;height:14px}
  /* la ficha ahora también es clickeable (abre capítulos): se defiende del
     área del riel igual que los controles */
  .dctl,.dock-left{position:relative;z-index:1}

  /* ── lector, chat, capítulos ─────────────────────────────────── */
  /* El lector y el chat llegan hasta el piso y el dock les pasa POR ENCIMA,
     que es el patrón de mini-player de cualquier app de audio. Cortarlos a
     la altura del dock dejaba una franja de 13px por donde se veía el
     mosaico de atrás — con el dock pegado al borde eso no existía, pero
     ahora que flota, sí. Lo que sube no es la superficie: es el contenido,
     más abajo. */
  .reader,.chat{inset:var(--mast-h) 0 var(--vv-bottom) 0}
  /* hay que decirlo con la misma especificidad que las reglas de escritorio
     (líneas 1103 y 1118), que si no las pegan al techo del dock otra vez */
  body.docked .reader,body.docked .chat{bottom:var(--vv-bottom)}
  .rd-bar,.chat-head{height:48px;padding:0 var(--gut)}
  .rd-back,.chat-back{min-height:40px}
  .rd-esc{display:none}                    /* no hay tecla Esc en un teléfono */
  .rd-scroll{padding:0 var(--gut) 60px}
  body.docked .rd-scroll{padding-bottom:calc(var(--dock-res) + 24px)}
  .rd-art{padding-top:18px}
  .rd-body h1{font-size:27px;line-height:1.15}
  .chat-hist-btn{min-height:40px;min-width:40px}
  .chat-form{padding:10px var(--gut) calc(10px + env(safe-area-inset-bottom))}
  /* la barra de escribir no puede quedar debajo del dock: es donde va el
     dedo, y el dock le taparía el campo entero */
  body.docked .chat-form{padding-bottom:calc(var(--dock-res) + 10px)}
  .chat-form button{width:44px;height:44px}
  /* Hoja al pie, no tarjeta centrada: es donde llega el pulgar.
     OJO con el `transform:translate(-50%,…)` de escritorio, que va con el
     `left:50%`: si se cambia el anclaje y no se resetea el transform, la
     hoja se dibuja media pantalla a la izquierda. Pasó. */
  .chapters{
    top:auto;bottom:var(--vv-bottom);left:0;right:0;width:auto;
    max-height:calc(100dvh - var(--mast-h) - 40px - var(--dock-res));
    border-radius:20px 20px 0 0;
    transform:translateY(14px);
  }
  body.panel-open .chapters{transform:none}
  body.docked .chapters{bottom:var(--dock-res)}
  .ch-head{padding:16px var(--gut) 12px}
  .ch-close{width:40px;height:40px}
  .ch-body{padding:0 10px 8px}
  .ch-foot{padding-bottom:calc(14px + env(safe-area-inset-bottom))}

  /* ── el FAB ──────────────────────────────────────────────────── */
  .fab{
    padding:0;width:52px;height:52px;justify-content:center;
    right:16px;bottom:calc(var(--vv-bottom) + env(safe-area-inset-bottom) + 20px);
  }
  /* con el dock puesto, el FAB sube por encima de él */
  body.docked .fab{bottom:calc(var(--dock-res) + 8px)}
  .fab span{display:none}
  /* con la hoja de capítulos abierta el FAB le tapaba el pie. Ya se
     esconde con el lector y con el chat; en mobile la hoja ocupa toda
     la parte de abajo, así que corresponde lo mismo. */
  body.panel-open .fab{opacity:0;pointer-events:none;transform:scale(.9)}
}

/* ── el dock, en pantallas muy angostas ─────────────────────────
   Los SIETE controles se muestran siempre: 7 × 44px = 308px de fila, que
   entra en cualquier iPhone (el más chico en circulación tiene 375px de
   ancho, y el dock dispone de 331). Lo que hay que impedir es que el flex
   mande `.dock-right` a una TERCERA fila, porque esos dos botones terminan
   apoyados contra el piso — justo donde la barra del navegador los tapa.
   Recién abajo de 350px, que no es ningún iPhone actual, se achican los
   blancos táctiles. Antes que eso, no se saca ningún control. */
@media (max-width:350px){
  .dock .dctl,.dock .dctl-sm{width:40px;height:40px}
  #dSpeed{min-width:40px}   /* acompaña al resto de la fila cuando se achica */
  .dock-right{margin-left:6px;gap:2px}
}
