/* ==========================================================================
   TOKENS — Sistema de diseño "Centro de Operaciones"
   ========================================================================== */
:root {
  /* superficie — noche operativa */
  --bg: #080B12;
  --bg-rgb: 8, 11, 18; /* mismo color que --bg, en formato "R, G, B" — se
    necesita aparte porque rgba() no puede leer los canales de un color hex
    guardado en variable directamente. Se usa en .topbar para el fondo
    traslúcido con blur; así SÍ cambia con el tema en vez de quedar fijo
    en negro sin importar qué tema esté activo. */
  --bg-hex: #0B0F18;
  --surface: #10151F;
  --surface-2: #171E2C;
  --surface-raised: #1C2434;
  --border: #232B3D;
  --border-soft: #1A2130;

  /* texto */
  --text: #EDF1F7;
  --text-muted: #8D99B0;
  --text-faint: #566078;

  /* acentos operativos */
  --signal: #35E5B4;       /* verde señal — sistema activo / seguro */
  --signal-dim: #1B7A62;
  --alert: #FF6A52;        /* rojo-ámbar — alerta / relevancia alta */
  --alert-dim: #9A3F2E;

  /* texto que va ENCIMA de un fondo de color (botones, badges activos) — a
     propósito NO es lo mismo que --bg, aunque en modo oscuro coincidan. Si
     esto fuera var(--bg) directo, se invertiría solo al cambiar de tema y el
     texto quedaría ilegible sobre el botón (ver modo claro abajo). */
  --on-accent: #080B12;

  /* clasificación por categoría */
  --cat-vulnerabilidades: #F0A93A;
  --cat-ransomware: #FF5C5C;
  --cat-filtracion: #9B8CF0;
  --cat-phishing: #34C7C2;
  --cat-inteligencia: #5B93F5;
  --cat-herramientas: #35E5B4;
  --cat-regulacion: #B9C2D4;
  --cat-aula: #E8B4E0;
  --cat-otro: #6B7690;

  --font-display: 'Space Grotesk', sans-serif;
  --font-body: 'Inter', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;

  --radius: 4px;
  --radius-lg: 8px;
  --max-width: 1240px;
}

/* ==========================================================================
   MODO CLARO — mismo set de variables, valores invertidos. Se activa con
   [data-theme="light"] en <html> (ver theme.js). Los acentos (--signal,
   --alert, --cat-*) se oscurecen un poco respecto al modo oscuro: los mismos
   tonos que se ven bien como texto/ícono sobre fondo casi negro pierden
   contraste sobre blanco si no se ajustan.
   ========================================================================== */
[data-theme="light"] {
  --bg: #F7F9FC;
  --bg-rgb: 247, 249, 252; /* mismo color que --bg de arriba, en "R, G, B" */
  --bg-hex: #EEF1F6;
  --surface: #FFFFFF;
  --surface-2: #F1F4F9;
  --surface-raised: #FFFFFF;
  --border: #DCE2EC;
  --border-soft: #E7EBF2;

  --text: #10141F;
  --text-muted: #565F72;
  --text-faint: #8891A0;

  --signal: #0E9F76;
  --signal-dim: #9EE8CE;
  --alert: #D6432A;
  --alert-dim: #F3C1B5;

  /* --on-accent se queda igual (near-black) en los dos temas a propósito —
     ver el comentario junto a su definición arriba. */

  --cat-vulnerabilidades: #B87816;
  --cat-ransomware: #D93F3F;
  --cat-filtracion: #7A68D9;
  --cat-phishing: #1D8F8A;
  --cat-inteligencia: #3968C4;
  --cat-herramientas: #0E9F76;
  --cat-regulacion: #5C6B85;
  --cat-aula: #B85FAC;
  --cat-otro: #6B7690;
}

/* ==========================================================================
   BASE
   ========================================================================== */
* { box-sizing: border-box; }
html { color-scheme: dark; }

/* Red de seguridad: si algo se desborda horizontalmente (una imagen sin
   redimensionar, texto sin cortar, un elemento flex/grid mal calculado),
   esto evita que arrastre a TODA la página a poder scrollearse hacia los
   lados — que es exactamente lo que le pasaba al topbar antes del fix de
   min-width en .cat-nav. No reemplaza corregir la causa real cuando se
   encuentre una, pero cubre casos que todavía no se han detectado.

   IMPORTANTE: es "clip", no "hidden". Con "hidden" en ambos (html Y body a
   la vez), el navegador calcula overflow-y como "auto" por debajo del agua
   (regla del spec de CSS: si un eje no es "visible" ni "clip", el otro eje
   deja de ser "visible" y pasa a "auto") — eso convierte a html/body en su
   propio contenedor de scroll, DISTINTO del scroll real de la página, y
   rompe position:sticky en cualquier descendiente (el topbar completo
   desaparece al hacer scroll en vez de quedarse fijo, en vez de solo dejar
   de "pegarse" — exactamente el bug reportado). "clip" evita ese cálculo
   por completo: sigue bloqueando el desborde horizontal, pero sin tocar el
   eje vertical ni el contenedor de scroll real. */
html, body { overflow-x: clip; max-width: 100%; }
img, svg { max-width: 100%; height: auto; }

body {
  margin: 0;
  background:
    radial-gradient(ellipse 900px 500px at 15% -10%, rgba(53, 229, 180, 0.05), transparent),
    radial-gradient(ellipse 700px 500px at 100% 0%, rgba(255, 106, 82, 0.05), transparent),
    var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}

.wrap { max-width: var(--max-width); margin: 0 auto; padding-inline: 24px; }

/* patrón hex/circuito de fondo, sutil */
.hex-field {
  position: absolute;
  inset: 0;
  opacity: 0.5;
  pointer-events: none;
  background-image:
    linear-gradient(var(--border-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--border-soft) 1px, transparent 1px);
  background-size: 42px 42px;
  -webkit-mask-image: linear-gradient(to bottom, black, transparent);
  mask-image: linear-gradient(to bottom, black, transparent);
}

/* ==========================================================================
   MASTHEAD
   ========================================================================== */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: rgba(var(--bg-rgb), 0.88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.topbar-strip {
  border-bottom: 1px solid var(--border-soft);
}
.topbar-strip-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 6px 24px;
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  text-transform: uppercase;
}
.topbar-strip-inner span.live {
  color: var(--signal);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.pulse-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--signal);
  box-shadow: 0 0 0 0 rgba(53,229,180,0.5);
  animation: pulse 2s infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(53,229,180,0.45); }
  70% { box-shadow: 0 0 0 6px rgba(53,229,180,0); }
  100% { box-shadow: 0 0 0 0 rgba(53,229,180,0); }
}

.topbar-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 14px 24px;
  display: flex;
  align-items: center;
  /* Antes esto lo resolvía .cat-nav-wrap con flex:1 en medio — ahora que el
     menú de categorías vive en su propia fila (.topbar-catnav, ver abajo),
     logo y topbar-actions son los únicos dos hijos, así que necesitan
     justify-content explícito para separarse a los extremos. */
  justify-content: space-between;
  gap: 28px;
}

