/* Acá había un `@import` de Google Fonts (Open Sans 400/500/600/700 + IBM Plex
   Mono 400/500). Se sacó el 2026-08-14 por dos motivos, y el segundo es el que
   manda:

   1. Un `@import` adentro de una hoja que ya bloquea el render encadena DOS
      viajes en serie: el navegador baja global.css, recién ahí descubre el CSS
      de Google, lo baja, y recién ahí las fuentes. Aunque las fuentes hicieran
      falta, la forma correcta es un `<link>` en el `<head>` — nunca un
      `@import` acá adentro.
   2. No hacían falta. La tipografía del sitio es DATO del portal, no código
      del molde: sale de `--site-font-body`/`-heading`/`-nav`, que cada portal
      redefine en `portals.header_scripts` (AnnurTV usa Fraunces, Newsreader e
      Inter, y las carga él mismo). De las dos familias que pedía este import,
      Open Sans no la dibujaba nadie e IBM Plex Mono se usaba en UN lugar: el
      `.boot-label` de la pantalla de carga, nueve caracteres que además
      desaparecen. Lighthouse lo contaba como recurso que bloquea el render.

   Si un portal quiere una fuente puntual, la carga desde su `header_scripts`
   como ya hace AnnurTV. El molde no le impone una descarga de terceros a
   ninguna instancia. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  /* `min-height` y no `height`. Con `height: 100%` el `<body>` mide UNA ventana
     aunque el documento tenga 14.654 px, y eso rompe cualquier
     `position: sticky` que cuelgue de acá: un sticky solo se desplaza dentro de
     la caja de su contenedor, así que el menú del sitio se despegaba al pasar
     la primera pantalla. Reportado el 2026-08-12 sobre AnnurTV con el scroll
     infinito prendido, que es donde más se nota (la página crece sin límite),
     pero pasaba igual en cualquier nota larga. */
  min-height: 100%;
  /* `--site-*`: base default = look actual de Pupila NEWS, redefinible
     por portal vía `portals.header_scripts`. */
  font-family: var(--site-font-body, 'Open Sans', sans-serif);
  background: var(--site-bg, oklch(0.98 0.003 250));
  color: var(--site-text, oklch(0.2 0.01 250));
}

/* `100vh` y no `100%`: con el `<body>` en `min-height` el porcentaje no tiene
   contra qué resolver. Es el contenedor del que cuelga el shell del sitio en
   las páginas de nota, así que de su alto depende hasta dónde llega el menú
   pegajoso. */
#app { min-height: 100vh; }

/* Scrollbar chica y unificada en todos los navegadores/elementos. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border, oklch(0.8 0.008 250)) transparent;
}

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border, oklch(0.8 0.008 250));
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-faint, oklch(0.62 0.01 250)); }
::-webkit-scrollbar-corner { background: transparent; }

input::placeholder { color: oklch(0.62 0.01 250); }

button { font-family: inherit; cursor: pointer; }

input:focus, textarea:focus, select:focus {
  outline: none !important;
  border-color: var(--accent, oklch(0.55 0.18 250)) !important;
}

/* Pedido explícito: "no debe tener loader ni nada... debe cargar
   inmediatamente" — con el caché persistente de compilados (ver
   lib/svelte-loader.js) la carga real ya es casi instantánea en visitas
   repetidas, pero SIEMPRE hay una primera carga (o una con caché fría)
   que tarda algo. En vez de mostrar el loader desde el frame 0 (parpadeo
   feo incluso cuando la carga es rápida), queda invisible los primeros
   400ms — si la app ya montó para entonces, `main.js` vacía `#app` antes
   de que el loader llegue a hacerse visible y el usuario nunca lo ve. Si
   de verdad tarda más que eso, aparece como fallback (mejor que una
   pantalla en blanco que parece colgada). */
.boot-loader {
  /* Antes heredaba el alto de un `#app` que medía exactamente una ventana. Con
     `#app` creciendo con el contenido, un `height: 100%` acá se resuelve contra
     `auto` y el loader colapsa: se fija a la ventana, que es lo que quiere
     decir. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  opacity: 0;
  animation: boot-loader-reveal 0s 400ms forwards;
}

@keyframes boot-loader-reveal {
  to { opacity: 1; }
}

.boot-bar {
  width: 160px;
  height: 4px;
  border-radius: 2px;
  background: oklch(0.9 0.005 250);
  overflow: hidden;
  position: relative;
}

.boot-bar-fill {
  position: absolute;
  top: 0;
  left: -40%;
  height: 100%;
  width: 40%;
  border-radius: 2px;
  background: linear-gradient(90deg, oklch(0.5 0.19 320), oklch(0.55 0.18 250), oklch(0.55 0.15 200));
  animation: boot-bar-slide 1.1s ease-in-out infinite;
}

.boot-label {
  /* Monoespaciada del sistema: era 'IBM Plex Mono' y obligaba a bajar dos
     pesos de Google en el camino crítico para un texto que se ve mientras
     carga y después se va. Ver el comentario del tope del archivo. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: oklch(0.55 0.012 250);
  letter-spacing: 0.02em;
}

@keyframes boot-bar-slide {
  0% { left: -40%; }
  100% { left: 100%; }
}

/* ------------------------------------------------------------------ */
/* Contenido público — el HTML de acá abajo lo arma BlockRenderer del  */
/* lado del backend (ver PublicSiteController) e inyecta PageView.svelte */
/* con `{@html}` tal cual; estas clases son las que ya usan los bloques  */
/* reales cargados en Configuración → Bloques (Bienvenida/Últimas       */
/* notas/Detalle de nota/páginas estáticas) — ver PUPILA_CMS.md.        */
/* ------------------------------------------------------------------ */

.hero {
  padding: 40px 0 32px;
  margin-bottom: 8px;
  border-bottom: 1px solid oklch(0.9 0.005 250);
}
.hero-title {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.03em;
  margin: 0 0 10px;
  color: oklch(0.2 0.01 250);
}
.hero-lead {
  font-size: 15.5px;
  line-height: 1.6;
  color: oklch(0.48 0.012 250);
  max-width: 640px;
  margin: 0;
}

.note-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 20px;
  margin-top: 24px;
}