.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}
.logo-mark {
  width: 30px; height: 30px;
  flex-shrink: 0;
  color: var(--signal);
}
.logo-text {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.01em;
  line-height: 1.1;
}
.logo-text small {
  display: block;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  text-transform: uppercase;
  margin-top: 1px;
}

.cat-nav {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  width: 100%;
  min-width: 0; /* crítico: sin esto, flexbox nunca encoge este elemento por
                   debajo del ancho de su contenido (todos los botones sin
                   envolver), y ESO desborda todo el topbar — y de ahí toda
                   la página — en vez de scrollear solo el menú internamente
                   como está pensado (overflow-x: auto, arriba). */
  /* Difumina el borde derecho por default — el izquierdo se activa dinámico
     (ver .cat-nav--scrolled más abajo, la agrega app.js) solo cuando ya te
     moviste hacia la derecha, para no cortar sin razón el primer botón
     ("Todo") cuando estás al inicio y no hay nada que difuminar de ese lado. */
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
}
.cat-nav::-webkit-scrollbar { display: none; }
/* Se agrega/quita por JS (initCatNavArrow) según la posición del scroll —
   difumina también el borde izquierdo una vez que ya no estás al inicio. */
.cat-nav.cat-nav--scrolled {
  mask-image: linear-gradient(to right, transparent 0, black 20px, black calc(100% - 28px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0, black 20px, black calc(100% - 28px), transparent 100%);
}

/* Segunda línea del header — ancho completo, dedicada solo al menú de
   categorías. Vive dentro de .topbar (ver arriba, position:sticky), así que
   se queda fija en pantalla junto con el resto del header al hacer scroll,
   sin necesitar su propio position:sticky. */
.topbar-catnav {
  border-top: 1px solid var(--border-soft);
  padding: 10px 24px;
}
.topbar-catnav .cat-nav-wrap {
  max-width: var(--max-width);
  margin: 0 auto;
}
/* Botones un poco más grandes que antes — al tener toda la fila para sí
   solo, ya no hace falta apretarlos para competir con la búsqueda y el
   botón de alertas. */
.topbar-catnav .cat-nav button { padding: 8px 14px; font-size: 12.5px; }

/* Contenedor de la flecha de scroll — envuelve a .cat-nav en index.html.
   La flecha en sí (.cat-nav-arrow) la muestra/oculta app.js según si de
   verdad hay contenido escondido a la derecha (scrollWidth > clientWidth). */
.cat-nav-wrap {
  position: relative;
  min-width: 0;
  display: flex;
}
.cat-nav-arrow {
  display: none; /* app.js lo cambia a flex cuando aplica */
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-raised);
  color: var(--text-muted);
  cursor: pointer;
  z-index: 2;
}
.cat-nav-arrow:hover { color: var(--text); border-color: var(--signal); }
.cat-nav-arrow svg { width: 12px; height: 12px; }
.cat-nav-arrow-right { right: 0; box-shadow: -8px 0 10px -2px var(--bg); }
.cat-nav-arrow-left { left: 0; box-shadow: 8px 0 10px -2px var(--bg); }

.cat-nav button {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.01em;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  padding: 7px 12px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 7px;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.cat-nav button:hover { color: var(--text); border-color: var(--dot-color, var(--border)); }
.cat-nav button.active {
  color: var(--on-accent);
  background: var(--dot-color, var(--signal));
  border-color: var(--dot-color, var(--signal));
  font-weight: 600;
}
.cat-nav button.active .cat-dot { background: var(--bg); }

.cat-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--dot-color, var(--text-faint));
  flex-shrink: 0;
}

.cat-icon { width: 13px; height: 13px; flex-shrink: 0; color: var(--dot-color, currentColor); }
.cat-nav button.active .cat-icon { color: var(--on-accent); }

/* ==========================================================================
   HERO — "Señal del día"
   ========================================================================== */
.hero {
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--border-soft);
}

.hero-inner {
  position: relative;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 40px 24px 32px;
}

/* Texto presente en el DOM para buscadores/lectores de pantalla, pero sin
   ocupar espacio visual — técnica estándar, no es contenido oculto tipo
   "cloaking" (el texto coincide con el <title> y meta description reales). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal);
  margin-bottom: 18px;
}
/* Antes era un ::after — pasó a elemento real porque ahora necesita algo
   DESPUÉS de él en la misma fila (.hero-intro, ver abajo); un ::after
   siempre es lo último, no puede tener hermanos después. */
.hero-eyebrow-line {
  flex: 1;
  height: 1px;
  min-width: 24px;
  background: linear-gradient(90deg, var(--signal-dim), transparent);
}
.scan-icon { width: 14px; height: 14px; flex-shrink: 0; }

/* Línea de contexto del sitio — qué es InfoSecDash, para quien llega por
   primera vez y todavía no sabe qué está viendo. Vive en la misma fila que
   "SEÑAL DEL DÍA", a la derecha, chica y discreta — así no le quita espacio
   vertical al carrusel de abajo. En pantallas angostas se esconde (ver
   media query más abajo): ahí el espacio ya es demasiado poco para las dos
   cosas en una sola línea, y la explicación completa sigue disponible en
   /metodologia.html.  */
.hero-intro {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  line-height: 1.4;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 46%;
  flex-shrink: 0;
}

/* Carrusel de "Señal del día" — .hero-inner ya está centrado (max-width +
   margin:0 auto, ver arriba), así que el carrusel hereda ese centrado sin
   necesitar nada aparte. Recuadro propio (fondo + borde), mismo lenguaje
   visual que las tarjetas del feed (.card) más abajo, en vez de flotar
   directo sobre el fondo degradado del hero. */
.hero-carousel {
  animation: heroCarruselFade 0.35s ease;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px 32px;
  /* Alto FIJO real (height, no min-height) — a propósito, para que el
     recuadro nunca cambie de tamaño entre una noticia y otra ni mueva los
     puntos de abajo. overflow:hidden es la red de seguridad final: en el
     caso extremo de un título tan largo que no deje nada de espacio para
     .hero-body-link, se recorta en vez de desbordar y romper el layout.
     Valor calculado a partir del CSS existente, no visto en vivo — si en la
     práctica sobra mucho aire o se ve apretado, es un solo número que ajustar. */
  height: 400px;
  overflow: hidden;
}
@keyframes heroCarruselFade {
  from { opacity: 0; }
  to { opacity: 1; }
}

.hero-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 20px;
}
.hero-dot {
  width: 8px; height: 8px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: var(--border);
  cursor: pointer;
  transition: background 0.2s, transform 0.2s;
}
.hero-dot:hover { background: var(--text-faint); }
.hero-dot.active {
  background: var(--signal);
  transform: scale(1.3);
}
/* Objetivo táctil más grande que el punto visual — 8px es difícil de tocar
   con precisión en celular, esto amplía el área de toque sin agrandar el
   punto en sí. */
.hero-dot { position: relative; }
.hero-dot::after {
  content: "";
  position: absolute;
  inset: -10px;
}