.note-card {
  border: 1px solid oklch(0.9 0.005 250);
  border-radius: 8px;
  padding: 18px;
  background: oklch(1 0 0);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.note-card-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.note-chip {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: oklch(0.55 0.18 250);
  background: oklch(0.94 0.03 250);
  border-radius: 3px;
  padding: 3px 7px;
}
.note-card-title { font-size: 16.5px; font-weight: 700; line-height: 1.35; margin: 0; }
.note-card-title a { color: oklch(0.2 0.01 250); text-decoration: none; }
.note-card-title a:hover { color: oklch(0.55 0.18 250); }
.note-card-bajada { font-size: 13.5px; line-height: 1.5; color: oklch(0.5 0.012 250); margin: 0; }
.note-card-bajada p, .note-card-bajada ul { margin: 0; padding-left: 18px; }

.note-detail { display: flex; flex-direction: column; gap: 14px; }
.note-detail-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.note-detail-bajada { font-size: 17px; line-height: 1.55; color: oklch(0.35 0.012 250); font-weight: 500; }
.note-detail-autores { font-size: 12.5px; color: oklch(0.55 0.012 250); font-weight: 600; margin: 0; }
.note-detail-autores a { color: oklch(0.55 0.18 250); text-decoration: none; }
.note-detail-autores a:hover { text-decoration: underline; }
.note-detail-cuerpo { font-size: 15.5px; line-height: 1.75; color: oklch(0.25 0.01 250); }
.note-detail-cuerpo p { margin: 0 0 16px; }
.note-detail-cuerpo ul, .note-detail-cuerpo ol { margin: 0 0 16px; padding-left: 22px; }

/* Relacionadas/comentarios — el `{{each}}` de BlockRenderer no borra el
   contenedor cuando no hay filas (no hay `{{if}}` real, ver
   `resolveIfBlocks()`), así que la lista vacía se oculta con `:has()`
   en vez de con lógica del lado del servidor. */
.note-related { margin-top: 8px; }
.note-related:has(.note-related-list:empty) { display: none; }
.note-related-title { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: oklch(0.45 0.012 250); margin: 0 0 8px; }
.note-related-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.note-related-list a { color: oklch(0.2 0.01 250); font-weight: 600; font-size: 14px; text-decoration: none; }
.note-related-list a:hover { color: oklch(0.55 0.18 250); }

.note-comments { margin-top: 24px; border-top: 1px solid oklch(0.9 0.005 250); padding-top: 20px; }
.note-comments-title { font-size: 16px; font-weight: 700; margin: 0 0 12px; }
.note-comments-list { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.note-comments-list li { border: 1px solid oklch(0.92 0.005 250); border-radius: 6px; padding: 10px 12px; }
.note-comments-list strong { display: block; font-size: 12.5px; margin-bottom: 4px; }
.note-comments-list p { margin: 0; font-size: 13.5px; color: oklch(0.35 0.012 250); line-height: 1.5; }
.note-comment-form { display: flex; flex-direction: column; gap: 8px; max-width: 420px; }
.note-comment-form input, .note-comment-form textarea {
  border: 1px solid oklch(0.85 0.008 250); border-radius: 4px; padding: 8px 10px; font-size: 13px; font-family: inherit;
}
.note-comment-form textarea { min-height: 70px; resize: vertical; }
.note-comment-form button {
  border: none; background: oklch(0.55 0.18 250); color: white; border-radius: 4px; padding: 8px 14px;
  font-size: 13px; font-weight: 600; align-self: flex-start;
}
.ajax-form-status { font-size: 12px; color: oklch(0.5 0.15 150); margin: 4px 0 0; }
.ajax-form-status.ajax-form-error { color: oklch(0.5 0.2 25); }

/* Paginador de los listados de sección y de tag — lo emite
   SiteRenderer::paginadorHtml() como HTML plano (no es un Bloque), y este CSS
   lo cargan las DOS vías de render: el SPA y el HTML pre-renderizado de
   SitePageHtml, que linkean este mismo archivo.
   Los colores usan `var(--site-*)` con fallback, igual que el resto del shell,
   para que un portal con su propia identidad en `header_scripts` lo pise sin
   tocar nada acá. */
.pn-pagination {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 6px; margin: 32px 0 8px; font-size: 13.5px;
  font-family: var(--site-font-body, inherit);
}
.pn-pagination-pages { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.pn-pagination-page, .pn-pagination-step {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 34px; height: 34px; padding: 0 10px;
  border: 1px solid oklch(0.9 0.005 250); border-radius: 4px;
  color: var(--site-text, oklch(0.2 0.01 250)); text-decoration: none; font-weight: 600;
}
.pn-pagination-page:hover, .pn-pagination-step:hover {
  border-color: var(--site-accent, oklch(0.55 0.18 250));
  color: var(--site-accent, oklch(0.55 0.18 250));
}
.pn-pagination-page.is-current {
  background: var(--site-accent, oklch(0.55 0.18 250));
  border-color: var(--site-accent, oklch(0.55 0.18 250));
  color: white; cursor: default;
}
/* "Anterior" en la página 1 y "Siguiente" en la última se siguen renderizando
   —como <span>, no como link— para que la barra no cambie de ancho ni salten
   los números al llegar a un extremo. */
.pn-pagination-step.is-disabled {
  color: var(--site-text-muted, oklch(0.65 0.012 250));
  border-color: oklch(0.94 0.004 250); cursor: default;
}
.pn-pagination-gap { color: var(--site-text-muted, oklch(0.6 0.012 250)); padding: 0 2px; }
/* La elipsis de un paginador maquetado en el Bloque: ahí es el MISMO elemento
   que los números (`{{each_paginas}}` repite un solo trozo de HTML), así que se
   marca con la clase en vez de con otra etiqueta. No lleva a ninguna parte. */
.pn-pagination-page.is-gap {
  border-color: transparent; background: none; pointer-events: none;
  color: var(--site-text-muted, oklch(0.6 0.012 250));
}
@media (max-width: 520px) {
  /* En pantalla chica los números sobran: con "Anterior/Siguiente" alcanza
     para recorrer, y 322 páginas no entran de ninguna forma. */
  .pn-pagination-pages { display: none; }
}

/* Sidebar (nota/autor) — ver PublicSiteController::syntheticTemplate(). */
.content-with-sidebar { display: grid; grid-template-columns: 1fr; gap: 32px; }
@media (min-width: 820px) { .content-with-sidebar { grid-template-columns: 2fr 1fr; align-items: start; } }
.content-sidebar { display: flex; flex-direction: column; gap: 24px; }
.sidebar-widget { border: 1px solid oklch(0.9 0.005 250); border-radius: 8px; padding: 16px; }
.sidebar-widget-title { font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: oklch(0.45 0.012 250); margin: 0 0 10px; }
.sidebar-recent-list { display: flex; flex-direction: column; gap: 8px; }
.sidebar-recent-link { color: oklch(0.2 0.01 250); font-size: 13.5px; font-weight: 600; text-decoration: none; }
.sidebar-recent-link:hover { color: oklch(0.55 0.18 250); }
.sidebar-newsletter-lead { font-size: 12.5px; color: oklch(0.5 0.012 250); margin: 0 0 10px; }
.sidebar-newsletter-form { display: flex; flex-direction: column; gap: 8px; }
.sidebar-newsletter-form input {
  border: 1px solid oklch(0.85 0.008 250); border-radius: 4px; padding: 8px 10px; font-size: 13px; font-family: inherit;
}
.sidebar-newsletter-form button {
  border: none; background: oklch(0.2 0.01 250); color: white; border-radius: 4px; padding: 8px 14px; font-size: 13px; font-weight: 600;
}
.sidebar-newsletter-success { font-size: 12.5px; color: oklch(0.5 0.15 150); font-weight: 600; }

/* Perfil de autor. */
.author-profile { text-align: center; padding-bottom: 20px; border-bottom: 1px solid oklch(0.9 0.005 250); margin-bottom: 20px; }
.author-avatar {
  width: 84px; height: 84px; border-radius: 50%; margin: 0 auto 14px; object-fit: cover;
  display: flex; align-items: center; justify-content: center;
}
.author-avatar-initial { background: oklch(0.55 0.18 250); color: white; font-size: 32px; font-weight: 700; }
.author-profile-name { font-size: 22px; font-weight: 700; margin: 0 0 8px; }
.author-profile-bio { font-size: 14px; color: oklch(0.45 0.012 250); line-height: 1.6; max-width: 520px; margin: 0 auto 12px; }
.author-profile-social { display: flex; justify-content: center; gap: 8px; margin-bottom: 10px; }
.author-social-icon {
  display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid oklch(0.85 0.008 250); color: oklch(0.45 0.012 250); font-size: 10.5px; font-weight: 700; text-decoration: none;
}
.author-social-icon:hover { border-color: oklch(0.55 0.18 250); color: oklch(0.55 0.18 250); }
.author-profile-count { font-size: 12.5px; color: oklch(0.55 0.012 250); margin: 0; }

.note-paywall {
  border: 1px dashed oklch(0.8 0.02 80);
  background: oklch(0.97 0.02 80);
  border-radius: 8px;
  padding: 24px;
}
.note-paywall p { margin: 0 0 10px; font-size: 14.5px; line-height: 1.6; color: oklch(0.4 0.012 250); }
.note-paywall-notice { font-weight: 700; color: oklch(0.5 0.15 80) !important; margin: 0 !important; }

.page-static { font-size: 15px; line-height: 1.75; color: oklch(0.3 0.012 250); }
.page-static p { margin: 0 0 14px; }
.contact-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.contact-list strong { color: oklch(0.2 0.01 250); }

.page-category { padding-top: 4px; }

/* --- Imagen destacada de las notas -----------------------------------------
   Los Bloques genéricos (Detalle de nota, Últimas notas, Card de nota, Notas
   del autor, Notas de sección) no emitían `{{nota.imagen_destacada}}` — el
   frontend mostraba las notas sin foto aunque las 30 la tuvieran cargada.
   Estas clases son el contenedor con proporción fija que necesita la imagen
   para no romper el layout: sin `aspect-ratio` + `object-fit`, una foto de
   1600px de ancho entra a tamaño natural y desarma la tarjeta.

   Van acá, en el CSS genérico del frontend, y NO en el `header_scripts` de un
   portal: son parte del componente, no de la identidad visual de un cliente
   (mismo criterio que `.note-card`/`.note-grid`, que ya vivían acá). Un portal
   que quiera otra proporción la pisa desde su `header_scripts`. */
.note-card-media,
.note-detail-media {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 5px;
  background: oklch(0.95 0.005 250);
}
.note-card-media { margin-bottom: 2px; }
.note-card-media img,
.note-detail-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Red de seguridad: una nota (o un autor) sin imagen cargada emitía
   `<img src="">`, que el navegador dibuja como ícono de imagen rota. El motor
   de tokens no tiene condicionales reales (`resolveIfBlocks()` solo borra los
   marcadores), así que no se puede evitar emitir el tag — pero sí que se vea.
   Caso concreto: los 6 autores sin avatar de la portada. */
img[src=""] { display: none; }


/* === Shell del sitio (header + footer) ==========================
   Movido acá desde los <style> scoped de los componentes Header/Footer (ya
   borrados: el shell ahora son los Bloques "Cabecera del sitio"/"Pie del
   sitio"): el
   HTML pre-renderizado que genera StaticSiteGenerator no pasa por Svelte,
   así que un estilo scoped no existiría ahí y el shell se vería sin
   formato. Mismas reglas, mismas variables `--site-*`, mismo fallback.
   ============================================================== */

/* --- shell: cabecera --- */
/* Pedido explícito: "todo es configurable por backed" — este shell es
   el mismo para cualquier portal (ver App.svelte), así que su look NO
   puede quedar hardcodeado acá: cada color/tipografía sale de una
   custom property `--site-*`, con el look actual de Pupila NEWS como
   fallback (así un portal que no cargó nada en
   `portals.header_scripts` se ve exactamente igual que hasta ahora).
   Un portal con identidad propia redefine estas variables en un
   `:root{...}` dentro de su propio `header_scripts` — ver
   PublicSiteController::nav()/App.svelte. */
.site-header {
  border-bottom: 1px solid var(--site-border, oklch(0.9 0.005 250));
  background: var(--site-header-bg, oklch(1 0 0));
  position: sticky;
  top: 0;
  z-index: 10;
}
.site-header-inner {
  max-width: var(--site-max-width, 960px);
  margin: 0 auto;
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.site-brand {
  font-family: var(--site-font-heading, inherit);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--site-text, oklch(0.2 0.01 250));
  text-decoration: none;
  display: flex;
  align-items: center;
}
/* Alto fijo y ancho automático: el logo que suba cada portal puede tener
   cualquier proporción. `--site-logo-height` deja ajustarlo desde el
   `header_scripts` del portal sin tocar este componente, igual que el
   resto de las `--site-*`. */
.site-logo {
  display: block;
  height: var(--site-logo-height, 34px);
  width: auto;
  max-width: 260px;
  object-fit: contain;
}
.site-nav {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.site-nav a {
  font-family: var(--site-font-nav, inherit);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--site-text-muted, oklch(0.45 0.012 250));
  text-decoration: none;
  padding: 4px 0;
  border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--site-text, oklch(0.2 0.01 250)); }
.site-nav a[aria-current="page"] {
  color: var(--site-accent, oklch(0.55 0.18 250));
  border-bottom-color: var(--site-accent, oklch(0.55 0.18 250));
}

/* --- shell: pie --- */
/* Mismo criterio de variables `--site-*` que la cabecera — ver el
   comentario ahí. */
.site-footer {
  border-top: 1px solid var(--site-border, oklch(0.9 0.005 250));
  background: var(--site-footer-bg, transparent);
  margin-top: 48px;
  padding: 24px;
  text-align: center;
}
.site-footer p {
  font-family: var(--site-font-body, inherit);
  margin: 4px 0;
  font-size: 12.5px;
  color: var(--site-footer-text, oklch(0.55 0.012 250));
}
.site-footer-note { color: var(--site-footer-text-muted, oklch(0.65 0.01 250)); font-size: 11.5px; }

/* Marca del sitio sin logo cargado: el Bloque "Cabecera del sitio" emite
   SIEMPRE la imagen y el nombre, porque el motor de tokens no tiene
   condicionales reales (`resolveIfBlocks()` solo borra los marcadores). El
   nombre queda oculto salvo que la imagen venga vacía — que es el caso de un
   portal sin logo (The Java Paper). Sin esto ese header quedaba SIN marca:
   `<img src="">` no dibuja nada y no había fallback. La regla general
   `img[src=""] { display: none }` de más arriba oculta la imagen vacía. */
.site-brand-name { display: none; }
.site-logo[src=""] + .site-brand-name { display: inline; }

/* Contenido atenuado mientras llega la página siguiente.
   ------------------------------------------------------
   Pedido: al paginar una sección el scroll salta arriba pero las notas tardan
   en cambiar, y "parece un error". Atenuar da la señal de que algo está
   pasando sin vaciar la pantalla.

   `pointer-events: none` evita el doble clic impaciente sobre un paginador que
   ya está trabajando — que además pediría dos páginas y mostraría la que
   conteste última, no la que el lector eligió.

   La transición es asimétrica a propósito: entra en 120ms (suave, acompaña la
   espera) y sale en 60ms, para que el contenido nuevo aparezca despierto y no
   dé la sensación de seguir cargando. */
.page-view-cargando,
.page-shell-cargando {
  opacity: 0.45;
  pointer-events: none;
  cursor: progress;
  transition: opacity 120ms ease-out;
}

.page-view,
.page-shell {
  transition: opacity 60ms ease-in;
}

@media (prefers-reduced-motion: reduce) {
  .page-view-cargando,
  .page-shell-cargando,
  .page-view,
  .page-shell { transition: none; }
}

/* Un slot que centra su contenido Y puede scrollear en horizontal esconde
   parte del contenido donde NADIE puede llegar.
   ------------------------------------------------------------------------
   Reportado el 2026-08-14 sobre AnnurTV: "en mobile el menu queda mal, no se
   ven todas las opciones". El menú del sitio es un slot con
   `flex justify-center … overflow-x-auto`. Cuando los items no entran,
   `justify-content: center` reparte el espacio que FALTA hacia los dos lados,
   así que el contenido se sale por izquierda y por derecha en partes iguales.
   Lo que se sale por la derecha se alcanza scrolleando; lo que se sale por la
   izquierda queda ANTES del origen del scroll (`scrollLeft: 0`) y no hay gesto
   que lo alcance. Medido en un teléfono de 375 px: de seis secciones se veían
   dos, y "Portada" y "Mundo" eran inalcanzables — desde el menú no se podía
   volver a la portada.

   El arreglo son márgenes `auto` en los extremos en vez de `justify-content`.
   Hacen exactamente lo mismo cuando sobra espacio (absorben el sobrante por
   partes iguales: centrado idéntico al de hoy), pero cuando falta espacio la
   spec de flexbox los resuelve a cero en lugar de repartir un negativo. El
   contenido queda pegado al inicio y el scroll llega a todo.

   Va acotado con las dos clases juntas a propósito: `justify-center` marca que
   el autor de la plantilla PIDIÓ centrado, y `overflow-x-auto` que el
   contenedor puede desbordar. Un slot centrado que no scrollea, o uno
   alineado a la izquierda, no se tocan. Las tres clases suman más
   especificidad que el `.justify-center` de Tailwind, así que gana esta regla
   sin `!important`. */
.tpl-block-slot.justify-center.overflow-x-auto { justify-content: flex-start; }

/* Un bloque que se fija al hacer scroll (`position: sticky`) queda ENCERRADO
   en su slot: el slot es un div que mide exactamente lo mismo que él, y un
   sticky solo se mueve dentro de su contenedor. O sea que se queda quieto y
   parece que la propiedad no funcionara. Reportado en InfoNegocios el
   2026-08-26 con un menú `sticky top-0` de Tailwind.

   `display: contents` saca al slot del layout —sus hijos pasan a colgar
   directamente del contenedor de la Plantilla—, así que el sticky se mueve
   dentro de ese contenedor y no dentro de una caja de su mismo alto.

   OJO, esto NO alcanza solo: el sticky nunca puede salirse del elemento que lo
   contiene. Si el bloque está adentro de un `<header>` que mide lo que su
   contenido, va a quedar fijo mientras ese header esté en pantalla y se va a
   ir con él. Para que acompañe TODO el scroll, la propiedad va en un elemento
   cuyo contenedor sea la página entera — en la práctica, el `<header>` de la
   Plantilla.

   Se cubren las clases de los frameworks que ofrece el CMS más el `style`
   escrito a mano; una clase propia definida en la hoja del portal no se puede
   detectar desde acá. */
.tpl-block-slot:has(> .sticky),           /* Tailwind */
.tpl-block-slot:has(> .sticky-top),       /* Bootstrap 5 */
.tpl-block-slot:has(> .position-sticky),  /* Bootstrap 4 */
.tpl-block-slot:has(> [style*="sticky"]) {
  display: contents;
}
.tpl-block-slot.justify-center.overflow-x-auto > :first-child { margin-inline-start: auto; }
.tpl-block-slot.justify-center.overflow-x-auto > :last-child  { margin-inline-end: auto; }

/* Barra de compartir — la emite el token `{{compartir}}` de BlockRenderer
   (2026-08-14). Estos estilos son un DEFAULT sobrio para que funcione de
   entrada en cualquier instancia, no el diseño final: cada portal los pisa
   desde su `header_scripts`, que es donde vive su identidad visual. Por eso
   son colores neutros y no los de cada marca.

   `min-width`/`min-height` de 40px: es el tamaño mínimo de un objetivo táctil
   que recomienda WCAG, y estos botones se tocan sobre todo en un teléfono. */
.pupila-share {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 24px 0;
}

/* "Compartir:" delante de los botones. Sin esto, cuatro cuadraditos sueltos
   debajo de los tags no se leen como una acción — que es exactamente lo que
   pasó en la primera versión. */
.pupila-share::before {
  content: "Compartir";
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
  margin-right: 4px;
}

.pupila-share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40px es el objetivo táctil mínimo que recomienda WCAG, y estos botones se
     tocan sobre todo en un teléfono. */
  width: 40px;
  height: 40px;
  border-radius: 50%;
  text-decoration: none;
  color: #fff;
  background-color: var(--pupila-share-color, #555);
  transition: transform 120ms ease-out, filter 120ms ease-out;
}

.pupila-share-btn:hover,
.pupila-share-btn:focus-visible {
  filter: brightness(1.12);
  transform: translateY(-1px);
}

/* El glifo de texto del HTML (`f`, `𝕏`, `WA`, `TG`) queda como FALLBACK y se
   oculta cuando el ícono se puede dibujar. En la primera versión era lo único
   que había, y el resultado eran cuatro cajitas iguales a los tags de arriba:
   los botones funcionaban y nadie los reconocía como redes. */
.pupila-share-btn > span {
  display: block;
  width: 20px;
  height: 20px;
  background-color: currentColor;
  -webkit-mask: var(--pupila-share-icon) center / contain no-repeat;
  mask: var(--pupila-share-icon) center / contain no-repeat;
  font-size: 0;            /* esconde el texto cuando la máscara pinta */
  color: inherit;
}

/* Los íconos van en el CSS y no en el HTML a propósito: el volcado estático
   tiene 14.805 notas, y cuatro SVG inline por nota serían decenas de MB
   repetidos. Acá viajan UNA vez, en un archivo que además se cachea.
   Trazos de Simple Icons (CC0). */
.pupila-share-fb {
  --pupila-share-color: #1877f2;
  --pupila-share-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.96.93-1.96 1.89v2.25h3.33l-.53 3.49h-2.8V24C19.61 23.1 24 18.1 24 12.07"/></svg>');
}
.pupila-share-x {
  --pupila-share-color: #000;
  --pupila-share-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.9 1.15h3.68l-8.04 9.19L24 22.85h-7.41l-5.8-7.58-6.64 7.58H.47l8.6-9.83L0 1.15h7.59l5.24 6.93zm-1.29 19.5h2.04L6.49 3.24H4.3z"/></svg>');
}
.pupila-share-wa {
  --pupila-share-color: #25d366;
  --pupila-share-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.47 14.38c-.3-.15-1.76-.87-2.03-.97-.27-.1-.47-.15-.67.15-.2.29-.77.96-.94 1.16-.17.2-.35.22-.64.07-.3-.15-1.26-.46-2.4-1.48-.88-.79-1.48-1.76-1.66-2.06-.17-.3-.02-.46.13-.6.13-.14.3-.35.45-.53.15-.17.2-.3.3-.5.1-.2.05-.37-.02-.52-.08-.15-.67-1.61-.92-2.2-.24-.58-.49-.5-.67-.51h-.57c-.2 0-.52.07-.79.37-.27.3-1.04 1.01-1.04 2.48s1.07 2.87 1.22 3.07c.15.2 2.1 3.2 5.08 4.49.71.3 1.26.49 1.7.63.71.23 1.36.19 1.87.12.57-.09 1.76-.72 2-1.41.25-.7.25-1.29.17-1.41-.07-.12-.27-.2-.57-.35M12.05 21.8h-.02a9.8 9.8 0 0 1-4.99-1.37l-.36-.21-3.7.97.99-3.62-.24-.37a9.8 9.8 0 0 1-1.5-5.23c0-5.42 4.4-9.83 9.83-9.83a9.76 9.76 0 0 1 9.82 9.84c0 5.42-4.41 9.82-9.83 9.82M20.5 3.49A11.8 11.8 0 0 0 12.05 0C5.5 0 .17 5.33.17 11.88c0 2.1.55 4.14 1.6 5.95L.07 24l6.33-1.66a11.9 11.9 0 0 0 5.65 1.44h.01c6.55 0 11.88-5.33 11.88-11.88 0-3.17-1.24-6.15-3.48-8.4"/></svg>');
}
.pupila-share-tg {
  --pupila-share-color: #26a5e4;
  --pupila-share-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.94 24a12 12 0 1 0 0-24 12 12 0 0 0 0 24M5.49 11.74c3.5-1.53 5.84-2.53 7.01-3.02 3.34-1.39 4.03-1.63 4.48-1.64.1 0 .32.03.47.14.12.1.15.23.17.32.02.1.04.3.02.46-.18 1.9-.96 6.5-1.36 8.62-.17.9-.5 1.2-.82 1.23-.7.06-1.23-.46-1.9-.9-1.06-.7-1.66-1.13-2.68-1.81-1.19-.79-.42-1.22.26-1.93.18-.19 3.25-2.98 3.31-3.23.01-.04.01-.15-.06-.21s-.17-.04-.24-.02c-.1.02-1.79 1.14-5.05 3.35-.48.33-.91.49-1.3.48-.43-.01-1.25-.24-1.86-.44-.75-.24-1.35-.37-1.3-.79.03-.22.33-.44.9-.67"/></svg>');
}

@media (prefers-reduced-motion: reduce) {
  .pupila-share-btn { transition: none; }
}

/* Embebidos del cuerpo de una nota (2026-08-26) — los emite
   `App\Libraries\NoteEmbeds` al resolver `{{nota.cuerpo}}`: una card de otra
   nota, un espacio publicitario y el formulario de una encuesta.

   Mismo criterio que la barra de compartir de acá arriba: esto es un DEFAULT
   sobrio para que el embebido se vea entero en cualquier instancia desde el
   primer día, no el diseño final. Cada portal lo pisa desde sus
   `header_scripts`. Nada de colores de marca ni de tipografías propias. */
.pupila-embed {
  display: block;
  margin: 24px 0;
}

.pupila-embed-nota .pupila-embed-nota-link {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 12px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 6px;
  text-decoration: none;
  color: inherit;
}

.pupila-embed-nota-img {
  width: 140px;
  height: 92px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}

.pupila-embed-nota-texto {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.pupila-embed-nota-volanta {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.75;
}

.pupila-embed-nota-titulo {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.3;
}

/* En un teléfono, 140px de imagen dejan el título en dos palabras por
   renglón. */
@media (max-width: 520px) {
  .pupila-embed-nota .pupila-embed-nota-link { flex-direction: column; align-items: stretch; }
  .pupila-embed-nota-img { width: 100%; height: auto; aspect-ratio: 16 / 9; }
}

.pupila-embed-publicidad {
  position: relative;
  text-align: center;
}

.pupila-embed-publicidad img { max-width: 100%; height: auto; }

.pupila-embed-encuesta {
  padding: 16px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 6px;
}

.pupila-encuesta-pregunta {
  margin: 0 0 12px;
  font-weight: 700;
  font-size: 16px;
}

.pupila-encuesta-form { display: flex; flex-direction: column; gap: 8px; }

.pupila-encuesta-opcion-label {
  display: flex;
  gap: 8px;
  align-items: center;
  cursor: pointer;
}

.pupila-encuesta-votar {
  align-self: flex-start;
  margin-top: 6px;
  padding: 8px 18px;
  border: 1px solid currentColor;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* Los resultados los pinta `lib/encuesta.js` después de votar: la barra es un
   `<span>` vacío al que le fija el `width` en porcentaje. */
.pupila-encuesta-opcion {
  display: grid;
  grid-template-columns: 1fr 3fr auto;
  gap: 10px;
  align-items: center;
  margin-bottom: 8px;
}

.pupila-encuesta-barra {
  display: block;
  height: 10px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.pupila-encuesta-barra > span {
  display: block;
  height: 100%;
  background: currentColor;
  opacity: 0.55;
}

.pupila-encuesta-opcion[data-mi-voto] { font-weight: 700; }
.pupila-encuesta-opcion[data-mi-voto] .pupila-encuesta-barra > span { opacity: 0.9; }

.pupila-encuesta-total,
.pupila-encuesta-estado {
  font-size: 12.5px;
  opacity: 0.75;
}

/* Sesión del lector en el HTML de los Bloques.
   Estas reglas se quedan aunque ya NO haya JS que escriba `data-lector` en
   <html> (`lib/lectorUi.js` se borró el 2026-08-27 a pedido: "saca todo el js
   que está molestando"). Sin el atributo mandan las dos primeras: se ve el
   estado ANÓNIMO. Eso es lo que hace que un Bloque que declare los dos estados
   —el Top de InfoNegocios los tiene— muestre "Registrarme / Ingresar" y no los
   dos a la vez. Si el HTML de los Bloques no se toca, borrar esto rompe la
   cabecera; por eso quedan. */
html:not([data-lector="si"]) [data-si-lector] { display: none !important; }
html[data-lector="si"] [data-sin-lector] { display: none !important; }
html:not([data-lector-plan="si"]) [data-si-suscripto] { display: none !important; }
html[data-lector-plan="si"] [data-sin-suscripcion] { display: none !important; }

/* Scroll infinito del detalle de nota en el HTML PRE-RENDERIZADO
   (ver lib/scrollInfinito.js). Los mismos dos valores están en el <style> de
   PageView.svelte, para el SPA: ahí van con `:global` porque los nodos los crea
   `pintarHilo()` a mano y no llevan la clase de scope del componente. Acá hacen
   falta igual porque en el sitio servido no hay componente que los traiga —sin
   esto el separador entre notas desaparece y el disparador queda de alto cero,
   y el scroll infinito se lee como una sola nota larguísima. */
.next-note { border-top: 3px solid var(--site-border, oklch(0.9 0.01 250)); margin-top: 48px; padding-top: 40px; }
.next-sentinel { min-height: 1px; text-align: center; padding: 24px 0; }

/* Player de video propio (módulo Videos, `videoPlayer.js`). El `<video>` ocupa
   el ancho del contenedor; la capa del aviso va encima con el rótulo, el
   botón de saltar y el link de la campaña. */
.pupila-video { position: relative; width: 100%; background: #000; line-height: 0; }
.pupila-video video { display: block; width: 100%; height: auto; max-height: 80vh; background: #000; }
/* El aviso, encima del principal y del mismo tamaño: la caja no cambia de alto. */
.pupila-video-ad-el { position: absolute; inset: 0; width: 100%; height: 100% !important; max-height: none !important; object-fit: contain; cursor: pointer; z-index: 1; }
.pupila-video:not(.pupila-video-en-aviso) .pupila-video-ad-el, .pupila-video:not(.pupila-video-en-aviso) .pupila-video-ad { display: none !important; }
.pupila-video-ad { z-index: 2; }
.pupila-video-ad { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between; padding: 10px 12px; pointer-events: none; font: 600 12px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
.pupila-video-ad-rotulo { align-self: flex-start; background: rgba(0,0,0,.65); color: #fff; padding: 3px 8px; border-radius: 3px; letter-spacing: .06em; text-transform: uppercase; font-size: 10px; }
.pupila-video-ad-saltar { align-self: flex-end; pointer-events: auto; border: 1px solid rgba(255,255,255,.6); background: rgba(0,0,0,.65); color: #fff; padding: 8px 14px; border-radius: 3px; cursor: pointer; font: inherit; }
.pupila-video-ad-saltar:disabled { cursor: default; opacity: .8; }
.pupila-video-ad-link { position: absolute; left: 12px; bottom: 12px; pointer-events: auto; color: #fff; background: rgba(0,0,0,.65); padding: 6px 10px; border-radius: 3px; text-decoration: none; }