/* Layout de la noticia destacada, con grid-template-areas — permite
   reacomodar folio+badges / título / foto / resumen distinto en mobile sin
   tocar el HTML (ver heroHTML() en app.js: título y cuerpo son dos <a>
   separados justo para poder colocarlos en celdas de grid independientes).

   El recuadro completo (.hero-carousel, ver más abajo) tiene un ALTO FIJO
   real (height, no min-height) — a propósito, para que nunca cambie de
   tamaño entre una noticia y otra ni mueva los puntos de abajo. Dentro de
   ese alto fijo: el título SIEMPRE se muestra completo (nunca se corta),
   y es el RESUMEN el que se ajusta — ocupa lo que sobra después de folio+
   badges y título, ni una línea más. Eso se logra con grid-template-rows
   (title = auto, body = 1fr, el resumen es el único elemento "flexible") +
   .hero-body-link como columna flex donde p.summary es el que hace
   overflow:hidden sobre lo que no cabe.

   Trade-off honesto: si algún título llega a ser excepcionalmente largo
   (3+ líneas), el resumen puede quedar con muy poco o nada de espacio — es
   la consecuencia esperada de priorizar "título siempre completo" + "caja
   siempre del mismo tamaño" sobre "el resumen siempre se ve algo".

   Desktop: título y resumen en una columna, la foto ocupa toda la altura de
   la derecha. Sin imagen (mostrar_imagen_hero=false, o la noticia no tiene
   una guardada), .hero-layout--sin-imagen la reduce a una sola columna. */
.hero-layout {
  display: grid;
  height: 100%;
  grid-template-columns: 1fr minmax(220px, 300px);
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "top   top"
    "title media"
    "body  media";
  column-gap: 28px;
  align-items: start;
}
.hero-layout.hero-layout--sin-imagen {
  grid-template-columns: 1fr;
  grid-template-areas:
    "top"
    "title"
    "body";
}

.hero-top {
  grid-area: top;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 28px;
}

.hero-title-link {
  grid-area: title;
  position: relative;
  display: block;
  min-width: 0; /* evita que un título largo empuje la columna más ancha que su fr */
}
.hero-body-link {
  grid-area: body;
  position: relative;
  display: block;
  min-width: 0;
}

.hero-media { grid-area: media; }
.hero-img {
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  margin-bottom: 6px;
}
.hero-img img {
  width: 100%;
  height: 220px;
  object-fit: cover;
  display: block;
  transition: transform 0.3s ease, filter 0.3s ease;
}
.hero-img:hover img { transform: scale(1.015); filter: brightness(1.04); }
.hero-img-attrib {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
}
.hero-img-attrib a { color: var(--text-muted); }
.hero-img-attrib a:hover { color: var(--signal); }

.report-id {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

.badge-relevancia {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--alert);
  border: 1px solid var(--alert-dim);
  background: rgba(255, 106, 82, 0.08);
  padding: 3px 9px;
  border-radius: 999px;
  text-transform: uppercase;
}

/* ==========================================================================
   BETA 2 — clasificación IFF (Amigo/Enemigo), badge KEV y Contexto Defensor
   ========================================================================== */

.badge-group { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Amigo = --signal (verde, "sistema activo/seguro"), Enemigo = --alert (rojo, "alerta") —
   reutiliza los mismos tokens de color que ya existen, sin inventar una paleta nueva. */
.badge-iff {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  padding: 3px 9px;
  border-radius: 999px;
  text-transform: uppercase;
}
.badge-iff-amigo {
  color: var(--signal);
  border: 1px solid var(--signal-dim);
  background: rgba(53, 229, 180, 0.08);
}
.badge-iff-enemigo {
  color: var(--alert);
  border: 1px solid var(--alert-dim);
  background: rgba(255, 106, 82, 0.08);
}

.badge-kev {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--on-accent);
  background: var(--alert);
  padding: 3px 9px 3px 7px;
  border-radius: 999px;
  text-transform: uppercase;
  font-weight: 600;
}
.badge-kev .cat-icon { width: 12px; height: 12px; }

/* Versión compacta para las tarjetas de lista — solo el ícono, sin texto, para no
   competir por espacio con el título en una tarjeta chica. */
.badge-kev-mini {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--alert);
  color: var(--on-accent);
}
.badge-kev-mini .cat-icon { width: 11px; height: 11px; }

/* Beta 2 (continuación) — badge de severidad CVSS, 4 niveles con colores estándar
   de la industria: crítica/alta en rojo-ámbar, media en ámbar, baja en gris neutro. */
.badge-cvss {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 3px 9px;
  border-radius: 999px;
}
.cvss-critica { color: #fff; background: var(--alert); }
.cvss-alta    { color: var(--alert); border: 1px solid var(--alert-dim); background: rgba(255, 106, 82, 0.08); }
.cvss-media   { color: var(--cat-vulnerabilidades); border: 1px solid var(--cat-vulnerabilidades); background: rgba(240, 169, 58, 0.08); }
.cvss-baja    { color: var(--text-muted); border: 1px solid var(--border); background: transparent; }

.badge-cvss-mini {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
}
.badge-cvss-mini.cvss-critica { color: #fff; background: var(--alert); }
.badge-cvss-mini.cvss-alta    { color: var(--alert); background: rgba(255, 106, 82, 0.12); }

.card-top-row { display: flex; align-items: center; justify-content: space-between; }
.card-top-badges { display: flex; align-items: center; gap: 6px; }

/* Ícono compacto que marca las noticias que traen Ruta de Defensa — mismo
   tamaño/forma que .badge-kev-mini para que se vean como una familia, pero
   en verde de marca (--signal) en vez de rojo-alerta, porque esto no es una
   señal de peligro sino de "aquí hay guía práctica para actuar". */
.badge-ruta-defensa-mini {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--signal);
  color: var(--on-accent);
  flex-shrink: 0;
}
.badge-ruta-defensa-mini .cat-icon { width: 11px; height: 11px; }

/* Punto de color simple para tarjetas de lista cuando no hay KEV pero sí clasificación IFF —
   más discreto que el badge de texto completo del hero/detalle. */
.badge-iff-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.badge-iff-dot-amigo { background: var(--signal); }
.badge-iff-dot-enemigo { background: var(--alert); }

.iff-razon {
  font-size: 13.5px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: -6px 0 16px;
}
.iff-razon strong { color: var(--text); }

/* Campo corto estilo Axios ("por qué te debería importar", pendiente #14
   de la lista de ideas) — a propósito más llamativo que .iff-razon: es el
   único texto de la noticia que lleva borde de color completo (no punteado
   como .advertencia) y fondo con el acento de marca, porque es la pieza
   editorial más importante de la tarjeta, la que responde "y a mí qué". */
.por-que-importa {
  margin: 14px 0 18px;
  padding: 12px 14px;
  border-left: 3px solid var(--signal);
  background: rgba(53, 229, 180, 0.06);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
}
.por-que-importa-etiqueta {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--signal);
  margin-bottom: 4px;
}

/* Botón de "escuchar" — Web Speech API del navegador, ver voz.js. Mismo
   lenguaje visual que .badge-relevancia (pastilla con borde), pero clicable
   y con un estado "activo" (leyendo ahora mismo) que lo tiñe con el color
   de acento del sitio, para que sea obvio que algo está pasando. */
.btn-escuchar {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 16px;
  margin: 4px 0 18px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.btn-escuchar:hover { border-color: var(--signal-dim); color: var(--text); }
.btn-escuchar.activo { border-color: var(--signal); color: var(--signal); }
.btn-escuchar .cat-icon { width: 16px; height: 16px; }

/* Recuadro de corrección. Deliberadamente distinto de .defensor-box (que usa
   el verde --signal): una corrección debe leerse como una advertencia, no como
   un consejo. Se coloca ARRIBA de la Ruta de Defensa y del cuerpo para que
   quien ya leyó la versión anterior se entere antes de volver a leer.
   Ver la sección 6 de politica-editorial.html. */
.correccion-box {
  border: 1px solid var(--alert-dim);
  background: rgba(255, 92, 92, 0.06);
  border-left: 3px solid var(--alert);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  margin-bottom: 20px;
}
.correccion-title {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--alert);
  margin: 0 0 6px;
}
.correccion-box p:not(.correccion-title) {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text);
}

.defensor-box {
  border: 1px solid var(--signal-dim);
  background: rgba(53, 229, 180, 0.06);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  margin-bottom: 20px;
}
.defensor-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  color: var(--signal);
  margin: 0 0 6px;
}
.defensor-title .cat-icon { width: 15px; height: 15px; }
.defensor-box p:not(.defensor-title) {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
  margin: 0;
}

.hero-title-link h2 {
  font-family: var(--font-display);
  font-weight: 700;
  /* Antes clamp(28px, 4.6vw, 46px) — más chico ahora que el carrusel vive en
     un recuadro con padding propio: deja ver más del resumen de un vistazo,
     sin tener que hacer scroll para llegar a él. */
  font-size: clamp(22px, 3.4vw, 34px);
  line-height: 1.12;
  margin: 0;
  letter-spacing: -0.015em;
  max-width: 26ch;
  /* Vuelve a tener tope de 2 líneas — "título siempre completo, sin límite"
     sonaba mejor, pero en la práctica hacía que el presupuesto de alto del
     recuadro fuera impredecible (un título de 3 líneas le dejaba muy poco
     espacio al resumen), y eso es justo lo que producía el corte a media
     letra que reportaste: el resumen (flex:1) terminaba con un alto que no
     era múltiplo exacto de una línea. Con el título topado, el presupuesto
     de alto vuelve a ser predecible. Un título de más de 2 líneas se corta
     con "…" — se ve mejor que medio título, y bastante menos común que un
     resumen cortado a la mitad. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hero-title-link:hover h2 { color: var(--signal); }

.hero-body-link p.summary {
  color: var(--text-muted);
  font-size: 16.5px;
  line-height: 1.65;
  max-width: 68ch;
  margin: 12px 0 0;
  /* Vuelve a un tope FIJO de líneas (con min-height reservando ese mismo
     espacio) en vez de flex:1 puro — flex:1 le daba al resumen cualquier
     alto sobrante, casi nunca múltiplo exacto de una línea, y overflow:hidden
     cortaba justo a la mitad de esa última línea ("medias letras"). Con un
     número fijo de líneas, -webkit-line-clamp SIEMPRE corta entre líneas
     completas — nunca a la mitad de una — y el min-height a juego asegura
     que también quepan sin que el título (ahora topado a 2 líneas, ver
     arriba) empuje al recuadro completo a desbordar. 4 líneas, como se
     pidió — con el título ya predecible (2 líneas tope), 380px de
     .hero-carousel alcanza para las dos cosas juntas. */
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 6.6em;
}
/* Solo aparece cuando el resumen se cortó (ver truncarResumen() en app.js) —
   todo el bloque ya es clicable (.hero-body-link), esto es nada más la
   pista visual de que hay más texto detrás del link. */
.hero-ver-completa {
  display: inline-block;
  color: var(--signal);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}
.hero-body-link:hover .hero-ver-completa { text-decoration: underline; }

.meta-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-faint);
  /* Fuera del carrusel (tarjetas del feed) este min-height es solo un piso
     razonable de alto. Dentro del carrusel, .hero-body-link (ver arriba) es
     flex en columna con p.summary como único elemento flex:1 — meta-row
     necesita flex-shrink:0 para que NUNCA sea meta-row el que ceda espacio,
     siempre el resumen. */
  min-height: 38px;
  flex-shrink: 0;
}

.meta-source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.source-favicon {
  width: 14px; height: 14px;
  border-radius: 3px;
  flex-shrink: 0;
  background: var(--surface-2);
}

.cat-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dot-color, var(--text-muted));
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tag {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-muted);
  padding: 2px 8px;
  border-radius: 999px;
}

/* Pendiente #11 — mismo look que .tag, pero es un link real a /grupo.html
   (no texto libre), así que lleva el color de acento del sitio para que se
   note que es distinto a un tag normal. */
.tag-grupo {
  color: var(--signal);
  border-color: var(--signal);
  text-decoration: none;
}
.tag-grupo:hover { background: var(--signal); color: var(--bg); }

/* Pendiente #12 — badge de CVE: mismo criterio de "es un link real, no un
   tag libre" que .tag-grupo, pero con tratamiento monoespaciado a propósito
   (se ve más "técnico"/como un identificador, a diferencia del nombre de un
   grupo) — mismo lenguaje visual que ya usa .badge-cvss en la página de
   detalle. */
.tag-cve {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border);
  text-decoration: none;
}
.tag-cve:hover { border-color: var(--signal); color: var(--signal); }

/* ==========================================================================
   BANNER DE SUSCRIPCIÓN AL BOLETÍN — franja horizontal debajo del hero.
   Mismo lenguaje visual que el resto del sitio: acento --signal (verde de
   marca), tipografía mono para la etiqueta, botón píldora igual que
   .btn-alertas. Se apila en columna en pantallas angostas.
   ========================================================================== */
.newsletter-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  max-width: var(--max-width);
  margin: 0 auto 28px;
  padding: 22px 32px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--signal);
  border-radius: var(--radius-lg);
}
.newsletter-strip-text { min-width: 0; }
.newsletter-strip-eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal);
  margin-bottom: 6px;
}
.newsletter-strip-titulo {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 6px;
  line-height: 1.3;
}
.newsletter-strip-sub {
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0;
  line-height: 1.5;
}
.newsletter-strip-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--signal);
  border-radius: 999px;
  padding: 12px 22px;
  white-space: nowrap;
  transition: filter 0.15s;
}
.newsletter-strip-btn:hover { filter: brightness(1.1); }
.newsletter-strip-btn svg { width: 14px; height: 14px; }

@media (max-width: 720px) {
  .newsletter-strip {
    flex-direction: column;
    align-items: flex-start;
    padding: 20px 22px;
    margin-inline: 16px;
    width: auto;
  }
  .newsletter-strip-btn { align-self: stretch; justify-content: center; }
  .newsletter-strip-titulo { font-size: 16.5px; }
}

/* ==========================================================================
   AD SLOTS
   ========================================================================== */
/* El contenedor pasa de fila a COLUMNA para que la etiqueta «Publicidad» quede
   ARRIBA del anuncio y no a su lado. En la primera versión la etiqueta era un
   hijo flex más en dirección fila, así que se repartía el ancho con el anuncio:
   le robaba espacio horizontal y los logotipos salían recortados a la derecha.
   Un `display:block` en un hijo flex no cambia nada — el contenedor manda. */
.ad-slot {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
}
/* height fija -> min-height. Con altura fija, los ~16px de la etiqueta se
   descontarían del anuncio y lo dejarían más chato; con min-height, el bloque
   crece esos pixeles y la imagen conserva su tamaño completo. */
.ad-slot-leaderboard { min-height: 90px; margin: 28px auto; max-width: var(--max-width); padding: 0 24px; }
.ad-slot-box { min-height: 250px; width: 100%; max-width: 260px; }
/* El anuncio conserva su alto EXACTO y es el contenedor el que crece.
   Con `flex: 1 1 auto` el enlace se habría repartido el alto disponible y la
   etiqueta le habría quitado sus ~14px, dejando el anuncio en 236px en vez de
   250. Fijando la base flex, el bloque completo mide 250 + etiqueta y el
   min-height del contenedor se cumple de sobra. */
.ad-slot-box .ad-directo { flex: 0 0 250px; }
.ad-slot-leaderboard .ad-directo,
.ad-slot-leaderboard .adsbygoogle { flex: 0 0 90px; }
.ad-slot .ad-directo { width: 100%; }

.ad-marca {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 5px;
  text-align: left;
}

.ad-label {
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  background: var(--surface);
}

/* Anuncio propio de reserva — cuando un espacio no tiene nada configurado */
.house-ad {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  transition: border-color 0.15s;
  padding: 12px;
  text-align: center;
}
.house-ad:hover { border-color: var(--signal); }
.house-ad-title { color: var(--text); font-size: 13px; font-weight: 600; }
.house-ad-sub { color: var(--signal); font-family: var(--font-mono); font-size: 11px; }
.house-ad-compact .house-ad-title { font-size: 12px; }

/* Anuncio directo (vendido a un anunciante) — la imagen ocupa todo el espacio */
.ad-directo {
  display: block;
  width: 100%;
  height: 100%;
}
.ad-directo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius);
  display: block;
}

/* ==========================================================================
   LAYOUT PRINCIPAL — feed + barra lateral
   ========================================================================== */
.layout {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 28px 24px 40px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
}
@media (min-width: 980px) {
  .layout { grid-template-columns: minmax(0, 1fr) 260px; }
}

.feed { min-width: 0; }

.sidebar {
  display: none;
  flex-direction: column;
  gap: 20px;
}
@media (min-width: 980px) {
  .sidebar { display: flex; }
}

.sidebar-ads-sticky {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Ahora SOLO este bloque (los 3 anuncios) se "pega" al hacer scroll — el
     widget de "Actividad por categoría" (.sidebar-panel, arriba de esto en
     el HTML) se queda fuera a propósito, se desplaza normal con el resto de
     la página. Mismo razonamiento de top/max-height que antes tenía
     .sidebar completo — ver ese historial si hace falta reajustar el
     número: calculado a partir del padding del header (que también es
     sticky), no medido en vivo. */
  position: sticky;
  top: 132px;
  align-self: start;
  max-height: calc(100vh - 132px - 24px);
  overflow-y: auto;
}

/* Barra de anuncio fija — SOLO celular (Opción A que se decidió con el
   usuario, para replicar en mobile lo que ya hace el sidebar "pegado" en
   escritorio: visibilidad garantizada del anuncio mientras se hace scroll).
   Oculta por default; JS la revela con .visible SOLO si hay un anuncio real
   configurado — a diferencia de los otros 4 espacios, este NO tiene un
   anuncio propio de reserva, una franja de 320×50 se ve mal forzando
   contenido genérico ahí. */
.mobile-ad-sticky {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  /* 74px en vez de 60px: la etiqueta «Publicidad» necesita su propio renglón.
     La alternativa era descontarlos del anuncio, que solo tiene 50px de alto —
     se habría comido casi un tercio del espacio útil y dejado el logotipo
     ilegible. Crece la barra, no se encoge el anuncio. */
  height: 74px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-sizing: border-box;
}
@media (max-width: 640px) {
  .mobile-ad-sticky.visible { display: flex; }
  /* Reserva el mismo espacio que ocupa la barra al final de la página —
     si no, el footer (o cualquier última tarjeta) queda tapado detrás de
     ella, ya que la barra es position:fixed y no empuja nada por sí sola. */
  body.tiene-ad-mobil { padding-bottom: 74px; }
}
.mobile-ad-sticky-content {
  flex: 1;
  min-width: 0;
  height: 64px;          /* 14px de etiqueta + 50px de anuncio */
  overflow: hidden;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
}
/* Versión compacta de la etiqueta para la barra de celular, donde cada pixel
   cuenta. El anuncio conserva sus 50px completos. */
.mobile-ad-sticky-content .ad-marca {
  font-size: 8.5px;
  line-height: 14px;
  margin-bottom: 0;
}
.mobile-ad-sticky-content a { flex: 0 0 50px; }
.mobile-ad-sticky-content a { display: block; width: 100%; }
.mobile-ad-sticky-content img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mobile-ad-sticky-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: pointer;
}
.mobile-ad-sticky-close .cat-icon { width: 14px; height: 14px; }

.sidebar-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px;
}

.sidebar-panel-title {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 12px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.threat-meter { display: flex; flex-direction: column; gap: 10px; }
.threat-row { display: flex; align-items: center; gap: 10px; font-size: 12.5px; }
.threat-row .cat-name { flex: 1; color: var(--text-muted); font-family: var(--font-mono); font-size: 11px; }
.threat-bar-track { width: 60px; height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.threat-bar-fill { height: 100%; border-radius: 999px; background: var(--dot-color, var(--signal)); }
.threat-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-faint); width: 18px; text-align: right; }

/* Pendiente #12 — widget "Más leídas" */
.mas-leidas-list { display: flex; flex-direction: column; gap: 10px; }
.mas-leida-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-decoration: none;
  color: inherit;
}
.mas-leida-rank {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--dot-color, var(--signal));
  width: 16px;
}
.mas-leida-titulo {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-muted);
}
.mas-leida-row:hover .mas-leida-titulo { color: var(--text); }

/* ==========================================================================
   SECCIONES POR CATEGORÍA
   ========================================================================== */
.cat-section { margin-bottom: 40px; }

.cat-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

.cat-section-header .cat-icon-wrap {
  width: 26px; height: 26px;
  border-radius: 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--dot-color, var(--text-faint)) 16%, transparent);
  color: var(--dot-color, var(--text-muted));
}
.cat-section-header .cat-icon-wrap svg { width: 14px; height: 14px; }

.cat-section-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.005em;
  color: var(--text);
}

.cat-section-count {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-faint);
}

.cat-section-line {
  flex: 1;
  height: 1px;
  background: var(--border);
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 14px;
}

/* Tarjetas ya descargadas pero colapsadas por "ver menos" — se quedan en el
   DOM (no se vuelven a pedir al servidor si se expande de nuevo), solo se
   ocultan visualmente. */
.card-grid .card--oculta { display: none; }

.cat-section-actions {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}

.btn-ver-mas, .btn-ver-menos {
  flex: 1;
  padding: 10px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.btn-ver-mas:hover, .btn-ver-menos:hover { color: var(--text); border-color: var(--signal-dim); }
.btn-ver-mas:disabled { cursor: default; opacity: 0.6; }
.btn-ver-mas:disabled:hover { color: var(--text-muted); border-color: var(--border); }
/* "Ver menos" se ve un poco menos protagónico que "ver más" — es la acción
   secundaria de las dos (sin fondo propio), para no competir visualmente. */
.btn-ver-menos { background: none; }

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: background 0.15s, border-color 0.15s, transform 0.12s;
  overflow: hidden;
}
.card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--dot-color, var(--text-faint));
}
.card:hover { background: var(--surface-2); border-color: var(--dot-color, var(--border)); transform: translateY(-1px); }

.card .report-id { font-size: 11px; }

.card h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16.5px;
  line-height: 1.3;
  margin: 0;
  letter-spacing: -0.005em;
}

.card p.summary {
  font-size: 13.5px;
  color: var(--text-muted);
  line-height: 1.55;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card .meta-row { font-size: 11px; margin-top: auto; padding-top: 10px; border-top: 1px solid var(--border-soft); }

/* ==========================================================================
   SKELETONS / EMPTY STATES
   ========================================================================== */
.skeleton {
  background: linear-gradient(90deg, var(--surface) 0%, var(--surface-2) 50%, var(--surface) 100%);
  background-size: 200% 100%;
  animation: shimmer 1.4s infinite;
  border-radius: var(--radius-lg);
}
/* Alto del esqueleto = alto real de lo que lo reemplaza. Si no coinciden, todo
   lo que está debajo salta cuando el JS pinta el contenido, y eso es CLS puro.
   El diagnóstico vino de Cloudflare Web Analytics: 53% de las mediciones en
   "needs improvement", señalando a .newsletter-strip (0.121) en la portada y a
   .meta-row / .iff-razon / .defensor-box (0.133) en las notas. Ninguno de esos
   elementos se mueve por sí mismo: los empuja el bloque de arriba al crecer.

   Portada: .hero-carousel mide 400px fijos + .hero-dots (20px de margen + 8px
   del punto) = 428px. El esqueleto medía 220px, así que la página crecía ~208px
   de golpe. Los dos breakpoints replican los min-height del carrusel. */
.skeleton-hero { height: 428px; }
@media (max-width: 860px) { .skeleton-hero { height: 388px; } }
@media (max-width: 640px) { .skeleton-hero { height: 488px; } }
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.empty-state {
  font-family: var(--font-mono);
  font-size: 13.5px;
  color: var(--text-muted);
  padding: 28px 20px;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.empty-state svg { width: 22px; height: 22px; color: var(--alert); }
/* Ícono de "no hay nada" (radar) — NO el rojo de alerta que usa el resto de
   .empty-state svg para errores (ver noticia.js). Un color tranquilo porque
   esto no es un error, es un resultado vacío normal — mayor especificidad
   (.empty-state .empty-icon vs .empty-state svg) para que gane sobre la
   regla de color de arriba sin tocarla. */
.empty-state .empty-icon { width: 26px; height: 26px; color: var(--text-faint); }
.empty-state .empty-detail {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--text-faint);
  max-width: 46ch;
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 40px 24px 32px;
  position: relative;
  overflow: hidden;
}
.footer-inner {
  position: relative;
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  gap: 48px;
  flex-wrap: wrap;
}
.footer-col { flex: 1; min-width: 200px; }
.footer-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
  margin-bottom: 12px;
}
.footer-logo svg { width: 22px; height: 22px; color: var(--signal); }
.footer-note {
  color: var(--text-faint);
  font-size: 12.5px;
  line-height: 1.65;
  max-width: 42ch;
}
.footer-heading {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.footer-cats {
  list-style: none;
  padding: 0;
  margin: 14px 0 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.footer-cats li {
  font-size: 13px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 8px;
}
.footer-bottom {
  max-width: var(--max-width);
  margin: 32px auto 0;
  padding-top: 20px;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

/* Declaración de apertura al entrenamiento de modelos de IA. flex-basis:100%
   la saca de la fila de dos columnas de .footer-bottom y le da su propio
   renglón — si compartiera fila con los enlaces legales quedaría comprimida y
   se leería como una nota al pie más, cuando es una postura deliberada del
   portal. Mismo tamaño que el resto del pie, pero centrada y separada. */
.footer-ia-abierta {
  flex-basis: 100%;
  text-align: center;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border-soft);
}
.footer-ia-abierta a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; }
.footer-ia-abierta a:hover { color: var(--signal); }

/* ==========================================================================
   PÁGINA DE DETALLE
   ========================================================================== */
.detail-wrap { max-width: var(--max-width); margin: 0 auto; padding: 36px 24px 60px; }

.detail-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  align-items: start;
}
@media (min-width: 980px) {
  .detail-layout { grid-template-columns: minmax(0, 1fr) 320px; }
}

.detail-sidebar { display: none; }
@media (min-width: 980px) {
  .detail-sidebar { display: block; }
}

/* ==========================================================================
   PÁGINA DE GRUPO DE AMENAZA (pendiente #11)
   ========================================================================== */
.grupo-header {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  margin-bottom: 28px;
}
.grupo-header h1 { margin: 8px 0 10px; }
.grupo-header .summary { margin: 0; }
.grupo-header h1.cve-titulo { font-family: var(--font-mono); letter-spacing: -0.01em; }


.related-list { display: flex; flex-direction: column; gap: 4px; }
.related-item {
  display: block;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border-soft);
  text-decoration: none;
}
.related-item:last-child { border-bottom: none; }
.related-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
  margin-bottom: 6px;
}
.related-item:hover .related-title { color: var(--signal); }
.related-meta {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
}
.back-link {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 24px;
}
.back-link:hover { color: var(--signal); }

.detail-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 32px;
  overflow: hidden;
}
.detail-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: var(--dot-color, var(--signal));
}
.detail-card h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(26px, 4vw, 38px);
  line-height: 1.15;
  margin: 0 0 18px;
  letter-spacing: -0.01em;
  max-width: 34ch;
}
.detail-card .summary { font-size: 17px; color: var(--text-muted); line-height: 1.72; margin: 0 0 26px; max-width: 72ch; }

.detail-hero-img { margin: 0 0 22px; }
.detail-hero-img img {
  width: 100%;
  /* aspect-ratio + height:auto reservan la caja ANTES de que la imagen llegue.
     Sin esto la altura es 0 hasta que carga y, al aparecer, empuja hacia abajo
     .meta-row, .iff-razon y .defensor-box — los tres elementos que Cloudflare
     Web Analytics señaló con 0.133 de desplazamiento en las páginas de nota.
     La imagen viene de images.unsplash.com, un tercero: el retraso no lo
     controlamos, pero el hueco sí se puede reservar.
     16/9 sobre el ancho de .detail-card da ~360px, que coincide con el
     max-height que ya estaba puesto. */
  aspect-ratio: 16 / 9;
  height: auto;
  max-height: 360px;
  background: var(--surface-raised);
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  display: block;
}
.detail-hero-attrib {
  display: block;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
}
.detail-hero-attrib a { color: var(--text-muted); }
.detail-hero-attrib a:hover { color: var(--signal); }

/* Enlaces dentro del cuerpo de las páginas de texto largo (metodologia.html,
   aviso-privacidad.html). SIN esta regla heredan la global de arriba del
   archivo — `a { color: inherit; text-decoration: none; }` — y quedan
   visualmente idénticos al texto que los rodea: el lector no tiene forma de
   saber que son clicables. Aplica a los enlaces internos (contacto) y a los
   externos de atribución (NVD, CISA, Unsplash, las 6 fuentes RSS). */
.legal-content a {
  color: var(--signal);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.legal-content a:hover { text-decoration: none; }

/* Pie compartido de las páginas internas (ver pie-pagina.js). Deliberadamente
   discreto: su función es que los enlaces de confianza estén disponibles desde
   cualquier página, no competir con el contenido. El pie de varias columnas de
   la portada se mantiene aparte y no se toca. */
.pie-simple {
  max-width: var(--max-width);
  margin: 48px auto 0;
  padding: 20px 32px 32px;
  border-top: 1px solid var(--border-soft);
  font-size: 12.5px;
  color: var(--text-faint);
  text-align: center;
  line-height: 1.7;
}
.pie-simple nav { margin-bottom: 8px; }
.pie-simple a { color: var(--text-muted); text-decoration: none; }
.pie-simple a:hover { color: var(--signal); text-decoration: underline; }
.pie-simple p { margin: 0; }
@media (max-width: 640px) {
  .pie-simple { padding: 18px 18px 28px; font-size: 12px; }
}

/* Nota al margen dentro del contenido legal: aclaraciones secundarias que no
   deben competir visualmente con el texto principal (por ejemplo, la
   explicación del domicilio fiscal en acerca-de.html). */
.legal-content .small-note {
  font-size: 13px;
  color: var(--text-muted);
  border-left: 2px solid var(--border);
  padding-left: 12px;
}

.source-box {
  border-top: 1px solid var(--border-soft);
  padding-top: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}
.source-identity { display: flex; align-items: center; gap: 10px; }
.source-favicon-lg {
  width: 32px; height: 32px;
  border-radius: 7px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  flex-shrink: 0;
  object-fit: contain;
  padding: 4px;
}
.source-name-block { display: flex; flex-direction: column; gap: 1px; }
.source-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.source-label { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.06em; }

.source-link {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--on-accent);
  background: var(--signal);
  padding: 8px 14px;
  border-radius: 999px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: filter 0.15s;
}
.source-link:hover { filter: brightness(1.1); }

.advertencia {
  margin-top: 20px;
  padding: 12px 14px;
  border: 1px dashed var(--alert-dim);
  background: rgba(255, 106, 82, 0.06);
  border-radius: var(--radius);
  font-size: 12.5px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.provenance {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  line-height: 1.6;
}

/* ==========================================================================
   BUSCADOR Y BOTÓN DE ALERTAS (topbar)
   ========================================================================== */
.topbar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s;
}
.theme-toggle:hover { color: var(--text); border-color: var(--signal); }
.theme-toggle svg { width: 15px; height: 15px; }
/* En páginas sin topbar-actions (todo excepto el index), este botón vive
   solo dentro de .topbar-inner — empuja hacia la derecha sin depender de
   que exista un contenedor .topbar-actions al lado. */
.theme-toggle-standalone { margin-left: auto; }

.sort-select {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 12px;
  cursor: pointer;
  transition: border-color 0.15s;
}
.sort-select:focus,
.sort-select:hover {
  border-color: var(--signal);
  outline: none;
}
@media (max-width: 860px) {
  .sort-select { display: none; }
  /* iOS Safari hace zoom automático al enfocar un <input>/<select>/<textarea>
     cuyo font-size sea menor a 16px — y a veces no regresa el zoom al
     cerrarse el teclado, dejando además position:sticky roto (el header dejó
     de quedarse fijo) hasta que la persona hace zoom-out a mano. La barra de
     búsqueda (12px) y los campos de formulario (14.5px, registro/contacto/
     baja) estaban por debajo de ese umbral. Se sube a 16px SOLO en mobile
     (aquí, no en la regla base) para no agrandar los inputs en desktop, que
     no tiene este problema. Como style.css es compartido por todas las
     páginas, este único bloque cubre todos los inputs del sitio de una vez.
     NO se usa la alternativa de user-scalable=no en el viewport: deshabilitar
     el zoom manual del usuario es un problema de accesibilidad (WCAG 1.4.4),
     así que la corrección va por el lado del font-size, no por bloquear el
     zoom que alguien sí podría necesitar.

     !important a propósito: las reglas base de .search-box input/.field-input
     viven MÁS ABAJO en este mismo archivo (después de este bloque), así que
     en un empate de especificidad ganarían ellas y anularían este fix en
     mobile sin avisar — no es una preferencia de estilo, es una corrección
     de un bug real de iOS, así que se blinda contra reordenamientos futuros
     del CSS en vez de depender de la posición exacta en el archivo. */
  .search-box input,
  .field-input,
  select.field-input {
    font-size: 16px !important;
  }
  /* Opción C: foto chica tipo miniatura junto al título (no de ancho
     completo), resumen abajo ocupando todo el ancho. Un grid-template-areas
     distinto es TODO lo que hace falta — el HTML (heroHTML() en app.js) es
     el mismo para desktop y mobile, ver el comentario ahí sobre por qué
     título y cuerpo son dos <a> separados. */
  .hero-layout {
    grid-template-columns: 1fr 78px;
    grid-template-areas:
      "top   top"
      "title media"
      "body  body";
    column-gap: 12px;
  }
  .hero-title-link h2 { font-size: clamp(19px, 5.2vw, 24px); max-width: none; }
  .hero-media { margin-top: 2px; }
  .hero-img { margin-bottom: 3px; }
  .hero-img img { height: 78px; }
  /* La atribución de Unsplash sigue siendo obligatoria, pero en una
     miniatura de 78px no cabe legible — se achica al mínimo permitido en
     vez de quitarse. */
  .hero-img-attrib { font-size: 9px; line-height: 1.3; }
  /* Debajo de 860px, en vez de una altura FIJA con recorte forzado
     (overflow:hidden, como en desktop), se usa min-height sin recorte —
     calcular el alto exacto a mano para este breakpoint ha fallado ya un
     par de veces (contenido cortado a la mitad, o encimado con la imagen).
     Con min-height, si algún caso puntual necesita un poco más de espacio
     del calculado, el recuadro simplemente crece un poco en vez de cortar
     o encimar contenido — variación mínima de alto es mucho menos grave
     que texto cortado a la mitad o badges tapados por la foto. Desktop NO
     se toca — ahí el cálculo de altura fija sí venía funcionando bien. */
  .hero-carousel { height: auto; min-height: 360px; overflow: visible; }
}

@media (max-width: 640px) {
  /* Debajo de 640px ni la miniatura cabe cómoda junto al título sin que el
     título quede demasiado angosto — se vuelve a una sola columna, imagen
     completa arriba del título (mismo criterio que el resto del sitio en
     este breakpoint, ver .cat-nav-wrap más abajo). */
  .hero-layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "top"
      "media"
      "title"
      "body";
    /* IMPORTANTE: se redefine completo, no se hereda el de la regla base.
       El orden de los renglones cambió (media ahora va ANTES que title), y
       grid-template-rows asigna por posición de renglón, no por nombre de
       área — heredar "auto auto 1fr" de la base le habría dado el 1fr a
       "title" en vez de a "body" (el 3er renglón en este orden nuevo es
       title, no body), justo lo que NO se quiere. */
    grid-template-rows: auto auto auto 1fr;
  }
  .hero-img img { height: 180px; }
  .hero-img-attrib { font-size: 11px; }
  /* Mismo criterio que en el breakpoint de 860px: min-height sin recorte
     forzado, en vez de arriesgarse otra vez a calcular mal un alto fijo
     exacto para este layout (imagen completa arriba, la más alta de los
     tres breakpoints). */
  .hero-carousel { height: auto; min-height: 460px; overflow: visible; }
}

.search-box {
  position: relative;
  display: flex;
  align-items: center;
}
.search-box svg {
  position: absolute;
  left: 10px;
  width: 14px; height: 14px;
  color: var(--text-faint);
  pointer-events: none;
}
.search-box input {
  width: 148px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 7px 12px 7px 30px;
  transition: width 0.2s, border-color 0.15s;
}
.search-box input:focus {
  width: 200px;
  border-color: var(--signal);
  outline: none;
}
.search-box input::placeholder { color: var(--text-faint); }

.search-clear {
  position: absolute;
  right: 8px;
  width: 16px; height: 16px;
  color: var(--text-faint);
  background: none;
  border: none;
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
}
.search-clear svg { width: 12px; height: 12px; }
.search-clear.visible { display: flex; }

.btn-alertas {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--signal);
  border: none;
  border-radius: 999px;
  padding: 8px 14px;
  white-space: nowrap;
  transition: filter 0.15s;
}
.btn-alertas:hover { filter: brightness(1.1); }
.btn-alertas svg { width: 14px; height: 14px; }
.btn-alertas .btn-alertas-text { display: none; }
@media (min-width: 860px) { .btn-alertas .btn-alertas-text { display: inline; } }

/* ==========================================================================
   RESULTADOS DE BÚSQUEDA
   ========================================================================== */
.search-results-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.search-results-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
}
.search-results-back {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--signal);
  border: 1px solid var(--signal-dim);
  border-radius: 999px;
  padding: 5px 11px;
}

/* ==========================================================================
   PÁGINA DE REGISTRO (registro.html)
   ========================================================================== */
.registro-wrap { max-width: 620px; margin: 0 auto; padding: 40px 24px 60px; }

.registro-intro { margin-bottom: 28px; }
.registro-intro h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(26px, 4vw, 34px);
  margin: 0 0 10px;
  letter-spacing: -0.01em;
}
.registro-intro p { color: var(--text-muted); font-size: 15px; line-height: 1.6; margin: 0; max-width: 56ch; }

.registro-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px;
}

.field-group { margin-bottom: 20px; }
.field-label {
  display: block;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.field-input {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14.5px;
  padding: 11px 13px;
  transition: border-color 0.15s;
}
.field-input:focus { border-color: var(--signal); outline: none; }
select.field-input {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238B93A6' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px;
  padding-right: 38px;
  cursor: pointer;
}
.field-hint { font-size: 12px; color: var(--text-faint); margin-top: 6px; }
.field-error { font-size: 12.5px; color: var(--alert); margin-top: 6px; display: none; }
.field-error.visible { display: block; }

.radio-option, .checkbox-option {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 8px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.radio-option:hover, .checkbox-option:hover { border-color: var(--signal-dim); background: var(--surface-2); }
.radio-option input, .checkbox-option input { margin-top: 2px; accent-color: var(--signal); flex-shrink: 0; }
.radio-option-body strong { display: block; font-size: 14px; margin-bottom: 2px; }
.radio-option-body span { font-size: 12.5px; color: var(--text-muted); }

.cat-checklist {
  display: none;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
  padding-left: 4px;
}
.cat-checklist.visible { display: flex; }
.cat-checklist .checkbox-option { margin-bottom: 0; padding: 9px 12px; }
.cat-checklist .checkbox-option span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  color: var(--text);
}

.consent-row { display: flex; align-items: flex-start; gap: 10px; margin: 22px 0; }
.consent-row input { margin-top: 3px; accent-color: var(--signal); flex-shrink: 0; }
.consent-row label { font-size: 12.5px; color: var(--text-muted); line-height: 1.6; }

.btn-submit {
  width: 100%;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 13.5px;
  color: var(--on-accent);
  background: var(--signal);
  border: none;
  border-radius: 999px;
  padding: 13px;
  cursor: pointer;
  transition: filter 0.15s;
}
.btn-submit:hover { filter: brightness(1.1); }
.btn-submit:disabled { opacity: 0.5; cursor: not-allowed; }

.form-msg {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: var(--radius);
  font-size: 13px;
  font-family: var(--font-mono);
  display: none;
  align-items: flex-start;
  gap: 8px;
}
.form-msg.visible { display: flex; }
.form-msg svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
.form-msg.success { background: rgba(53,229,180,0.08); border: 1px solid var(--signal-dim); color: var(--text); }
.form-msg.error { background: rgba(255,106,82,0.08); border: 1px solid var(--alert-dim); color: var(--text); }

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 640px) {
  .topbar-strip { display: none; }
  .topbar-inner {
    gap: 16px;
    flex-wrap: wrap;
    row-gap: 12px;
    justify-content: space-between;
  }
  /* El subtítulo hereda white-space:nowrap de .logo — no puede partirse en
     dos líneas, así que fuerza un ancho fijo grande. Combinado con la barra
     de búsqueda y el botón (que tampoco se encogen, a propósito), esto por
     sí solo ya no cabía en un celular, sin importar qué tan chico se
     encogiera el menú de categorías al lado. Se oculta aquí — la marca
     "INFOSECDASH" sola ya es suficiente en el espacio angosto del header móvil.
     El slogan completo sigue visible en el <title> de la pestaña del navegador. */
  .logo-text small { display: none; }
  /* Mismo criterio que el subtítulo del logo (arriba): en una pantalla
     angosta no cabe junto a "SEÑAL DEL DÍA" sin verse apretado — se
     esconde, la explicación completa del sitio sigue en /metodologia.html. */
  .hero-intro { display: none; }
  /* .topbar-catnav ya es su propia fila de ancho completo para CUALQUIER
     tamaño de pantalla (ver arriba) — no necesita ningún ajuste aparte aquí
     para mobile, a diferencia de antes que vivía adentro de .topbar-inner y
     había que forzarlo a su propia línea con order/flex-basis. */
  .topbar-catnav { padding: 8px 16px; }
  .hero-inner { padding: 28px 16px 24px; }
  .hero-carousel { padding: 20px 18px; }
  .layout { padding: 20px 16px 32px; }
  .ad-slot-leaderboard { padding: 0 16px; }
  .detail-card { padding: 22px; }
  .search-box input { width: 100px; }
  .search-box input:focus { width: 140px; }
  .registro-card { padding: 20px; }
}
