/* ════════════════════════════════════════════════════════════════════
   estilosBloques.css
   Render visual de cada tipo de bloque del page builder en el PORTAL
   PÚBLICO. El shell (navbar, footer, zonas, hero) vive en
   estilosPortal.css; este archivo se ocupa solo del contenido editable.

   Cada sección está prefijada con `portal-<nombre-bloque>-*` y usa las
   variables `--c-*` definidas en theme-config.css (que la
   ConfiguracionVisual del backend puede sobrescribir per-sitio).
   ════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════
   Color de acento configurable por bloque (paleta_color)

   Cualquier bloque que exponga un campo `color_acento` (paleta_color)
   aplica en su root la clase `portal-acento--<slug>`. Eso setea dos
   custom-properties consumibles desde el CSS del bloque:
     --portal-c-acento        : para `background` (acepta sólidos y
                                gradientes; usar como background o
                                background-image)
     --portal-c-acento-solid  : siempre un color sólido (apto para
                                `border`, `color`, `box-shadow`, etc.;
                                en los slugs de gradiente cae al color
                                "primario" del degradado)

   El set de slugs coincide con PALETA_OPCIONES_BASE en el catálogo.
   ═══════════════════════════════════════════════════════════════════ */
.portal-acento--transparente {
    --portal-c-acento: transparent;
    --portal-c-acento-solid: transparent;
}
.portal-acento--primary {
    --portal-c-acento: var(--c-primary);
    --portal-c-acento-solid: var(--c-primary);
}
.portal-acento--primary-suave {
    --portal-c-acento: var(--c-primary-lighter);
    --portal-c-acento-solid: var(--c-primary-lighter);
}
.portal-acento--accent {
    --portal-c-acento: var(--c-accent);
    --portal-c-acento-solid: var(--c-accent);
}
.portal-acento--accent-suave {
    --portal-c-acento: color-mix(in srgb, var(--c-accent) 25%, var(--c-bg));
    --portal-c-acento-solid: color-mix(in srgb, var(--c-accent) 25%, var(--c-bg));
}
.portal-acento--grad-primary {
    --portal-c-acento: linear-gradient(135deg,
        var(--c-primary-light) 0%, var(--c-primary) 55%, var(--c-primary-dark) 100%);
    --portal-c-acento-solid: var(--c-primary);
}
.portal-acento--grad-accent {
    --portal-c-acento: linear-gradient(135deg,
        var(--c-accent2, var(--c-accent)) 0%, var(--c-accent) 55%, var(--c-accent-dark) 100%);
    --portal-c-acento-solid: var(--c-accent);
}
.portal-acento--grad-mixto {
    --portal-c-acento: linear-gradient(135deg,
        var(--c-primary) 0%, var(--c-primary-dark) 45%, var(--c-accent) 100%);
    --portal-c-acento-solid: var(--c-primary);
}


/* ═══════════════════════════════════════════════════════════════════
   Banner promocional
   ═══════════════════════════════════════════════════════════════════ */

/* Mobile estándar del proyecto (xs Bootstrap, 575.98px). Las reglas
   mobile históricas del banner viven en `@media (max-width: 480px)`
   más abajo y se conservan para no tocar reglas vecinas de otros
   bloques; este bloque se agrega acá para cubrir el rango 481-575.98px
   (celulares grandes) que de otro modo se quedaba con padding y
   tipografía de desktop. */
@media (max-width: 575.98px) {
    .portal-banner-inner { padding: 1rem .85rem; }
    .portal-banner-texto { font-size: 1rem; line-height: 1.35; }
    .portal-banner--altura-baja  { min-height: 64px; }
    .portal-banner--altura-media { min-height: 110px; }
    .portal-banner--altura-alta  { min-height: 180px; }
}

.portal-banner {
    position: relative;
    color: var(--c-text);
    overflow: hidden;
    /* Margen vertical fijo — el banner siempre queda separado del bloque
       previo, sin importar la zona donde se haya soltado. Se suma al
       `gap` de la zona si existe (Grid no colapsa margins). */
    margin-block: 1.5rem;
}
/* `altura` actúa como min-height (sólo bloquea el "achicarse de más").
   En la variante imagen-fondo el alto efectivo lo dicta la imagen — así
   no recortamos ni distorsionamos la proporción natural. */
.portal-banner--altura-baja  { min-height: 100px; }
.portal-banner--altura-media { min-height: 180px; }
.portal-banner--altura-alta  { min-height: 280px; }

/* Toda la pieza como link (texto + imagen). */
.portal-banner--link {
    display: block;
    color: inherit;
    text-decoration: none;
    transition: filter .15s ease;
}
.portal-banner--link:hover,
.portal-banner--link:focus {
    color: inherit;
    text-decoration: none;
    filter: brightness(0.97);
}

/* Bordes redondeados opt-in. overflow:hidden del banner ya recorta la
   imagen interna a las esquinas. */
.portal-banner--redondeado { border-radius: 12px; }

/* ── Variante imagen-fondo ─────────────────────────────────────────
   La imagen se renderiza como <img> con width:100% y height auto:
   el container crece para acomodar la imagen completa sin recortarla
   ni forzarla a un alto fijo. Si la imagen es muy chica, las clases
   --altura-* dan un piso mínimo. */
.portal-banner--imagen-fondo {
    /* Fallback cuando el operador todavía no eligió imagen */
    background: linear-gradient(135deg, var(--c-accent), var(--c-primary));
    color: var(--c-text-light);
    display: block;
}
.portal-banner-imagen {
    display: block;
    width: 100%;
    height: auto;
}

/* ── Variantes sin imagen: necesitan centrar texto verticalmente ── */
.portal-banner--color-solido,
.portal-banner--compacto {
    display: flex;
    align-items: center;
}

/* ── Variante "vertical": banner retrato para zona lateral ─────────
   Igual mecánica que `imagen-fondo` (imagen como fondo del bloque + overlay
   opcional de texto), pero con aspect-ratio retrato (3:4) que encaja
   bien en el ancho reducido del lateral. La imagen se ajusta con
   object-fit a la caja para no romper la proporción del slot. */
.portal-banner--vertical {
    display: block;
    position: relative;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    border-radius: 12px;
    background: linear-gradient(180deg, var(--c-accent), var(--c-primary));
    color: var(--c-text-light);
    min-height: 0;     /* anula el min-height de las clases --altura-* */
}
.portal-banner--vertical .portal-banner-imagen {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.portal-banner--vertical .portal-banner-inner {
    position: absolute;
    inset: 0;
    align-items: flex-end;     /* texto al pie del banner */
    padding: 1.25rem 1rem;
    text-align: left;
    z-index: 2;
}
.portal-banner--vertical .portal-banner-texto {
    font-size: 1.1rem;         /* más chico que el horizontal */
    line-height: 1.25;
}
.portal-banner--vertical .portal-banner-overlay {
    /* gradiente vertical: la parte de arriba puede ser la imagen pura,
       y abajo se oscurece para que el texto entre con contraste. */
    background: linear-gradient(to top, rgba(0,0,0,.75) 0%, rgba(0,0,0,.15) 60%, rgba(0,0,0,0) 100%);
}

/* ── Variante "vertical-video": retrato lateral con video de fondo ──
   Hereda el aspect-ratio 3:4 y el comportamiento del vertical clásico,
   pero la capa de fondo es un <video> en lugar de <img>. El video se
   posiciona absolute fill (mismo patrón que `.portal-banner-video` de
   la variante horizontal) y se recorta por `overflow: hidden` del
   contenedor. El color sólido institucional queda detrás como fallback
   mientras carga; el overlay degradado del vertical se conserva para
   que el texto inferior tenga contraste. */
.portal-banner--vertical-video {
    display: block;
    position: relative;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    border-radius: 12px;
    /* Fallback si el video tarda en cargar o si prefers-reduced-motion
       lo oculta — gradiente institucional vertical, mismo tono que la
       variante vertical clásica. */
    background: linear-gradient(180deg, var(--c-accent), var(--c-primary));
    color: var(--c-text-light);
    min-height: 0;
}
.portal-banner--vertical-video .portal-banner-video {
    /* Mismas reglas que .portal-banner-video del horizontal, pero
       restringidas a este selector — así no dependemos de la cascada
       y la regla de prefers-reduced-motion sigue aplicando porque
       apunta a `.portal-banner-video` simple. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
}
.portal-banner--vertical-video .portal-banner-inner {
    position: absolute;
    inset: 0;
    align-items: flex-end;
    padding: 1.25rem 1rem;
    text-align: left;
    z-index: 2;
}
.portal-banner--vertical-video .portal-banner-texto {
    font-size: 1.1rem;
    line-height: 1.25;
}
.portal-banner--vertical-video .portal-banner-overlay {
    background: linear-gradient(to top, rgba(0,0,0,.75) 0%, rgba(0,0,0,.15) 60%, rgba(0,0,0,0) 100%);
}

/* ── Inner (contenedor del texto) ─────────────────────────────────
   En imagen-fondo se superpone absolute sobre la imagen.
   En color-solido / compacto fluye normal y centra con el flex del
   banner. */
.portal-banner-inner {
    position: relative;
    z-index: 2;
    padding-block: 1.5rem;
    width: 100%;
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
}
.portal-banner--imagen-fondo .portal-banner-inner {
    position: absolute;
    inset: 0;
}

.portal-banner-texto {
    font-size: clamp(1.2rem, 2.5vw, 1.8rem);
    font-weight: 700;
    /* El operador carga el texto en un textarea — preservamos saltos
       de línea y espacios múltiples tal como los escribió. `pre-wrap`
       respeta `\n` y espacios extra pero permite que las líneas largas
       sigan haciendo wrap natural en pantallas chicas. */
    white-space: pre-wrap;
    /* Cinturón anti-desborde: si el operador pega una URL larga o una
       palabra sin espacios, igual rompe dentro del banner en lugar de
       empujar texto fuera del recorte (crítico en variantes vertical /
       vertical-video donde el ancho útil es muy chico). */
    overflow-wrap: anywhere;
    word-break: break-word;
}
/* Color del texto controlado por el operador vía picker — se inyecta
   como variable CSS inline en el template, en el `<a>` o `<div>` raíz
   del banner. Aplicamos a `.portal-banner-inner` para que tanto el
   `.portal-banner-texto` como el ícono de flecha hereden el mismo
   tono. Fallback `--c-text-light` para bloques viejos sin `color_texto`. */
.portal-banner-inner {
    color: var(--portal-banner-color-texto, var(--c-text-light));
}

.portal-banner-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(to right, rgba(0,0,0,.55), rgba(0,0,0,.25));
    z-index: 1;
}

/* ── Variante video-fondo ────────────────────────────────────────
   Replica la estética del hero con video pero pensada para banners
   en zonas no-hero (ej: destacados, contenido_principal). El video
   se posiciona como capa de fondo a pantalla completa del bloque,
   con overlay oscuro opcional encima cuando hay texto.
   Si el video tarda en cargar o la red lo bloquea, queda el color
   sólido (.portal-banner--fondo-*) detrás como respaldo, junto con
   el `poster` opcional del <video>. */
.portal-banner--video-fondo {
    /* Fallback institucional mientras carga / si el video falla. */
    background: linear-gradient(135deg, var(--c-accent), var(--c-primary));
    color: var(--c-text-light);
    display: block;
    position: relative;
    overflow: hidden;
}
.portal-banner-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
}
.portal-banner--video-fondo .portal-banner-inner {
    position: absolute;
    inset: 0;
}
@media (prefers-reduced-motion: reduce) {
    /* Respeto a la preferencia del visitante: no autoplaymovement
       que no controle. El video queda en su primer frame (poster
       toma protagonismo si está definido). */
    .portal-banner-video { display: none; }
}

/* ── Variante compacto ───────────────────────────────────────────── */
.portal-banner--compacto {
    background: var(--c-bg-subtle);
    color: var(--c-text);
    border-block: 1px solid var(--c-border);
    min-height: 60px;
}
.portal-banner--compacto .portal-banner-texto { font-size: 1rem; font-weight: 600; }

/* ── Variantes de color (paleta_color) ────────────────────────────
   Mismo set de opciones que `paleta_color` en cards. Sólo se aplica
   en la variante color-solido (la plantilla agrega la clase). En las
   variantes dark, el texto pasa a claro automáticamente. */
:where(.portal-banner--fondo-primary,
       .portal-banner--fondo-accent,
       .portal-banner--fondo-grad-primary,
       .portal-banner--fondo-grad-accent,
       .portal-banner--fondo-grad-mixto) {
    color: var(--c-text-light);
}

.portal-banner--fondo-transparente   { background: transparent; }
.portal-banner--fondo-primary        { background: var(--c-primary); }
.portal-banner--fondo-primary-suave  { background: var(--c-primary-lighter); }
.portal-banner--fondo-accent         { background: var(--c-accent); }
.portal-banner--fondo-accent-suave   { background: color-mix(in srgb, var(--c-accent) 25%, var(--c-bg)); }
.portal-banner--fondo-grad-primary {
    background: linear-gradient(135deg,
        var(--c-primary-light) 0%, var(--c-primary) 55%, var(--c-primary-dark) 100%);
}
.portal-banner--fondo-grad-accent {
    background: linear-gradient(135deg,
        var(--c-accent2, var(--c-accent)) 0%, var(--c-accent) 55%, var(--c-accent-dark) 100%);
}
.portal-banner--fondo-grad-mixto {
    background: linear-gradient(135deg,
        var(--c-primary) 0%, var(--c-primary-dark) 45%, var(--c-accent) 100%);
}


/* ═══════════════════════════════════════════════════════════════════
   Cards destacadas
   3 variantes: redondeadas-imagen, cuadradas-color, minimalistas
   ═══════════════════════════════════════════════════════════════════ */

.portal-cards-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
    /* El titulo de seccion lo escribe el operador — puede entrar texto
       largo sin espacios (slugs, marcas). Permitimos que rompa palabra
       antes de empujar el ancho del bloque. */
    overflow-wrap: anywhere;
}
.portal-cards-grid {
    display: grid;
    gap: 1rem;
}
/* `cols-1`: una columna apilada. Útil cuando el bloque se pone en la
   zona lateral o cuando el operador quiere cards a ancho completo. No
   tiene max-width — toma todo el ancho del slot. */
.portal-cards--cols-1 .portal-cards-grid { grid-template-columns: 1fr; }
.portal-cards--cols-2 .portal-cards-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); max-width: 800px; }
.portal-cards--cols-3 .portal-cards-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.portal-cards--cols-4 .portal-cards-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

.portal-card {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    /* Radio unificado con el resto de elementos "card-like" del portal
       (banner redondeado, accesos con-imagen, etc.) — 12px. La variante
       "cuadradas-color" lo pisa con 4px porque su semántica es la
       opuesta. */
    border-radius: 12px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform .18s ease, box-shadow .18s ease;
    position: relative;   /* contexto para .portal-card-stretch */
}
.portal-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.08);
}
/* Stretched-link: <a> absoluto que cubre la card entera. Permite click
   en cualquier parte para navegar, sin perder la legibilidad ni la
   selección de texto interno (z-index 1; texto seleccionable funciona
   porque el navegador igual prioriza la selección sobre los hover). */
.portal-card--clickable { cursor: pointer; }
.portal-card-stretch {
    position: absolute;
    inset: 0;
    z-index: 1;
    text-indent: -9999px;
    overflow: hidden;
}
.portal-card--clickable:hover .portal-card-titulo { color: var(--c-text-brand); }
.portal-card--clickable:hover .portal-card-link { color: var(--c-primary-dark); }

/* La variante "redondeadas-imagen" hereda el radio base (12px) — no
   necesita override propio. Se deja la regla vacía / removida para
   mantener una sola fuente de verdad del radio. */
.portal-card-thumb {
    height: 160px;
    background-color: var(--c-primary-lighter);  /* fallback antes de que cargue el img */
    overflow: hidden;                              /* clipea el transform:scale del zoom */
    position: relative;
}
.portal-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;     /* default — `foco_estilos` lo pisa con el focal */
    display: block;
}

.portal-cards--cuadradas-color .portal-card { border-radius: 4px; }
.portal-card-color-band {
    /* Altura por defecto — la sobrescribe el modificador `--banda-<paso>`
       de abajo si el bloque declaró uno. Mantenemos 8px como fallback
       para bloques antiguos guardados sin `altura_banda`. */
    height: 8px;
    background: var(--c-primary);  /* fallback si no hay clase modificadora */
}
/* Pasos discretos de altura — corresponden al select `altura_banda` del
   catálogo. Los valores son intencionalmente chicos: la banda es un
   acento institucional, no una franja-protagonista. */
.portal-cards--banda-delgada .portal-card-color-band { height: 4px; }
.portal-cards--banda-media   .portal-card-color-band { height: 8px; }
.portal-cards--banda-gruesa  .portal-card-color-band { height: 16px; }
.portal-cards--banda-extra   .portal-card-color-band { height: 28px; }
/* Variantes de color de la banda. Mismo set de opciones que `paleta_color`
   (compartido con texto libre y fondos de card) para no introducir nuevas
   nomenclaturas. La banda se pinta SIEMPRE — la opción `transparente`
   solo aparece en `color_banda_card` para "heredar del color general". */
.portal-card-color-band--primary       { background: var(--c-primary); }
.portal-card-color-band--primary-suave { background: var(--c-primary-lighter); }
.portal-card-color-band--accent        { background: var(--c-accent); }
.portal-card-color-band--accent-suave  { background: color-mix(in srgb, var(--c-accent) 25%, #fff); }
.portal-card-color-band--grad-primary {
    background: linear-gradient(135deg,
        var(--c-primary-light) 0%, var(--c-primary) 55%, var(--c-primary-dark) 100%);
}
.portal-card-color-band--grad-accent {
    background: linear-gradient(135deg,
        var(--c-accent2, var(--c-accent)) 0%, var(--c-accent) 55%, var(--c-accent-dark) 100%);
}
.portal-card-color-band--grad-mixto {
    background: linear-gradient(135deg,
        var(--c-primary) 0%, var(--c-primary-dark) 45%, var(--c-accent) 100%);
}

.portal-cards--minimalistas .portal-card {
    background: transparent;
    border: 0;
    text-align: center;
    box-shadow: none;
}
.portal-card-icono {
    font-size: 2.2rem;
    /* Tono del ícono — el modificador `portal-cards--icono-<slug>` del
       wrapper inyecta el valor concreto. Caemos a --c-text-brand para
       cards guardadas antes de que existiera el picker. */
    color: var(--portal-cards-icono, var(--c-text-brand));
    margin-bottom: .5rem;
}
.portal-cards--icono-primary { --portal-cards-icono: var(--c-primary); }
.portal-cards--icono-accent  { --portal-cards-icono: var(--c-accent); }

.portal-card-body { padding: 1rem 1.1rem 1.25rem; flex: 1; display: flex; flex-direction: column; min-width: 0; }
.portal-card-titulo {
    color: var(--c-text-strong);
    font-size: 1.1rem;
    font-weight: 700;
    margin: 0 0 .4rem;
    /* Titulo escrito por el operador — habilitamos wrap por caracter
       para que un termino largo o URL no empuje el ancho de la card. */
    overflow-wrap: anywhere;
}
.portal-card-descripcion {
    color: var(--c-text-soft);
    font-size: .92rem;
    margin: 0 0 .8rem;
    flex: 1;
    /* Idem titulo — la descripcion suele incluir enlaces o terminos
       sin espacios que deben poder romper en cards angostas. */
    overflow-wrap: anywhere;
}
.portal-card-link {
    color: var(--c-text-brand);
    font-weight: 600;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    margin-top: auto;
}
.portal-card-link:hover { color: var(--c-primary-dark); text-decoration: none; }
.portal-card-link i { transition: transform .15s ease; }
.portal-card-link:hover i { transform: translateX(2px); }

/* ── Variantes de fondo de cards (paleta_color) ─────────────────────
   Aplican al .portal-card cuando el bloque tiene modo "por-card"
   (cada card lleva su propia clase) o cuando el bloque tiene modo
   "general" (todas las cards descendientes del .portal-cards--fondo-X
   reciben el mismo fondo). El catálogo expone las mismas opciones
   que el bloque "Texto libre" — coherencia institucional. */

/* Sólidos oscuros: texto blanco. */
:where(.portal-card--fondo-primary,
       .portal-card--fondo-accent,
       .portal-card--fondo-grad-primary,
       .portal-card--fondo-grad-accent,
       .portal-card--fondo-grad-mixto,
       .portal-cards--fondo-primary .portal-card,
       .portal-cards--fondo-accent .portal-card,
       .portal-cards--fondo-grad-primary .portal-card,
       .portal-cards--fondo-grad-accent .portal-card,
       .portal-cards--fondo-grad-mixto .portal-card) {
    color: var(--c-text-light);
    border-color: transparent;
}
:where(.portal-card--fondo-primary,
       .portal-card--fondo-accent,
       .portal-card--fondo-grad-primary,
       .portal-card--fondo-grad-accent,
       .portal-card--fondo-grad-mixto,
       .portal-cards--fondo-primary .portal-card,
       .portal-cards--fondo-accent .portal-card,
       .portal-cards--fondo-grad-primary .portal-card,
       .portal-cards--fondo-grad-accent .portal-card,
       .portal-cards--fondo-grad-mixto .portal-card) :where(.portal-card-titulo,
                                                            .portal-card-descripcion,
                                                            .portal-card-link) {
    color: var(--c-text-light);
}

/* Fondos concretos */
.portal-card--fondo-primary,
.portal-cards--fondo-primary .portal-card {
    background: var(--c-primary);
}
.portal-card--fondo-primary-suave,
.portal-cards--fondo-primary-suave .portal-card {
    background: var(--c-primary-lighter);
}
.portal-card--fondo-accent,
.portal-cards--fondo-accent .portal-card {
    background: var(--c-accent);
}
.portal-card--fondo-accent-suave,
.portal-cards--fondo-accent-suave .portal-card {
    background: color-mix(in srgb, var(--c-accent) 25%, #fff);
}
.portal-card--fondo-grad-primary,
.portal-cards--fondo-grad-primary .portal-card {
    background: linear-gradient(135deg,
        var(--c-primary-light) 0%,
        var(--c-primary) 55%,
        var(--c-primary-dark) 100%);
}
.portal-card--fondo-grad-accent,
.portal-cards--fondo-grad-accent .portal-card {
    background: linear-gradient(135deg,
        var(--c-accent2, var(--c-accent)) 0%,
        var(--c-accent) 55%,
        var(--c-accent-dark) 100%);
}
.portal-card--fondo-grad-mixto,
.portal-cards--fondo-grad-mixto .portal-card {
    background: linear-gradient(135deg,
        var(--c-primary) 0%,
        var(--c-primary-dark) 45%,
        var(--c-accent) 100%);
}


/* ── Variante: imagen con bloque flotante de título ────────────────
   Cada card es una imagen ocupando toda la superficie + un recuadro
   flotante con el título encima. Posición del recuadro (arriba /
   centro / abajo) la controla el modificador `--overlay-<pos>` que
   se setea a nivel de la grilla. Los colores del recuadro (fondo y
   texto) los inyecta el template inline en `style=""` — pueden venir
   de la paleta institucional o de un hex personalizado, según lo
   que el operador haya elegido en la config del bloque. */
.portal-cards--imagen-overlay-flotante { --card-overlay-aspect: 16 / 10; }

.portal-cards--imagen-overlay-flotante .portal-cards-grid {
    gap: 1rem;
}
.portal-cards--imagen-overlay-flotante .portal-card--overlay {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: var(--card-overlay-aspect, 16 / 10);
    overflow: hidden;
    border-radius: 12px;
    background: var(--c-bg-subtle);
    border: 0;
    padding: 0;
    transition: transform .2s ease, box-shadow .2s ease;
    box-shadow: 0 .2rem .55rem rgba(0,0,0,.08);
}
.portal-cards--imagen-overlay-flotante .portal-card--overlay:hover {
    transform: translateY(-3px);
    box-shadow: 0 .6rem 1.35rem rgba(0,0,0,.14);
}
.portal-cards--imagen-overlay-flotante .portal-card-overlay-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .35s ease;
}
.portal-cards--imagen-overlay-flotante .portal-card--overlay:hover
    .portal-card-overlay-img {
    transform: scale(1.04);
}
.portal-cards--imagen-overlay-flotante .portal-card-overlay-img--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-muted);
    font-size: 2.6rem;
}

/* El bloque flotante: posicionado absoluto dentro de la card, con
   margen horizontal para no quedar pegado a los bordes. El margen
   vertical lo define el modificador `--overlay-<pos>`. */
.portal-cards--imagen-overlay-flotante .portal-card-overlay-bloque {
    position: absolute;
    left: 1rem;
    right: 1rem;
    padding: .75rem 1rem;
    border-radius: 8px;
    font-weight: 600;
    line-height: 1.3;
    box-shadow: 0 .25rem .9rem rgba(0,0,0,.18);
    display: flex;
    align-items: center;
    gap: .5rem;
    overflow-wrap: anywhere;
    /* z-index para quedar por encima del stretched-link. */
    z-index: 2;
    pointer-events: none;
}
.portal-cards--imagen-overlay-flotante .portal-card-overlay-titulo {
    flex: 1 1 auto;
    color: inherit;     /* hereda del recuadro flotante */
    font-size: .98rem;
}

/* Posición vertical del bloque flotante: tres modificadores que se
   aplican a nivel de la grilla (portal-cards--overlay-<pos>). */
.portal-cards--overlay-superior .portal-card-overlay-bloque { top: 1rem; }
.portal-cards--overlay-inferior .portal-card-overlay-bloque { bottom: 1rem; }
.portal-cards--overlay-medio    .portal-card-overlay-bloque {
    top: 50%;
    transform: translateY(-50%);
}

/* Ancho del bloque flotante. Dos modos elegibles por el operador:

   - `banda`    (default): el bloque ocupa todo el ancho de la card
     (banda horizontal). Es el comportamiento histórico — left:1rem,
     right:1rem fuerza el estiramiento.
   - `compacto`: el bloque se ajusta al texto del título (auto-width).
     Combinado con la alineación, el operador puede dejar el chip a
     izquierda, centro o derecha de la imagen. */
.portal-cards--overlay-ancho-compacto .portal-card-overlay-bloque {
    right: auto;
    width: auto;
    max-width: calc(100% - 2rem);
}

/* Alineación horizontal del bloque y/o del texto adentro. Si el ancho
   es `compacto`, además mueve el bloque entero (left/right auto). Si
   es `banda`, solo cambia `text-align` del título. */
.portal-cards--overlay-align-izquierda .portal-card-overlay-bloque {
    text-align: left;
    justify-content: flex-start;
}
.portal-cards--overlay-align-centro .portal-card-overlay-bloque {
    text-align: center;
    justify-content: center;
}
.portal-cards--overlay-align-derecha .portal-card-overlay-bloque {
    text-align: right;
    justify-content: flex-end;
}
/* En modo compacto, la alineación horizontal mueve la posición del
   chip dentro de la card. La banda completa ignora estos overrides
   porque ya cubre todo el ancho. */
.portal-cards--overlay-ancho-compacto.portal-cards--overlay-align-izquierda
    .portal-card-overlay-bloque {
    left: 1rem;
}
.portal-cards--overlay-ancho-compacto.portal-cards--overlay-align-centro
    .portal-card-overlay-bloque {
    left: 50%;
    transform: translateX(-50%);
}
/* En medio + compacto + centro, combinamos los dos translate. */
.portal-cards--overlay-ancho-compacto.portal-cards--overlay-medio.portal-cards--overlay-align-centro
    .portal-card-overlay-bloque {
    transform: translate(-50%, -50%);
}
.portal-cards--overlay-ancho-compacto.portal-cards--overlay-align-derecha
    .portal-card-overlay-bloque {
    left: auto;
    right: 1rem;
}

/* Breakpoint estandar del proyecto (575.98px). Cierra el hueco entre
   el @media 480px (mas abajo en este archivo) y los 768px: en el rango
   481-575.98px la grilla cols-2 con minmax(280px,1fr) podia desbordar
   en slots angostos. Forzamos 1 columna para todas las variantes de
   cards destacadas y reducimos un punto el padding del body. */
@media (max-width: 575.98px) {
    .portal-cards-grid {
        grid-template-columns: 1fr;
    }
    .portal-card-body {
        padding: .85rem 1rem 1rem;
    }
    .portal-card-thumb {
        /* La altura fija de 160px queda excesiva para una card a ancho
           completo en mobile — reducimos un punto para mantener la
           proporcion. */
        height: 140px;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Variante "flip" de cards: giro 3D al click
   ═══════════════════════════════════════════════════════════════════
   Cada card tiene un <article class="portal-card portal-card--flip">
   con un wrapper interno `.portal-card-flip-inner` que rota en Y al
   recibir la clase `is-flipped` (la agrega el JS al click). Las dos
   caras viven absolutamente posicionadas, una de cada lado del wrapper,
   con `backface-visibility: hidden` para que el reverso quede oculto
   mientras la card no está girada.

   Accesibilidad:
   - El <article> es role="button" + tabindex="0" para que screen
     readers y teclado puedan interactuar.
   - `aria-pressed` refleja el estado actual (true = reverso visible).
   - Con `prefers-reduced-motion: reduce`, el flip se desactiva: el
     reverso queda oculto definitivamente y la card se comporta como
     una card común (con imagen si tiene, o solo título).
*/
.portal-cards--flip .portal-cards-grid {
    /* La card flip necesita aspect-ratio fijo para que ambas caras
       midan igual y queden encajadas en el mismo bbox. 4:3 está bien
       para una card estándar; ajustable a futuro si lo pide el caso. */
    --card-flip-aspect: 4 / 3;
}
.portal-card--flip {
    position: relative;
    aspect-ratio: var(--card-flip-aspect);
    /* perspective lleva el plano 3D al elemento padre del que rota,
       sino el flip se ve plano (sin profundidad). 1000px da una
       sensación de "estoy mirando la card de frente". */
    perspective: 1000px;
    background: transparent;
    border: none;
    padding: 0;
    /* El article es interactivo — cursor pointer e foco visible. */
    cursor: pointer;
    overflow: visible;
}
.portal-card--flip:focus-visible {
    outline: 3px solid var(--c-accent);
    outline-offset: 4px;
    border-radius: 12px;
}
.portal-card-flip-inner {
    position: relative;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transition: transform .6s cubic-bezier(.4, 0, .2, 1);
}
.portal-card--flip.is-flipped .portal-card-flip-inner {
    transform: rotateY(180deg);
}
.portal-card-flip-cara {
    position: absolute;
    inset: 0;
    /* Backface hidden: cuando la card está girada, esta cara queda
       hacia atrás y NO se debe ver. Sin esto, ambas caras se ven
       superpuestas (el hint del anverso aparecía espejado al pie
       del reverso). El `translateZ` adicional fuerza una capa de
       composición separada en Chrome/Safari — sin él, algunos
       motores siguen renderizando ambas caras por un glitch del
       compositor 3D. */
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 .25rem .7rem rgba(0, 0, 0, .12);
    display: flex;
    flex-direction: column;
}
.portal-card-flip-anverso {
    /* Anverso (cara visible al cargar): fondo institucional + texto
       claro — es la cara "llamativa" que invita al click. Cuando la
       card tiene imagen, la imagen ocupa toda la cara y el fondo
       primary queda tapado (los selectores --con-imagen /
       --solo-titulo definen el resto). */
    background: var(--c-primary);
    color: var(--c-text-light);
    /* Z separación para evitar glitches de backface en Chrome. */
    transform: translateZ(1px);
}
.portal-card-flip-reverso {
    /* Reverso (cara que se descubre al click): fondo blanco + texto
       oscuro — pensado para lectura cómoda del contenido detallado
       (más texto, más tiempo en pantalla). */
    background: var(--c-bg-card, #fff);
    color: var(--c-text-strong, #1c1917);
    /* El reverso gira 180° + un Z mínimo (capa separada). */
    transform: rotateY(180deg) translateZ(1px);
    padding: 1.25rem 1.1rem;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: .65rem;
}
/* Anverso CON imagen: la imagen ocupa toda la card. El título NO
   se renderiza acá (queda como "secreto" del reverso) — el hint
   queda absolute con gradiente al pie para legibilidad sobre
   cualquier foto. */
.portal-card-flip-anverso--con-imagen {
    position: absolute;
    inset: 0;
}
.portal-card-flip-anverso-imagen {
    position: absolute;
    inset: 0;
    overflow: hidden;
}
.portal-card-flip-anverso-imagen img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-card-flip-anverso--con-imagen .portal-card-flip-hint {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 1rem 1rem .85rem;
    color: var(--c-text-light);
    background: linear-gradient(to top,
        rgba(0, 0, 0, .55) 0%,
        rgba(0, 0, 0, .35) 60%,
        rgba(0, 0, 0, 0) 100%);
    justify-content: center;
    opacity: 1;
    z-index: 2;
}
/* Anverso SIN imagen: título grande centrado como protagonista,
   ocupando toda la card. El hint queda al pie del body. */
.portal-card-flip-anverso--solo-titulo .portal-card-flip-anverso-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 1.5rem 1.25rem;
    gap: 1rem;
}
.portal-card-flip-anverso--solo-titulo .portal-card-flip-titulo {
    /* Texto claro sobre el fondo institucional del anverso. Tamaño
       grande para que el título sea protagonista cuando no hay
       imagen. */
    color: var(--c-text-light);
    font-size: 1.85rem;
    font-weight: 700;
    margin: 0;
    line-height: 1.2;
    overflow-wrap: anywhere;
    /* Tope para evitar que un título larguísimo desborde la card. */
    max-width: 100%;
}
.portal-card-flip-anverso--solo-titulo .portal-card-flip-hint {
    color: var(--c-text-light);
    opacity: .75;
}
.portal-card-flip-reverso-titulo {
    color: inherit;
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0 0 .25rem;
    line-height: 1.25;
    /* Wrap por caracter para titulos largos en el reverso angosto. */
    overflow-wrap: anywhere;
}
.portal-card-flip-reverso-texto {
    color: inherit;
    font-size: .95rem;
    line-height: 1.45;
    margin: 0;
    /* Overflow: si el texto es largo, scroll dentro de la card en
       lugar de romper el aspect-ratio. */
    overflow: auto;
    max-height: 100%;
    /* Idem titulo: el reverso es angosto (aspect 4:3) — permitimos
       que palabras sin espacios rompan antes de forzar scroll horizontal. */
    overflow-wrap: anywhere;
}
.portal-card-flip-hint {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .78rem;
    opacity: .72;
    margin-top: auto;
    font-weight: 500;
}
.portal-card-flip-reverso .portal-card-flip-hint {
    color: inherit;
}
/* Fallback para reduce-motion: anulamos toda la mecánica 3D. El
   reverso queda oculto (display: none) y el anverso ocupa 100%
   sin rotación. La card sigue siendo clickable pero no hace nada
   visualmente — se comporta como una card común con título e
   imagen opcional. Removemos el cursor: pointer para no engañar
   al usuario. */
@media (prefers-reduced-motion: reduce) {
    .portal-card--flip {
        cursor: default;
    }
    .portal-card-flip-inner {
        transition: none;
        transform: none !important;
    }
    .portal-card-flip-reverso {
        display: none;
    }
    .portal-card-flip-anverso .portal-card-flip-hint {
        display: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Grilla de noticias
   ═══════════════════════════════════════════════════════════════════ */

.portal-noticias-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
}
.portal-noticias-grid {
    display: grid;
    gap: 1.25rem;
}
/* `cols-1`: noticias apiladas verticalmente — pensado para zona
   lateral, pero también sirve para "feed" de ancho completo. */
.portal-noticias--cols-1 .portal-noticias-grid { grid-template-columns: 1fr; }
.portal-noticias--cols-2 .portal-noticias-grid { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); max-width: 900px; }
.portal-noticias--cols-3 .portal-noticias-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.portal-noticias--cols-4 .portal-noticias-grid { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.portal-noticia {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform .18s ease, box-shadow .18s ease;
}
.portal-noticia:hover {
    transform: translateY(-3px);
    box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.08);
}
.portal-noticia-thumb {
    /* Aspect-ratio 16:10 alineado con la thumb que genera el sistema
       (`NoticiaImagen.thumb` es 1024×640). Antes usaba `height: 180px`
       fijo con ancho variable según columnas, lo que producía slots
       con ratios inconsistentes y forzaba el `cover` a recortar de más. */
    aspect-ratio: 16 / 10;
    background-size: cover;
    background-position: center;
    background-color: var(--c-primary-lighter);
    display: block;
}
.portal-noticia-thumb--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-text-brand);
    font-size: 2.5rem;
    background: var(--c-primary-lighter);
}
.portal-noticia-body {
    padding: 1rem 1.1rem 1.25rem;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.portal-noticia-fecha {
    color: var(--c-muted);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}
.portal-noticia-titulo {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1.3;
    /* Texto cargado por el operador: una URL pegada, un hashtag o una
       palabra muy larga puede romper el ancho del card en mobile. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-noticia-titulo a { color: var(--c-text-strong); text-decoration: none; }
.portal-noticia-titulo a:hover { color: var(--c-text-brand); text-decoration: none; }
.portal-noticia-copete {
    color: var(--c-text-soft);
    font-size: .9rem;
    margin: 0;
    /* Idem titulo: el truncatechars del template limita la longitud pero
       no parte un token sin espacios. */
    overflow-wrap: anywhere;
    word-break: break-word;
}

.portal-noticias-sin-datos {
    background: var(--c-bg-subtle);
    color: var(--c-muted);
    border-radius: 8px;
    padding: 1.5rem;
    text-align: center;
    margin: 0;
}

/* Mobile xs (≤575.98px) — los overrides consolidados del archivo viven
   en 480/768/1024, pero el estandar del proyecto es 575.98 (Bootstrap xs).
   Cubrimos la franja 481-575.98 forzando 1 columna para que el minmax
   de cols-2 (320px) no termine generando una unica columna mas ancha
   que el viewport con paddings de container chicos. */
@media (max-width: 575.98px) {
    .portal-noticias--cols-2 .portal-noticias-grid,
    .portal-noticias--cols-3 .portal-noticias-grid,
    .portal-noticias--cols-4 .portal-noticias-grid {
        grid-template-columns: 1fr;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Bloque de autoridades
   4 variantes:
     - mosaico-circular   (default; foto circular grande)
     - cuadradas-uniforme (estilo Hacienda; foto cuadrada/retrato)
     - jerarquico-titular (titular destacado + grilla a su costado)
     - lista-compacta     (filas con foto chica + datos al lado)
   ═══════════════════════════════════════════════════════════════════ */

.portal-autoridades-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
}

/* ── Card base — usada por las variantes en grilla ──────────────── */
.portal-autoridad {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 12px;
    padding: 1.25rem 1rem;
    transition: transform .18s ease, box-shadow .18s ease;
    text-align: center;
    display: flex;
    flex-direction: column;
}
.portal-autoridad:hover {
    transform: translateY(-3px);
    box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.08);
}
.portal-autoridad-foto {
    width: 110px;
    height: 110px;
    margin: 0 auto .75rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--c-primary-lighter);
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Mosaico circular: anillo de acento alrededor de la foto. Usa la
   variante sólida del color_acento (los slugs de gradiente degradan a
   un color sólido para no romper el border-radius). */
.portal-autoridades--mosaico-circular .portal-autoridad-foto {
    border: 3px solid var(--portal-c-acento-solid, var(--c-primary));
}
.portal-autoridad-foto img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-autoridad-foto-placeholder {
    color: var(--c-text-brand);
    font-size: 2.8rem;
}
.portal-autoridad-info {
    display: flex;
    flex-direction: column;
    gap: .15rem;
}
.portal-autoridad-nombre {
    color: var(--c-text-strong);
    font-size: 1rem;
    line-height: 1.25;
}
.portal-autoridad-cargo {
    color: var(--c-text-brand);
    font-size: .85rem;
    font-weight: 600;
}
.portal-autoridad-area {
    color: var(--c-muted);
    font-size: .78rem;
    font-style: italic;
}

/* ── Variante 1: mosaico-circular (default) ─────────────────────── */
.portal-autoridades--mosaico-circular .portal-autoridades-grid {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
/* Sin overrides — la card base ya es circular. */


/* ── Variante 2: cuadradas-uniforme (estilo Hacienda) ───────────── */
.portal-autoridades--cuadradas-uniforme .portal-autoridades-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.portal-autoridades--cuadradas-uniforme .portal-autoridad {
    padding: 0;
    overflow: hidden;
    /* Banda de acento arriba de la card — pintada con border-top.
       Usa la variante sólida del color_acento (border no acepta
       gradiente). */
    border-top: 6px solid var(--portal-c-acento-solid, var(--c-primary));
}
.portal-autoridades--cuadradas-uniforme .portal-autoridad-foto {
    width: 100%;
    height: 220px;
    margin: 0;
    border-radius: 0;
    aspect-ratio: 1 / 1;
}
.portal-autoridades--cuadradas-uniforme .portal-autoridad-foto img {
    object-position: center top;   /* prioriza la cabeza al recortar */
}
.portal-autoridades--cuadradas-uniforme .portal-autoridad-info {
    padding: .85rem 1rem 1.1rem;
    gap: .25rem;
}
.portal-autoridades--cuadradas-uniforme .portal-autoridad-nombre {
    font-size: 1.05rem;
}


/* ── Variante 3: jerarquico-titular ─────────────────────────────── */
.portal-autoridades-jer {
    display: grid;
    grid-template-columns: minmax(280px, 360px) 1fr;
    gap: 1.5rem;
    align-items: start;
}
@media (max-width: 900px) {
    .portal-autoridades-jer { grid-template-columns: 1fr; }
}

.portal-autoridad-titular {
    background: linear-gradient(180deg, var(--c-primary-lighter) 0%, var(--c-bg-card) 60%);
    border: 1px solid var(--c-primary-lighter);
    border-radius: 14px;
    padding: 1.5rem 1.25rem 1.75rem;
    text-align: center;
    box-shadow: 0 .25rem .75rem rgba(0,0,0,.05);
    position: sticky;
    top: 90px;   /* evita que se vaya con scroll cuando hay mucha grilla */
    /* Detalle de acento sobre el borde superior — respeta el
       color_acento del bloque. */
    border-top: 4px solid var(--portal-c-acento-solid, var(--c-primary));
}
.portal-autoridad-titular-foto {
    width: 180px;
    height: 180px;
    margin: 0 auto 1rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--c-bg-card);
    border: 4px solid var(--c-bg-card);
    box-shadow: 0 .25rem 1rem rgba(0,0,0,.1);
    display: flex;
    align-items: center;
    justify-content: center;
}
.portal-autoridad-titular-foto img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.portal-autoridad-titular-foto .portal-autoridad-foto-placeholder {
    color: var(--c-text-brand);
    font-size: 4.5rem;
}
.portal-autoridad-titular-info {
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.portal-autoridad-titular-nombre {
    font-size: 1.35rem;
    font-weight: 800;
    color: var(--c-text-strong);
    line-height: 1.2;
}
.portal-autoridad-titular-cargo {
    color: var(--c-text-brand);
    font-size: 1rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
}
.portal-autoridad-titular-area {
    color: var(--c-muted);
    font-size: .9rem;
    font-style: italic;
}
.portal-autoridad-titular-bio {
    margin: .85rem 0 0;
    font-size: .88rem;
    color: var(--c-text-soft);
    line-height: 1.55;
    text-align: left;
    border-top: 1px solid var(--c-border-light);
    padding-top: .85rem;
}
.portal-autoridad-titular-contacto {
    margin-top: .85rem;
    display: flex;
    flex-direction: column;
    gap: .35rem;
    align-items: center;
}
.portal-autoridad-contacto-link {
    color: var(--c-text-brand);
    text-decoration: none;
    font-size: .82rem;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
}
.portal-autoridad-contacto-link:hover { text-decoration: underline; }

.portal-autoridades-jer-grilla {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: .75rem;
    align-content: start;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini {
    padding: .85rem .65rem 1rem;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini .portal-autoridad-foto {
    width: 80px;
    height: 80px;
    margin-bottom: .55rem;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini .portal-autoridad-foto-placeholder {
    font-size: 1.85rem;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini .portal-autoridad-nombre {
    font-size: .88rem;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini .portal-autoridad-cargo {
    font-size: .76rem;
}
.portal-autoridades-jer-grilla .portal-autoridad--mini .portal-autoridad-area {
    font-size: .72rem;
}


/* ── Variante 4: lista-compacta (zona lateral) ──────────────────── */
.portal-autoridades-lista {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.portal-autoridad-fila {
    display: flex;
    align-items: center;
    gap: .85rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 8px;
    padding: .55rem .75rem;
    transition: border-color .15s ease, background .15s ease;
}
.portal-autoridad-fila:hover {
    border-color: var(--c-primary-light);
    background: var(--c-bg-subtle);
}
.portal-autoridad-fila-foto {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--c-primary-lighter);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.portal-autoridad-fila-foto img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.portal-autoridad-fila-foto .portal-autoridad-foto-placeholder {
    font-size: 1.3rem;
    color: var(--c-text-brand);
}
.portal-autoridad-fila-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}
.portal-autoridad-fila-info strong {
    color: var(--c-text-strong);
    font-size: .9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.portal-autoridad-fila-info span {
    color: var(--c-text-brand);
    font-weight: 600;
    font-size: .78rem;
}
.portal-autoridad-fila-info small {
    color: var(--c-muted);
    font-size: .72rem;
    font-style: italic;
}


/* ── Botón "Mostrar ubicación" en cards ──────────────────────────
   Aparece al pie de cada card cuando la autoridad tiene cargados
   latitud/longitud o dirección. Abre Google Maps en pestaña nueva.
   Background: var(--portal-c-acento) — respeta el color_acento del
   bloque (cae al primary si no hay).
   Texto: var(--c-bg) para máximo contraste sobre acento sólido. */
.portal-autoridad-ubicacion {
    margin-top: .9rem;
    align-self: center;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .45rem .9rem;
    font-size: .78rem;
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    border-radius: 999px;
    background: var(--portal-c-acento, var(--c-primary));
    color: var(--c-text-light);
    transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.portal-autoridad-ubicacion:hover,
.portal-autoridad-ubicacion:focus-visible {
    transform: translateY(-1px);
    box-shadow: 0 .25rem .65rem rgba(0,0,0,.12);
    color: var(--c-text-light);
    text-decoration: none;
    opacity: .92;
}
.portal-autoridad-ubicacion i {
    font-size: .95rem;
}
/* Cards "mini" del jerarquico-titular: botón más chico. */
.portal-autoridad--mini .portal-autoridad-ubicacion {
    margin-top: .6rem;
    padding: .32rem .7rem;
    font-size: .7rem;
}
.portal-autoridad--mini .portal-autoridad-ubicacion i {
    font-size: .82rem;
}
/* Titular del jerarquico: el botón vive fuera del .portal-autoridad-info,
   se centra y respira un poco más del contacto. */
.portal-autoridad-titular > .portal-autoridad-titular-info > .portal-autoridad-ubicacion {
    margin-top: 1rem;
}


/* ── Ícono de ubicación en la lista-compacta ─────────────────────
   Botón circular pequeño al costado derecho de cada píldora.
   Solo el ícono — la etiqueta queda en `aria-label` / `title`. */
.portal-autoridad-fila-ubicacion {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--portal-c-acento, var(--c-primary));
    color: var(--c-text-light);
    font-size: 1rem;
    text-decoration: none;
    transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.portal-autoridad-fila-ubicacion:hover,
.portal-autoridad-fila-ubicacion:focus-visible {
    transform: scale(1.08);
    box-shadow: 0 .15rem .5rem rgba(0,0,0,.15);
    color: var(--c-text-light);
    text-decoration: none;
    opacity: .92;
}

/* ── Mobile (≤ 575.98px) — endurecemos word-wrap en textos del operador
   para que cargos/áreas/biografías largas y emails no rompan el ancho
   de las cards en viewports de ~390-430px. Los nombres/cargos/áreas
   los carga el operador, así que pueden ser arbitrariamente largos. */
@media (max-width: 575.98px) {
    .portal-autoridad-nombre,
    .portal-autoridad-cargo,
    .portal-autoridad-area,
    .portal-autoridad-titular-nombre,
    .portal-autoridad-titular-cargo,
    .portal-autoridad-titular-area,
    .portal-autoridad-titular-bio {
        overflow-wrap: anywhere;
        word-break: break-word;
    }
    /* El email del titular es texto plano dentro de un inline-flex —
       sin esto, un email largo empuja el ancho de toda la card. */
    .portal-autoridad-contacto-link {
        max-width: 100%;
        overflow-wrap: anywhere;
        word-break: break-word;
    }
    /* Lista-compacta: el nombre ya recortaba con ellipsis; aplicamos lo
       mismo a cargo y área para que tampoco desborden la fila. */
    .portal-autoridad-fila-info span,
    .portal-autoridad-fila-info small {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Grilla de accesos directos — 4 variantes
   ═══════════════════════════════════════════════════════════════════
   El bloque usa `portal-accesos--<variante>` para alternar layout y
   estilo, y `portal-accesos--cols-<N>` (2/3/4) para el grid.
   Variantes:
     · circulos-centrados  Círculos sobre fondo del portal, texto bajo el círculo.
     · pildora-icono-izq   Píldora horizontal con ícono a la izquierda.
     · con-imagen          Card con imagen (o ícono) arriba y texto debajo.
     · solo-imagenes       Sólo imágenes, sin texto ni ícono.
   `--sin-icono` (en variantes con ícono) hace que el título ocupe toda
   la card cuando el operador deja el ícono vacío.
   ═══════════════════════════════════════════════════════════════════ */

.portal-accesos-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
}
.portal-accesos-grid {
    display: grid;
    gap: 1rem;
    /* El min ancho se sube para 4 columnas (con cols-4) y se baja para 2.
       Con `repeat(<N>, 1fr)` forzamos exactamente N columnas en desktop;
       en mobile colapsa a auto-fit con minmax. */
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
/* `cols-1`: accesos apilados — útil en zona lateral o como menú en
   columna. Para "solo-imagenes" y demás variantes con thumb, el item
   se estira al ancho disponible del slot. */
.portal-accesos--cols-1 .portal-accesos-grid { grid-template-columns: 1fr; }
.portal-accesos--cols-2 .portal-accesos-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.portal-accesos--cols-3 .portal-accesos-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.portal-accesos--cols-4 .portal-accesos-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 800px) {
    .portal-accesos--cols-3 .portal-accesos-grid,
    .portal-accesos--cols-4 .portal-accesos-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 480px) {
    .portal-accesos-grid,
    .portal-accesos--cols-2 .portal-accesos-grid,
    .portal-accesos--cols-3 .portal-accesos-grid,
    .portal-accesos--cols-4 .portal-accesos-grid {
        grid-template-columns: 1fr;
    }
}

/* ── Item base (lo redefine cada variante) ─────────────────────── */

.portal-acceso {
    display: flex;
    align-items: center;
    gap: .65rem;
    text-decoration: none;
    color: var(--c-text-strong);
    transition: transform .15s ease, box-shadow .15s ease, color .15s ease, background-color .15s ease;
    position: relative;
}
.portal-acceso:hover {
    text-decoration: none;
}
.portal-acceso-icono {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    /* Fondo: usa el color_acento del bloque cuando hay (vía
       .portal-acento--<slug>); fallback al lighter institucional. */
    background: var(--portal-c-acento, var(--c-primary-lighter));
    /* Color del glifo: usa el color_texto del acceso si está seteado.
       Cae al primary cuando el operador no eligió ninguno. */
    color: var(--portal-acceso-color-texto, var(--c-primary));
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    flex: 0 0 auto;
}
.portal-acceso-titulo {
    font-weight: 600;
    font-size: .95rem;
    line-height: 1.3;
    /* El operador puede meter titulos largos o URLs sin espacios — habilitamos
       wrap agresivo para que no desborden la pildora (flex-row) ni la card
       en viewports angostos (~390px). */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-acceso-externo {
    position: absolute;
    top: .5rem;
    right: .5rem;
    color: var(--c-muted);
    font-size: .8rem;
}

/* ── Variante: círculos centrados sobre fondo del portal ───────── */
/* "Card invisible": sin fondo, sin borde, sin sombra. El círculo del
   ícono es la única superficie con peso visual; el título queda en
   color institucional debajo. */
.portal-accesos--circulos-centrados .portal-acceso {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: .75rem;
    background: transparent;
    border: 0;
    padding: 1rem .5rem;
    color: var(--c-text-brand);
}
.portal-accesos--circulos-centrados .portal-acceso-icono {
    width: 78px;
    height: 78px;
    font-size: 2.1rem;
    /* Tono más fuerte para que el círculo respire sin la card.
       Respeta el color_acento del bloque si está seteado. */
    background: var(--portal-c-acento, var(--c-primary-lighter));
    color: var(--portal-acceso-color-texto, var(--c-primary));
    box-shadow: 0 .25rem .85rem rgba(0,0,0,.06);
    transition: transform .18s ease, box-shadow .18s ease;
}
.portal-accesos--circulos-centrados .portal-acceso:hover .portal-acceso-icono {
    transform: translateY(-3px) scale(1.04);
    box-shadow: 0 .55rem 1.1rem rgba(0,0,0,.1);
}
.portal-accesos--circulos-centrados .portal-acceso-titulo {
    color: var(--portal-acceso-color-texto, var(--c-primary));
    font-weight: 600;
}
.portal-accesos--circulos-centrados .portal-acceso--sin-icono {
    /* Sin ícono: el título crece y vive solo, manteniendo el aire de
       la "card invisible". */
    padding: 1.5rem .75rem;
}
.portal-accesos--circulos-centrados .portal-acceso--sin-icono .portal-acceso-titulo {
    font-size: 1.1rem;
    font-weight: 700;
}

/* ── Variante: píldora con ícono a la izquierda ────────────────── */
.portal-accesos--pildora-icono-izq .portal-acceso {
    flex-direction: row;
    align-items: center;
    gap: .85rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 999px;
    padding: .55rem 1.25rem .55rem .55rem;
    /* Respetar el color_texto elegido por el operador. El título
       hereda este color del link (no tiene rule propia), y el ícono
       resuelve `--portal-acceso-color-texto` directamente desde la
       base `.portal-acceso-icono`. */
    color: var(--portal-acceso-color-texto, var(--c-text-strong));
}
.portal-accesos--pildora-icono-izq .portal-acceso:hover {
    border-color: var(--c-primary);
    color: var(--portal-acceso-color-texto, var(--c-primary));
    box-shadow: 0 .35rem 1rem rgba(0,0,0,.06);
    transform: translateY(-2px);
}
.portal-accesos--pildora-icono-izq .portal-acceso-icono {
    width: 44px;
    height: 44px;
    font-size: 1.3rem;
    /* El ícono vive sobre el círculo de acento (típicamente primary) y
       necesita un color propio independiente del título (que está sobre
       el fondo blanco de la píldora). Caemos a --c-text-light (blanco)
       para asegurar contraste sobre primary cuando el operador no eligió
       nada. La regla está acá — no en la base — para no romper otras
       variantes donde ícono y título comparten fondo. */
    color: var(--portal-acceso-color-icono, var(--c-text-light));
}
.portal-accesos--pildora-icono-izq .portal-acceso-titulo {
    flex: 1 1 auto;
    text-align: left;
}
.portal-accesos--pildora-icono-izq .portal-acceso--sin-icono {
    /* Sin ícono: la píldora se vuelve un "chip" centrado con el título
       ocupando todo el ancho. */
    justify-content: center;
    padding: .85rem 1.25rem;
}
.portal-accesos--pildora-icono-izq .portal-acceso--sin-icono .portal-acceso-titulo {
    text-align: center;
    flex: 0 1 auto;
}

/* ── Orientación de las imágenes ────────────────────────────────
   Variable `--acc-img-aspect` consumida por las variantes con imagen
   (`con-imagen` y `solo-imagenes`). Se setea según el modificador
   `--img-<orientacion>`. La elección es a nivel bloque para mantener
   la grilla pareja: todas las imágenes se recortan al mismo ratio. */
.portal-accesos--img-horizontal { --acc-img-aspect: 16 / 10; }
.portal-accesos--img-cuadrada   { --acc-img-aspect: 1 / 1; }
.portal-accesos--img-vertical   { --acc-img-aspect: 3 / 4; }

/* ── Variante: cards con imagen ───────────────────────────────── */
.portal-accesos--con-imagen .portal-acceso {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 12px;
    padding: 0;
    overflow: hidden;
    /* Mismo criterio que las píldoras: el título hereda el color del
       link, así que el color_texto del acceso aplica vía esta var. */
    color: var(--portal-acceso-color-texto, var(--c-text-strong));
}
.portal-accesos--con-imagen .portal-acceso:hover {
    border-color: var(--c-primary);
    transform: translateY(-2px);
    box-shadow: 0 .55rem 1.2rem rgba(0,0,0,.08);
    color: var(--portal-acceso-color-texto, var(--c-primary));
}
.portal-accesos--con-imagen .portal-acceso-img-wrap {
    display: block;
    width: 100%;
    aspect-ratio: var(--acc-img-aspect, 16 / 10);
    overflow: hidden;
    background: var(--c-bg-subtle);
}
.portal-accesos--con-imagen .portal-acceso-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .25s ease;
}
.portal-accesos--con-imagen .portal-acceso:hover .portal-acceso-img {
    transform: scale(1.04);
}
.portal-accesos--con-imagen .portal-acceso-icono {
    /* Fallback cuando no hay imagen: el ícono ocupa la "ranura" de la
       imagen como cabecera de la card, con el mismo aspect-ratio
       elegido para que la grilla quede uniforme. */
    width: 100%;
    height: auto;
    aspect-ratio: var(--acc-img-aspect, 16 / 10);
    border-radius: 0;
    font-size: 2.6rem;
}
.portal-accesos--con-imagen .portal-acceso-titulo {
    padding: .85rem 1rem 1rem;
    font-size: 1rem;
    font-weight: 600;
}
.portal-accesos--con-imagen .portal-acceso--sin-icono {
    /* Sin imagen ni ícono: la card se reduce a su título centrado y
       grande, con paddings amplios. */
    align-items: center;
    justify-content: center;
    min-height: 110px;
}
.portal-accesos--con-imagen .portal-acceso--sin-icono .portal-acceso-titulo {
    text-align: center;
    font-size: 1.15rem;
    padding: 1.5rem 1rem;
}

/* ── Variante: sólo imágenes ───────────────────────────────────── */
/* En esta variante el ratio default es cuadrado (mejor para mosaicos
   de logos/escudos). Si el operador elige otra orientación, el
   `--acc-img-aspect` la pisa. */
.portal-accesos--solo-imagenes { --acc-img-aspect: 1 / 1; }
.portal-accesos--solo-imagenes.portal-accesos--img-horizontal { --acc-img-aspect: 16 / 10; }
.portal-accesos--solo-imagenes.portal-accesos--img-vertical   { --acc-img-aspect: 3 / 4; }
.portal-accesos--solo-imagenes .portal-accesos-grid {
    gap: .65rem;
}
.portal-accesos--solo-imagenes .portal-acceso--solo-imagen {
    display: block;
    width: 100%;
    aspect-ratio: var(--acc-img-aspect, 1 / 1);
    overflow: hidden;
    border-radius: 10px;
    border: 1px solid var(--c-border);
    background: var(--c-bg-subtle);
    padding: 0;
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.portal-accesos--solo-imagenes .portal-acceso--solo-imagen:hover {
    transform: translateY(-2px);
    border-color: var(--c-primary);
    box-shadow: 0 .55rem 1.2rem rgba(0,0,0,.08);
}
.portal-accesos--solo-imagenes .portal-acceso-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .3s ease;
}
.portal-accesos--solo-imagenes .portal-acceso--solo-imagen:hover .portal-acceso-img {
    transform: scale(1.05);
}

/* ── Ícono superpuesto sobre la imagen ────────────────────────────
   Caso: variante `con-imagen` + acceso con ícono cargado y título
   vacío. El ícono se centra sobre la imagen, pintado con el
   color_texto del acceso. La clase `--icono-overlay` se agrega en el
   template solo bajo esa combinación. La especificidad
   (`.portal-accesos--con-imagen .portal-acceso-icono--overlay`) está
   pensada para ganarle a la regla genérica
   `.portal-accesos--con-imagen .portal-acceso-icono` (mismo peso,
   pero esta viene después en el archivo). */
.portal-accesos--con-imagen .portal-acceso--icono-overlay .portal-acceso-img-wrap {
    position: relative;
}
.portal-accesos--con-imagen .portal-acceso-icono--overlay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;       /* anula el 16/10 heredado de la regla base */
    background: transparent;
    border-radius: 0;
    color: var(--portal-acceso-color-texto, var(--c-bg));
    font-size: clamp(2.4rem, 6vw, 4rem);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Sombra suave para que el ícono mantenga legibilidad sobre
       imágenes claras o ruidosas. */
    text-shadow: 0 .15rem .6rem rgba(0, 0, 0, .35);
    pointer-events: none;
}

/* ── Variante: cards de solo texto ────────────────────────────────
   Replica el patrón "Secretarías" de Hacienda: cards rectangulares
   con el color de acento como fondo y un único título centrado.
   El color del texto sale del color_texto per-item (custom-property
   inline en el <a>). */
.portal-accesos--cards-texto .portal-accesos-grid {
    gap: .85rem;
}
.portal-accesos--cards-texto .portal-acceso--solo-texto {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    /* Fondo: usa el color_acento del bloque. Si no hay, primary. */
    background: var(--portal-c-acento, var(--c-primary));
    border-radius: 8px;
    padding: 1.25rem 1rem;
    min-height: 64px;
    /* Color del título: si el operador eligió color_texto, lo respeta;
       si no, blanco (asume fondo oscuro). */
    color: var(--portal-acceso-color-texto, var(--c-bg));
    font-weight: 700;
    font-size: 1rem;
    transition: transform .15s ease, box-shadow .15s ease, filter .15s ease;
}
.portal-accesos--cards-texto .portal-acceso--solo-texto:hover {
    transform: translateY(-2px);
    box-shadow: 0 .55rem 1.2rem rgba(0, 0, 0, .12);
    filter: brightness(1.05);
    color: var(--portal-acceso-color-texto, var(--c-bg));
}
.portal-accesos--cards-texto .portal-acceso-titulo {
    line-height: 1.25;
    /* Aplicamos el color_texto del acceso explícitamente acá además
       de en el <a> padre. La inheritance sola no era confiable en
       algunos browsers cuando el `color` del <a> se setea con un
       `var()` que a su vez resuelve a otro `var()` (chain via custom
       property). Setearlo directo sobre el titulo elimina el problema. */
    color: var(--portal-acceso-color-texto, var(--c-bg));
}
/* Idem para píldora y con-imagen: el operador puede setear color_texto
   y antes dependíamos puro de herencia desde el <a>. Lo declaramos
   directo sobre el titulo para que el chain de `var()` se evalúe en el
   propio elemento. Especificidad 0,2,1 — gana sobre la regla base
   `.portal-acceso-titulo` (0,1,0) sin necesidad de !important. */
.portal-accesos--pildora-icono-izq .portal-acceso-titulo,
.portal-accesos--con-imagen .portal-acceso-titulo {
    color: var(--portal-acceso-color-texto, var(--c-text-strong));
}

/* ── Mobile (≤ 575.98px, Bootstrap xs) ──────────────────────────────
   Las reglas mobile del bloque viven historicamente en breakpoints
   480px y 800px (ver mas abajo en el archivo). Esto deja sin cubrir
   el rango 481-575.98px (celulares en landscape, ventanas angostas),
   que es justo donde el preview mobile del page builder simula. Esta
   media query alinea el bloque al breakpoint estandar del proyecto
   sin tocar las reglas existentes. */
@media (max-width: 575.98px) {
    /* Todas las variantes colapsan a 1 columna en mobile real. */
    .portal-accesos-grid,
    .portal-accesos--cols-2 .portal-accesos-grid,
    .portal-accesos--cols-3 .portal-accesos-grid,
    .portal-accesos--cols-4 .portal-accesos-grid {
        grid-template-columns: 1fr;
    }
    /* Circulos: icono mas chico para no comerse la pantalla. */
    .portal-accesos--circulos-centrados .portal-acceso-icono {
        width: 64px; height: 64px; font-size: 1.7rem;
    }
    /* Pildora: padding y tipografia reducidos. */
    .portal-accesos--pildora-icono-izq .portal-acceso {
        padding: .5rem .8rem .5rem .5rem;
    }
    .portal-accesos--pildora-icono-izq .portal-acceso-icono {
        width: 38px; height: 38px; font-size: 1.1rem;
    }
    .portal-accesos--pildora-icono-izq .portal-acceso-titulo {
        font-size: .9rem;
    }
    /* Titulo de seccion mas chico para que no rompa linea raro. */
    .portal-accesos-titulo {
        font-size: 1.35rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Feed RSS externo
   ═══════════════════════════════════════════════════════════════════
   El bloque levanta noticias de un feed RSS/Atom externo (ej:
   prensa.jujuy.gob.ar) y las lista con título + fecha + descripción.
   Pensado para usarse 1×1 (ancho full) o de a dos (ancho half) lado
   a lado — replica el patrón de las dos columnas RSS que tenían los
   WP viejos (Prensa Jujuy + Últimas noticias). */
.portal-rss { margin-block: 1rem; }

.portal-rss-titulo {
    color: var(--c-text-brand);
    font-size: 1.4rem;
    font-weight: 700;
    margin: 0 0 1rem;
    display: flex;
    align-items: center;
    gap: .5rem;
    /* Si el titulo configurado por el operador es largo, permitir que
       baje de linea en lugar de empujar el ancho del bloque. */
    flex-wrap: wrap;
}
.portal-rss-titulo-link {
    color: inherit;
    text-decoration: none;
    /* Asegura que el link como flex-item pueda encogerse y que un titulo
       sin espacios (ej. una URL pegada) se quiebre en lugar de desbordar. */
    min-width: 0;
    overflow-wrap: anywhere;
}
.portal-rss-titulo-link:hover {
    text-decoration: underline;
}
.portal-rss-icono {
    color: var(--c-accent, #f60);
    font-size: 1rem;
    flex: 0 0 auto;
}

.portal-rss-lista {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: .85rem;
}
.portal-rss-item {
    padding: .65rem .85rem .75rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light, var(--c-border));
    border-left: 3px solid var(--c-primary);
    border-radius: 6px;
    transition: transform .15s ease, box-shadow .15s ease;
}
.portal-rss-item:hover {
    transform: translateX(2px);
    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .06);
}
.portal-rss-item-titulo {
    display: block;
    color: var(--c-text-strong);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.35;
    text-decoration: none;
    /* El contenido del feed es externo: forzar quiebre de URLs o palabras
       largas para que no desborden el ancho del bloque en mobile. */
    overflow-wrap: anywhere;
}
.portal-rss-item-titulo:hover {
    color: var(--c-text-brand);
    text-decoration: underline;
}
.portal-rss-item-fecha {
    display: block;
    font-size: .8rem;
    color: var(--c-muted);
    margin-top: .25rem;
}
.portal-rss-item-descripcion {
    margin: .4rem 0 0;
    color: var(--c-text-soft);
    font-size: .92rem;
    line-height: 1.5;
    /* Descripcion proveniente del feed: puede incluir URLs largas. */
    overflow-wrap: anywhere;
}

.portal-rss-error,
.portal-rss-vacio {
    padding: 1rem;
    border-radius: 6px;
    background: var(--c-bg-subtle);
    color: var(--c-muted);
    font-size: .9rem;
    font-style: italic;
    text-align: center;
}


/* ═══════════════════════════════════════════════════════════════════
   Listado de documentos
   ═══════════════════════════════════════════════════════════════════ */

.portal-documentos-titulo {
    color: var(--c-text-brand);
    font-size: 1.4rem;
    font-weight: 700;
    margin: 0 0 1rem;
}
.portal-documentos-lista {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.portal-documento {
    display: flex;
    align-items: center;
    gap: .85rem;
    /* min-width:0 evita que el card crezca por encima del lateral cuando
       el nombre del archivo es muy largo y sin espacios. */
    min-width: 0;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 8px;
    padding: .65rem .85rem;
    transition: border-color .15s ease, background .15s ease;
}
.portal-documento:hover {
    border-color: var(--c-primary);
    background: var(--c-primary-lighter);
}
.portal-documento-icono {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    flex-shrink: 0;
}
.portal-documento-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.portal-documento-titulo {
    /* display:block + max-width:100% es necesario para que el
       text-overflow:ellipsis funcione en este <a> (los inline no
       admiten ellipsis). */
    display: block;
    max-width: 100%;
    color: var(--c-text-strong);
    font-weight: 600;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.portal-documento-titulo:hover { color: var(--c-text-brand); text-decoration: none; }
.portal-documento-meta {
    color: var(--c-muted);
    font-size: .78rem;
    /* la meta puede contener nombres de tipo/peso largos; permitimos
       quiebre suave para evitar overflow del card. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-documento-descarga {
    color: var(--c-text-disabled);
    font-size: 1rem;
    flex-shrink: 0;
}
.portal-documento:hover .portal-documento-descarga {
    color: var(--c-text-brand);
}


/* ═══════════════════════════════════════════════════════════════════
   Texto libre (TipTap)
   ═══════════════════════════════════════════════════════════════════ */

.portal-texto {
    margin-block: 1rem;
    /* Evita el overflow cuando el bloque vive dentro de un contenedor
       flex/grid (zonas `destacados`, `lateral`, etc.): por default los
       items flex tienen `min-width: auto` y se niegan a encoger por
       debajo del min-content de sus hijos. Esto, combinado con texto
       sin wrap, hacía que la caja se saliera del viewport en mobile. */
    min-width: 0;
    max-width: 100%;
}
.portal-texto--center { text-align: center; }
.portal-texto--right  { text-align: right; }

/* Cap del line-length para legibilidad — el bloque queda alineado
   según `--center`/`--right` o por defecto a la izquierda. Si hay
   fondo de color, el cap se aplica DENTRO del padding del fondo
   (que viene de otra regla) — el color sigue ocupando todo el ancho
   del bloque, sólo el texto adentro queda contenido. */
.portal-texto--ancho-angosto  { max-width: 560px; }
.portal-texto--ancho-medio    { max-width: 760px; }
.portal-texto--ancho-ancho    { max-width: 960px; }
.portal-texto--ancho-completo { max-width: none; }
/* Cuando hay un cap activo (angosto/medio/ancho) y el bloque NO
   tiene alineación explícita, centramos el contenedor — porque
   tirar el texto a la izquierda con un cap chico genera una columna
   incómoda flotando al margen. Si el operador eligió `right` o
   `center`, el `text-align` ya manda y los `margin: auto` del flex
   padre no intervienen. */
.portal-texto--ancho-angosto,
.portal-texto--ancho-medio,
.portal-texto--ancho-ancho { margin-inline: auto; }

.portal-texto-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 .75rem;
}
.portal-texto-cuerpo {
    color: var(--c-text);
    line-height: 1.7;
    /* Defensivo contra overflow horizontal en mobile: si el operador
       pega una URL, un email o una palabra muy larga sin espacios, el
       ancho intrínseco del texto puede empujar la caja por encima del
       ancho del contenedor (especialmente notorio en la variante con
       fondo de color, donde se ve el background sobresalir). Forzamos
       el wrap a nivel de cualquier punto del texto. `anywhere` es más
       agresivo que `break-word` y prioriza la oportunidad de wrap antes
       de calcular el min-content del bloque. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
/* Mismo criterio para el título — si el operador escribe un título
   largo de una sola palabra, no debe romper el ancho del bloque. */
.portal-texto-titulo {
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-texto-cuerpo h2,
.portal-texto-cuerpo h3 {
    color: var(--c-text-brand);
    margin-top: 1.5rem;
}
/* Links dentro del texto libre: heredan el color del contenedor en
   lugar de usar el institucional fijo. Resuelve el caso de fondos
   oscuros donde un link en `var(--c-primary)` queda invisible contra
   el background. Ampliamos el scope a todo `.portal-texto` (no solo
   al cuerpo) para que también funcione en links dentro del título o
   en cualquier estructura que CKE5 produzca anidada. La regla cubre
   `:link`, `:visited` y los estados interactivos para que el browser
   no aplique sus colores default (que por la especificidad de
   `:visited` ganarían si solo poníamos `a { color: inherit }`). El
   underline se mantiene como pista visual de que el texto es
   clickeable. */
.portal-texto a,
.portal-texto a:link,
.portal-texto a:visited,
.portal-texto a:hover,
.portal-texto a:focus,
.portal-texto a:active {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.portal-texto a:hover { text-decoration-thickness: 2px; }
.portal-texto-cuerpo img { max-width: 100%; height: auto; border-radius: 6px; }
/* Imágenes de CKEditor con resize: vienen envueltas en
   `<figure class="image image_resized" style="width:X%">` con la imagen
   adentro al 100%. Respetamos el ancho que eligió el operador y centramos
   la figura por default. Las variantes de imageStyle (block/side/inline)
   ya traen sus propias clases que CKE expone y respetamos también. */
.portal-texto-cuerpo figure.image {
    margin: 1rem auto;
    max-width: 100%;
}
.portal-texto-cuerpo figure.image img {
    width: 100%;
    height: auto;
    border-radius: 6px;
    display: block;
}
.portal-texto-cuerpo figure.image.image-style-side {
    float: right;
    margin: .5rem 0 1rem 1.5rem;
    max-width: 50%;
}
.portal-texto-cuerpo figure.image.image-style-block-align-left,
.portal-texto-cuerpo figure.image.image-style-align-left {
    margin-left: 0;
    margin-right: auto;
}
.portal-texto-cuerpo figure.image.image-style-block-align-right,
.portal-texto-cuerpo figure.image.image-style-align-right {
    margin-left: auto;
    margin-right: 0;
}
/* Presets de tamaño aplicados por los image styles custom
   `tamaño-25/50/75/100`. La clase la pone CKEditor en la figure
   cuando el operador clickea un botón de la toolbar contextual o
   arrastra los handles de resize. */
.portal-texto-cuerpo figure.image.image-style-tamaño-25,
.portal-acceso-modal-cuerpo figure.image.image-style-tamaño-25 { width: 25%; }
.portal-texto-cuerpo figure.image.image-style-tamaño-50,
.portal-acceso-modal-cuerpo figure.image.image-style-tamaño-50 { width: 50%; }
.portal-texto-cuerpo figure.image.image-style-tamaño-75,
.portal-acceso-modal-cuerpo figure.image.image-style-tamaño-75 { width: 75%; }
.portal-texto-cuerpo figure.image.image-style-tamaño-100,
.portal-acceso-modal-cuerpo figure.image.image-style-tamaño-100 { width: 100%; }

/* ── Variantes de fondo del bloque de texto libre ──────────────────
   Cuando el bloque tiene un fondo distinto a "transparente", agregamos
   padding y borde redondeado para que la caja respire, y forzamos un
   color de texto con contraste suficiente. Los modificadores se
   listan abajo en orden: sólidos primero, luego gradientes. */

.portal-texto[class*="portal-texto--fondo-"]:not(.portal-texto--fondo-transparente) {
    padding: 1.75rem 1.85rem;
    border-radius: 12px;
    margin-block: 1.5rem;
}
@media (max-width: 480px) {
    .portal-texto[class*="portal-texto--fondo-"]:not(.portal-texto--fondo-transparente) {
        padding: 1.1rem 1rem;
    }
}

/* Sin fondo: comportamiento default, ningún cambio. */

/* Sólidos oscuros: texto blanco. Reglas con `:where()` para no pelear
   con la especificidad del color base de los hijos. */
.portal-texto--fondo-primary,
.portal-texto--fondo-accent,
.portal-texto--fondo-grad-primary,
.portal-texto--fondo-grad-accent,
.portal-texto--fondo-grad-mixto {
    color: var(--c-text-light);
}
.portal-texto--fondo-primary :where(.portal-texto-titulo, .portal-texto-cuerpo, .portal-texto-cuerpo h2, .portal-texto-cuerpo h3),
.portal-texto--fondo-accent :where(.portal-texto-titulo, .portal-texto-cuerpo, .portal-texto-cuerpo h2, .portal-texto-cuerpo h3),
.portal-texto--fondo-grad-primary :where(.portal-texto-titulo, .portal-texto-cuerpo, .portal-texto-cuerpo h2, .portal-texto-cuerpo h3),
.portal-texto--fondo-grad-accent :where(.portal-texto-titulo, .portal-texto-cuerpo, .portal-texto-cuerpo h2, .portal-texto-cuerpo h3),
.portal-texto--fondo-grad-mixto :where(.portal-texto-titulo, .portal-texto-cuerpo, .portal-texto-cuerpo h2, .portal-texto-cuerpo h3) {
    color: var(--c-text-light);
}
/* Los links de fondos oscuros heredan color automáticamente por la
   regla general de `.portal-texto-cuerpo a` (más arriba). No hace
   falta override per variante. */

/* Suaves: fondo claro, texto oscuro institucional. */
.portal-texto--fondo-primary-suave,
.portal-texto--fondo-accent-suave {
    color: var(--c-text);
}

/* Backgrounds concretos. Mantenemos los gradientes en este bloque
   para tener todas las superficies de color en un solo lugar. */
.portal-texto--fondo-primary       { background: var(--c-primary); }
.portal-texto--fondo-primary-suave { background: var(--c-primary-lighter); }
.portal-texto--fondo-accent        { background: var(--c-accent); }
.portal-texto--fondo-accent-suave  {
    /* No hay var dedicada para el accent suave; lo derivamos de la
       misma proporción que --c-primary-lighter (25% accent + 75% blanco). */
    background: color-mix(in srgb, var(--c-accent) 25%, #fff);
}
.portal-texto--fondo-grad-primary {
    background: linear-gradient(135deg,
        var(--c-primary-light) 0%,
        var(--c-primary) 55%,
        var(--c-primary-dark) 100%);
}
.portal-texto--fondo-grad-accent {
    background: linear-gradient(135deg,
        var(--c-accent2, var(--c-accent)) 0%,
        var(--c-accent) 55%,
        var(--c-accent-dark) 100%);
}
.portal-texto--fondo-grad-mixto {
    background: linear-gradient(135deg,
        var(--c-primary) 0%,
        var(--c-primary-dark) 45%,
        var(--c-accent) 100%);
}


/* ═══════════════════════════════════════════════════════════════════
   Hero de noticia destacada (hero_noticia_dinamica)
   ═══════════════════════════════════════════════════════════════════
   Sirve a bloques que muestran la noticia destacada de forma grande,
   tipo home de Prensa Jujuy. El contenido viene del módulo de
   noticias (no es estático), así que el operador no carga nada acá —
   solo decide qué fuente (propias / broadcast / mixta) y qué piezas
   de info muestra. */
.portal-hero-noticia {
    margin-block: 1.5rem;
    border-radius: 14px;
    overflow: hidden;
    position: relative;
}
.portal-hero-noticia-link {
    display: block;
    color: inherit;
    text-decoration: none;
    position: relative;
}
.portal-hero-noticia-link:hover { text-decoration: none; }

.portal-hero-noticia--altura-compacta .portal-hero-noticia-img-wrap { aspect-ratio: 21 / 8; }
.portal-hero-noticia--altura-media    .portal-hero-noticia-img-wrap { aspect-ratio: 16 / 7; }
.portal-hero-noticia--altura-alta     .portal-hero-noticia-img-wrap { aspect-ratio: 16 / 9; }

.portal-hero-noticia-img-wrap {
    width: 100%;
    background: linear-gradient(135deg, var(--c-primary), var(--c-accent));
    overflow: hidden;
    position: relative;
}
.portal-hero-noticia-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .35s ease;
}
.portal-hero-noticia-link:hover .portal-hero-noticia-img {
    transform: scale(1.02);
}
.portal-hero-noticia-img-wrap--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 4rem;
    color: rgba(255, 255, 255, .55);
}
.portal-hero-noticia-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
        rgba(0, 0, 0, .8) 0%,
        rgba(0, 0, 0, .3) 55%,
        rgba(0, 0, 0, 0) 100%);
    z-index: 1;
}
.portal-hero-noticia-inner {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 2rem 2.5rem;
    color: var(--c-text-light);
    z-index: 2;
}
.portal-hero-noticia-chip {
    display: inline-block;
    background: var(--c-accent);
    color: var(--c-text-light);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .05em;
    padding: .3rem .65rem;
    border-radius: 4px;
    margin-bottom: .75rem;
    width: max-content;
}
.portal-hero-noticia-titulo {
    color: var(--c-text-light);
    font-size: clamp(1.4rem, 3vw, 2.4rem);
    font-weight: 800;
    line-height: 1.15;
    margin: 0 0 .5rem;
    /* El titulo viene de la noticia (operator-provided), puede traer
       URLs o palabras muy largas. En mobile el inner es absolute con
       padding chico — sin esto, una palabra larga desborda. */
    overflow-wrap: anywhere;
    /* Sombra leve para legibilidad sobre imágenes claras donde el
       gradient no alcanza a oscurecer lo suficiente. */
    text-shadow: 0 .15rem .55rem rgba(0, 0, 0, .35);
}
.portal-hero-noticia-bajada {
    color: var(--c-text-light);
    font-size: clamp(.95rem, 1.5vw, 1.15rem);
    line-height: 1.5;
    margin: 0 0 .75rem;
    max-width: 65ch;
    opacity: .95;
    /* Bajada operator-provided: misma proteccion que el titulo. */
    overflow-wrap: anywhere;
    text-shadow: 0 .1rem .4rem rgba(0, 0, 0, .35);
}
.portal-hero-noticia-fecha {
    color: var(--c-text-light);
    font-size: .85rem;
    opacity: .85;
}
@media (max-width: 767.98px) {
    .portal-hero-noticia--altura-compacta .portal-hero-noticia-img-wrap,
    .portal-hero-noticia--altura-media    .portal-hero-noticia-img-wrap,
    .portal-hero-noticia--altura-alta     .portal-hero-noticia-img-wrap {
        aspect-ratio: 4 / 3;
    }
    .portal-hero-noticia-inner {
        padding: 1.25rem 1.25rem;
    }
}
/* Mobile-xs (~390-430px): el padding lateral pasa de .9rem (≈14px) a
   1.25rem (≈20px). Antes el padding chocaba con el `border-radius:
   14px` del wrapper: la curva del corner inferior cortaba la primera
   letra de cada línea del título (la H2 sin margin-left propia se
   pegaba a la curva). Con 1.25rem queda un margen de seguridad de
   ~6px entre el texto y el arco. */
@media (max-width: 575.98px) {
    .portal-hero-noticia-inner {
        padding: 1rem 1.25rem;
    }
    .portal-hero-noticia-chip {
        font-size: .65rem;
        margin-bottom: .5rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Carrusel de imágenes (carrusel_imagenes)
   ═══════════════════════════════════════════════════════════════════
   Banner rotativo con N slides. Cada slide es UNA imagen completa
   (el operador la prepara fuera del page builder). El carrusel se
   encarga de la rotación, las flechas, los dots y el autoplay.

   Implementación: track horizontal con `transform: translateX`. Cada
   slide es 100% del ancho del viewport. El JS solo ajusta el offset
   del track según el índice activo. Lazy load para las imágenes
   no-iniciales para que la primera pintada sea rápida. */
.portal-carrusel {
    position: relative;
    margin-block: 1.5rem;
    border-radius: 12px;
    overflow: hidden;
    background: var(--c-bg-subtle);
}
.portal-carrusel-viewport {
    position: relative;
    overflow: hidden;
    width: 100%;
}

/* Altura del viewport: el track interno se estira para cubrir el
   contenedor, y cada slide se ajusta a la altura via aspect-ratio
   del viewport. "auto" deja que las imágenes dicten la altura. */
.portal-carrusel--altura-compacta .portal-carrusel-viewport { aspect-ratio: 21 / 8; }
.portal-carrusel--altura-media    .portal-carrusel-viewport { aspect-ratio: 16 / 7; }
.portal-carrusel--altura-alta     .portal-carrusel-viewport { aspect-ratio: 16 / 9; }
.portal-carrusel--altura-vertical .portal-carrusel-viewport { aspect-ratio: 4 / 5; }
.portal-carrusel--altura-auto     .portal-carrusel-viewport { aspect-ratio: auto; }
/* La variante vertical solo tiene sentido a ancho acotado — si está
   en ancho "completo" (full-width), una imagen 4:5 ocupa pantalla
   entera y rompe el ritmo de la página. La encajamos en un contenedor
   máximo razonable y la centramos. El operador puede combinar con
   bloques al lado usando los anchos "mitad" o "un tercio" del bloque
   para layouts más naturales. */
.portal-carrusel--altura-vertical {
    max-width: 480px;
    margin-inline: auto;
}

.portal-carrusel-track {
    display: flex;
    width: 100%;
    height: 100%;
    transition: transform .5s ease;
    will-change: transform;
}
/* ═══════════════════════════════════════════════════════════════════
   Grilla curada de reels de Instagram
   ═══════════════════════════════════════════════════════════════════ */

.portal-ig-reels {
    margin-block: 1.5rem;
    background: var(--c-bg-card);
    border-radius: 14px;
    padding: 1.5rem;
}
.portal-ig-reels-header {
    margin-bottom: 1.25rem;
}
.portal-ig-reels-titulo {
    margin: 0;
    color: var(--c-text-strong);
    font-size: 1.5rem;
    font-weight: 700;
    /* El operador define el titulo libremente — si pega una palabra
       o URL larga sin espacios, sin esto desborda el contenedor en
       mobile (~390px). */
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* Grid de N columnas — el modificador --cols-X define el grid-template.
   Aspect ratio 9:16 por cada card (los reels son verticales). En
   tablet bajamos a 2 columnas, en mobile a 1, sin importar lo elegido
   en desktop. */
.portal-ig-reels-grid {
    display: grid;
    gap: .65rem;
}
.portal-ig-reels--cols-2 .portal-ig-reels-grid { grid-template-columns: repeat(2, 1fr); }
.portal-ig-reels--cols-3 .portal-ig-reels-grid { grid-template-columns: repeat(3, 1fr); }
.portal-ig-reels--cols-4 .portal-ig-reels-grid { grid-template-columns: repeat(4, 1fr); }
/* Breakpoints alineados al estandar del proyecto: tablet md (768) y
   mobile xs (576). En tablet bajamos 3 y 4 columnas a 2; en mobile
   colapsamos todo a 1 columna porque las cards quedan demasiado chicas
   para que el thumbnail se lea. */
@media (max-width: 767.98px) {
    .portal-ig-reels--cols-3 .portal-ig-reels-grid,
    .portal-ig-reels--cols-4 .portal-ig-reels-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}
@media (max-width: 575.98px) {
    .portal-ig-reels--cols-2 .portal-ig-reels-grid,
    .portal-ig-reels--cols-3 .portal-ig-reels-grid,
    .portal-ig-reels--cols-4 .portal-ig-reels-grid {
        grid-template-columns: 1fr;
    }
}
/* Lateral: 1 columna apilada vertical, ignorando el `cols` elegido.
   Los reels mantienen su aspect-ratio 9:16 que ya viene de
   `.portal-ig-reels-card` — naturalmente se ven angostos y altos,
   perfecto para una columna lateral. */
.portal-ig-reels--lateral .portal-ig-reels-grid {
    grid-template-columns: 1fr;
}

.portal-ig-reels-card {
    position: relative;
    display: block;
    /* Aspect-ratio configurable a nivel bloque vía clase modifier
       `.portal-ig-reels--aspect-<formato>`. Default cuadrado (1:1)
       porque la mayoría de las galerías que el operador arma son
       fotos genéricas, no reels reales 9:16. Los formatos definidos
       coinciden con el mapeo del catálogo (campo `aspect_ratio`)
       para que el encuadre del modal refleje el render real. */
    aspect-ratio: var(--ig-reels-aspect, 1 / 1);
    overflow: hidden;
    border-radius: 10px;
    text-decoration: none;
    background: var(--c-bg-subtle);
    transition: transform .15s ease, box-shadow .15s ease;
    /* Reset del subrayado heredado del link cuando highlight-links del
       widget de accesibilidad está activo — la card es un objeto
       visual, no un texto enlazado, el outline va por otro lado. */
    color: inherit;
}
.portal-ig-reels--aspect-cuadrado   { --ig-reels-aspect: 1 / 1; }
.portal-ig-reels--aspect-retrato    { --ig-reels-aspect: 4 / 5; }
.portal-ig-reels--aspect-vertical   { --ig-reels-aspect: 9 / 16; }
.portal-ig-reels--aspect-horizontal { --ig-reels-aspect: 16 / 9; }
.portal-ig-reels-card:hover,
.portal-ig-reels-card:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 .65rem 1.5rem rgba(0, 0, 0, .18);
    outline: none;
    text-decoration: none;
}
.portal-ig-reels-card:focus-visible {
    box-shadow: 0 0 0 3px var(--c-primary),
                0 .65rem 1.5rem rgba(0, 0, 0, .18);
}
.portal-ig-reels-card-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .25s ease;
}
.portal-ig-reels-card:hover .portal-ig-reels-card-img {
    transform: scale(1.05);
}

/* Overlay de play — círculo translúcido centrado con el ícono adentro.
   No usa fondo opaco para que la card se siga leyendo, solo refuerza
   "esto es un video". */
.portal-ig-reels-card-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .85);
    color: rgba(0, 0, 0, .8);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    transition: transform .2s ease, background .2s ease;
}
.portal-ig-reels-card-play svg {
    width: 22px;
    height: 22px;
    /* El path del SVG arranca al borde izquierdo del viewBox; lo
       centramos visualmente con un pequeño offset para que el
       triángulo se vea ópticamente centrado adentro del círculo. */
    margin-left: 3px;
}
.portal-ig-reels-card:hover .portal-ig-reels-card-play {
    background: #fff;
    transform: translate(-50%, -50%) scale(1.08);
}

.portal-ig-reels-vacio {
    text-align: center;
    padding: 2rem 1.25rem;
    background: var(--c-bg-subtle);
    border: 1px dashed var(--c-border);
    border-radius: 10px;
    color: var(--c-muted);
}
.portal-ig-reels-vacio i {
    font-size: 2rem;
    display: block;
    margin-bottom: .35rem;
    color: var(--c-primary);
}
.portal-ig-reels-vacio p {
    margin: 0;
    font-size: .9rem;
}

.portal-ig-reels-footer {
    margin-top: 1.25rem;
    display: flex;
    justify-content: center;
}
.portal-ig-reels-seguir {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    padding: .65rem 1.25rem;
    background: var(--c-primary);
    color: var(--c-bg);
    border-radius: 8px;
    text-decoration: none;
    font-size: .95rem;
    font-weight: 600;
    transition: background .15s ease, transform .08s ease;
}
.portal-ig-reels-seguir:hover,
.portal-ig-reels-seguir:focus-visible {
    background: var(--c-primary-dark, var(--c-primary));
    color: var(--c-bg);
    transform: translateY(-1px);
    text-decoration: none;
    outline: none;
}
.portal-ig-reels-seguir i {
    font-size: 1.1rem;
}

/* En mobile el padding de 1.5rem come ~12% del viewport — lo bajamos
   a 1rem para que las cards tengan mas aire horizontal. */
@media (max-width: 575.98px) {
    .portal-ig-reels {
        padding: 1rem;
    }
    .portal-ig-reels-titulo {
        font-size: 1.25rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Embed de red social (Instagram / Twitter-X / Facebook)
   ═══════════════════════════════════════════════════════════════════ */

.portal-embed-rs {
    margin-block: 1.5rem;
    /* Centrado horizontal por default — los embeds de las redes
       sociales tienen anchos máximos fijos (Instagram 540, Twitter
       550, Facebook 500-560). Sin esto quedarían pegados a la
       izquierda en un bloque que ocupa el ancho completo. La
       alineación del wrapper sigue funcionando arriba para placement
       en grilla de 12 columnas. */
    display: flex;
    justify-content: center;
    width: 100%;
    /* Defensivo: el wrapper grid (`.portal-bloque-wrapper`) ya tiene
       `overflow: visible` por default, pero los embeds de las redes
       sociales fuerzan anchos mínimos vía style inline (Instagram
       `min-width: 326px`, Twitter ~250px). Cuando el bloque está en
       1/3 sobre pantallas comunes (col ~280px), el embed desborda y
       pisa el bloque vecino. Recortamos el overflow en la fuente y
       además forzamos a los embeds a achicarse abajo. */
    overflow: hidden;
    min-width: 0;
}
.portal-embed-rs > blockquote,
.portal-embed-rs > iframe {
    max-width: 100%;
}

/* Forzar a los embeds a respetar el ancho del wrapper, incluso cuando
   sus styles inline declaran `min-width` que excede 1/3 / 1/2 de la
   página. Sin esta override, Instagram y Twitter desbordan hacia el
   lado y pisan bloques vecinos en layouts angostos.

   Trade-off conocido: si el wrapper es muy chico (<280px) el embed
   puede verse apretado, especialmente el header con avatar + nombre
   de cuenta + botón "Seguir". A esos anchos el bloque debería ir en
   Mitad o Completo, pero respetamos la elección del operador antes
   que romper el layout. */
.portal-embed-rs--instagram blockquote.instagram-media,
.portal-embed-rs--twitter .twitter-tweet,
.portal-embed-rs--twitter iframe[id^="twitter-widget"] {
    min-width: 0 !important;
    width: 100% !important;
    margin-inline: auto !important;
}
/* Skeleton mientras carga el script. Aparece breve y lo reemplaza
   el iframe oficial al ejecutarse `instgrm.Embeds.process()` o
   `twttr.widgets.load()`. Sin esto, durante el primer paint solo se
   ve un cuadrado blanco vacío. */
.portal-embed-rs-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    padding: 2rem 1rem;
    color: var(--c-muted);
    font-size: .9rem;
    min-height: 200px;
}
.portal-embed-rs-loading i {
    font-size: 1.5rem;
}
/* Estado vacío / URL no reconocida — visible al operador en preview. */
.portal-embed-rs-vacio {
    text-align: center;
    padding: 2rem 1.25rem;
    background: var(--c-bg-subtle);
    border: 1px dashed var(--c-border);
    border-radius: 10px;
    color: var(--c-muted);
    width: 100%;
    max-width: 540px;
}
.portal-embed-rs-vacio i {
    font-size: 1.75rem;
    display: block;
    margin-bottom: .35rem;
    color: var(--c-primary);
}
.portal-embed-rs-vacio p {
    margin: .25rem 0;
    font-size: .9rem;
    color: var(--c-text);
}
.portal-embed-rs-vacio code {
    font-size: .78rem;
    background: var(--c-bg-card);
    padding: 1px 5px;
    border-radius: 3px;
    border: 1px solid var(--c-border);
    /* La URL ingresada por el operador puede ser larga (truncamos a 80
       en el template, pero 80 chars siguen desbordando un viewport de
       ~390px). Permitimos romper en cualquier carácter para que la
       tarjeta no estire la columna en mobile. */
    overflow-wrap: anywhere;
    word-break: break-word;
    display: inline-block;
    max-width: 100%;
}
/* Iframes de Facebook — el ancho lo dicta el atributo `width`, pero
   en mobile queremos que se encoja al viewport disponible. */
.portal-embed-rs-iframe {
    max-width: 100%;
    /* Sin `height: auto`, el navegador respeta el atributo `height`
       del HTML (316 video / 600 post) aún cuando el ancho se achicó
       por max-width, dejando el iframe con proporción rota. Combinado
       con aspect-ratio por variante, la altura escala con el ancho. */
    height: auto;
}
.portal-embed-rs-iframe--fb-video {
    /* 560 × 316 ≈ 16:9 — el plugin de video de FB respeta esta
       relación, así que al achicar el ancho la altura sigue. */
    aspect-ratio: 560 / 316;
}
.portal-embed-rs-iframe--fb-post {
    /* 500 × 600 es el alto por defecto del plugin de post; mantenemos
       la misma proporción al encogerse para evitar la franja vacía
       inferior que aparece al squishear solo el ancho. */
    aspect-ratio: 500 / 600;
}
.portal-embed-rs--facebook {
    /* Defensivo: si por algún motivo el iframe excede el ancho del
       wrapper (ej. operador metió un width inline mayor), preferimos
       scroll horizontal antes que cortar el contenido. */
    overflow-x: auto;
}

.portal-carrusel-slide {
    flex: 0 0 100%;
    width: 100%;
    height: 100%;
    position: relative;
    /* overflow: hidden defensivo — cuando el encuadre incluye zoom (>1),
       la imagen se escala con `transform: scale()` y bleed visualmente
       fuera del recuadro del slide. El viewport del carrusel ya clipea,
       pero adentro del slide queremos garantizar que la imagen no se
       superponga con el caption o controles. */
    overflow: hidden;
    /* Slides ocultos para AT — el atributo aria-hidden lo setea el
       template para el render inicial; el JS lo va alternando al
       cambiar de slide. Visualmente todos los slides están en el
       track; el `aria-hidden` no afecta el render. */
}
.portal-carrusel-slide-link {
    display: block;
    width: 100%;
    height: 100%;
}
.portal-carrusel-slide-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}
.portal-carrusel--altura-auto .portal-carrusel-slide-img {
    height: auto;       /* mantiene la proporción natural en modo auto */
}

/* Epígrafe del slide — strip al pie con gradiente para que el texto
   se lea sobre cualquier imagen sin tapar demasiado. Posición absoluta
   para flotar sobre la imagen; pointer-events: none en el wrapper así
   no bloquea el click sobre la imagen (link / lightbox); el texto en
   sí vuelve a auto para que sea seleccionable. */
.portal-carrusel-slide-caption {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    padding: 1.4rem 1.25rem .85rem;
    background: linear-gradient(to top,
        rgba(0, 0, 0, .72) 0%,
        rgba(0, 0, 0, .55) 55%,
        rgba(0, 0, 0, 0) 100%);
    color: #fff;
    font-size: .92rem;
    line-height: 1.4;
    text-align: center;
    z-index: 1;
    pointer-events: none;
    /* El caption recibe texto libre del operador (incluyendo URLs o
       palabras largas sin espacios). overflow-wrap: anywhere evita
       que una sola palabra desborde la franja en mobile chico. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-carrusel-slide-caption > * { pointer-events: auto; }
/* Cuando el carrusel está en altura "auto" la imagen no llena toda la
   slide, así que el strip se ancla al borde inferior del slide igual
   — no a la imagen — para no quedar suspendido en el aire. */
.portal-carrusel--altura-auto .portal-carrusel-slide-caption {
    bottom: 0;
}
@media (max-width: 575.98px) {
    .portal-carrusel-slide-caption {
        font-size: .82rem;
        padding: 1.1rem .85rem .7rem;
    }
}

/* ── Botones de flecha (prev/next) ──────────────────────────────── */
.portal-carrusel-btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 0;
    background: rgba(0, 0, 0, .35);
    color: #fff;
    font-size: 1.3rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background .15s ease, opacity .15s ease;
}
.portal-carrusel-btn:hover {
    background: rgba(0, 0, 0, .55);
}
.portal-carrusel-btn--prev { left: .75rem; }
.portal-carrusel-btn--next { right: .75rem; }

/* ── Dots de paginación ─────────────────────────────────────────── */
.portal-carrusel-dots {
    position: absolute;
    bottom: .65rem;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: .4rem;
    z-index: 2;
    background: rgba(0, 0, 0, .25);
    padding: .35rem .6rem;
    border-radius: 999px;
}
.portal-carrusel-dot {
    width: 9px;
    height: 9px;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, .55);
    padding: 0;
    cursor: pointer;
    transition: background .15s ease, transform .15s ease;
}
.portal-carrusel-dot:hover {
    background: rgba(255, 255, 255, .85);
    transform: scale(1.1);
}
.portal-carrusel-dot.is-active {
    background: #fff;
    transform: scale(1.15);
}

@media (max-width: 575.98px) {
    /* Mobile: flechas más chicas y movidas a los bordes para no
       comer el ancho del slide. Breakpoint alineado al estandar
       Bootstrap xs del proyecto. */
    .portal-carrusel-btn {
        width: 36px;
        height: 36px;
        font-size: 1.1rem;
    }
    .portal-carrusel-btn--prev { left: .25rem; }
    .portal-carrusel-btn--next { right: .25rem; }
    /* Con muchos slides (10+) los dots pueden desbordar lateralmente
       en mobile chico — flex-wrap permite que rompan en dos lineas
       antes que empujar fuera del viewport por el translateX(-50%). */
    .portal-carrusel-dots {
        flex-wrap: wrap;
        justify-content: center;
        max-width: calc(100% - 1rem);
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Cinta promocional de WhatsApp (bloque insertable en cualquier zona)
   ═══════════════════════════════════════════════════════════════════
   Banda horizontal con ícono + texto + número, todo el bloque es un
   link a WhatsApp (wa.me o link manual). El operador decide en qué
   zona ponerla — típicamente debajo del cuerpo principal o arriba
   del footer.

   Diseño minimalista: solo box-shadow leve, sin bordes marcados. El
   color de fondo lo inyecta el template inline desde la config
   (`color_fondo`, default #25d366 — verde institucional de WhatsApp).
   El texto va siempre en blanco — esos dos colores garantizan
   contraste sin pedirle al operador que elija ambos. */
.portal-cinta-whatsapp {
    display: block;
    width: 100%;
    max-width: 100%;
    color: #fff;
    text-decoration: none;
    box-shadow: 0 .25rem .8rem rgba(0, 0, 0, .15);
    border-radius: 8px;
    margin-block: 1.5rem;
    transition: filter .15s ease, transform .15s ease;
    /* Defensa última: si algún contenido tozudo se rehúsa a quebrar,
       el border-radius ya implica recorte visual — formalizarlo evita
       overflow horizontal de página en mobile. */
    overflow: hidden;
}
.portal-cinta-whatsapp:hover {
    color: #fff;
    text-decoration: none;
    filter: brightness(1.05);
}
.portal-cinta-whatsapp:active {
    transform: translateY(1px);
}
.portal-cinta-whatsapp-inner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 1rem 1.25rem;
    flex-wrap: wrap;
}
.portal-cinta-whatsapp-icono {
    font-size: 1.7rem;
    line-height: 1;
}
.portal-cinta-whatsapp-texto {
    font-size: 1.1rem;
    font-weight: 700;
    /* min-width:0 + overflow-wrap permite que el flex item se achique
       y que el texto quiebre si viene sin espacios (mobile angosto). */
    min-width: 0;
    overflow-wrap: anywhere;
}
.portal-cinta-whatsapp-sep {
    opacity: .55;
    font-weight: 300;
}
.portal-cinta-whatsapp-subtexto {
    font-size: .9rem;
    opacity: .9;
    min-width: 0;
    overflow-wrap: anywhere;
}
.portal-cinta-whatsapp-numero {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: .02em;
    /* Un número cargado sin espacios (+543884221234) es un token
       indivisible: sin esto el flex item no se achica y desborda
       el ancho del viewport en mobile. */
    min-width: 0;
    overflow-wrap: anywhere;
}
@media (max-width: 640px) {
    /* En mobile el gap se reduce y el subtexto se oculta — quedan
       ícono + título + número (si está). Los separadores siguen a
       su contenido: si el subtexto desaparece, su separador también. */
    .portal-cinta-whatsapp-inner {
        gap: .55rem;
        padding: .75rem .85rem;
    }
    .portal-cinta-whatsapp-subtexto,
    .portal-cinta-whatsapp-subtexto + .portal-cinta-whatsapp-sep,
    .portal-cinta-whatsapp-texto + .portal-cinta-whatsapp-sep {
        display: none;
    }
    .portal-cinta-whatsapp-texto { font-size: 1rem; }
    .portal-cinta-whatsapp-numero { font-size: 1rem; }
}
@media (max-width: 575.98px) {
    /* Mobile angosto (~390-430px): recortar más el padding lateral
       libera ancho para texto + número cuando el operador carga el
       número como un solo token sin espacios. */
    .portal-cinta-whatsapp-inner {
        padding: .65rem .6rem;
        gap: .4rem;
    }
    .portal-cinta-whatsapp-icono { font-size: 1.4rem; }
    .portal-cinta-whatsapp-numero { font-size: .95rem; letter-spacing: 0; }
}


/* ═══════════════════════════════════════════════════════════════════
   Grilla de videos (videos_multiples)
   ═══════════════════════════════════════════════════════════════════
   Patrón "lazy embed": cada card arranca como un <button> con thumb
   estático del provider (YouTube hqdefault) + un ícono play encima.
   Al primer click el JS lo reemplaza por un <iframe> con autoplay.
   Esto evita cargar N iframes de YouTube en el render inicial —
   cada iframe pesa ~500KB de JS solo para sentarse a esperar el
   play. */
.portal-videos {
    margin-block: 1.5rem;
}
.portal-videos-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
}
.portal-videos-grid {
    display: grid;
    gap: 1rem;
}
.portal-videos--cols-1 .portal-videos-grid { grid-template-columns: 1fr; max-width: 720px; }
.portal-videos--cols-2 .portal-videos-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.portal-videos--cols-3 .portal-videos-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.portal-videos--cols-4 .portal-videos-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* Breakpoints alineados al estándar del proyecto:
   992px (Bootstrap lg) y 575.98px (Bootstrap xs). Antes usaba
   1024/480, que no coincidían con el resto de bloques. */
@media (max-width: 992px) {
    .portal-videos--cols-4 .portal-videos-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (max-width: 768px) {
    .portal-videos--cols-3 .portal-videos-grid,
    .portal-videos--cols-4 .portal-videos-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 575.98px) {
    .portal-videos-grid {
        grid-template-columns: 1fr !important;
    }
}
/* En zona lateral forzamos 1 columna apilada vertical sin importar
   el `columnas` que haya elegido el operador. Va DESPUÉS de las
   reglas --cols-N para ganar por orden de cascada (misma
   especificidad). El max-width se anula porque el contenedor lateral
   ya restringe el ancho. */
.portal-videos--lateral .portal-videos-grid {
    grid-template-columns: 1fr;
    max-width: none;
}

.portal-video-card {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.portal-video-card-thumb {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    padding: 0;
    margin: 0;
    cursor: pointer;
    border-radius: 10px;
    overflow: hidden;
    background: var(--c-bg-subtle);
    transition: transform .18s ease, box-shadow .18s ease;
}
.portal-video-card-thumb:hover {
    transform: translateY(-2px);
    box-shadow: 0 .55rem 1.2rem rgba(0, 0, 0, .15);
}
.portal-video-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-video-card-thumb-placeholder {
    display: flex;
    width: 100%;
    height: 100%;
    align-items: center;
    justify-content: center;
    font-size: 2.5rem;
    color: var(--c-muted);
}
.portal-video-card-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 3.2rem;
    color: var(--c-text-light);
    text-shadow: 0 .2rem .6rem rgba(0, 0, 0, .45);
    transition: transform .18s ease;
    pointer-events: none;
}
.portal-video-card-thumb:hover .portal-video-card-play {
    transform: translate(-50%, -50%) scale(1.08);
}
.portal-video-card-iframe {
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: 10px;
    display: block;
}
.portal-video-card-titulo {
    font-size: .92rem;
    font-weight: 600;
    color: var(--c-text-strong);
    margin: 0;
    line-height: 1.3;
    /* El título lo carga el operador a mano: si pega una URL o un
       texto sin espacios, sin esto rompe la grilla en mobile. */
    overflow-wrap: anywhere;
}


/* ═══════════════════════════════════════════════════════════════════
   Video embebido
   ═══════════════════════════════════════════════════════════════════ */

.portal-video { margin-block: 1rem; }
.portal-video-titulo {
    color: var(--c-text-brand);
    font-size: 1.4rem;
    font-weight: 700;
    margin: 0 0 .75rem;
    /* El título lo escribe el operador: si pega una URL larga o
       una palabra sin espacios, evitamos overflow horizontal en
       mobile cortando la palabra. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-video-frame {
    position: relative;
    aspect-ratio: 16 / 9;
    background: #000;
    border-radius: 8px;
    overflow: hidden;
}
.portal-video-frame iframe,
.portal-video-frame .portal-video-html5 {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    background: #000;
}
/* El <video> HTML5 además respeta su aspect-ratio sin recortar. Si el
   video subido no es 16:9, el frame agrega letterbox negro alrededor
   (que combina con el fondo del wrap). */
.portal-video-frame .portal-video-html5 {
    object-fit: contain;
}
/* Imagen de fallback (sólo se ve si el navegador no puede renderizar
   el <video>): que ocupe el frame completo y se adapte sin desbordar. */
.portal-video-frame .portal-video-fallback {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    max-width: 100%;
    object-fit: contain;
}


/* ═══════════════════════════════════════════════════════════════════
   Formulario de carga (mod_formularios)
   ═══════════════════════════════════════════════════════════════════ */

.portal-formulario {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 10px;
    padding: 1.5rem 1.75rem;
}
.portal-formulario-titulo {
    color: var(--c-text-brand);
    font-size: 1.4rem;
    font-weight: 700;
    margin: 0 0 .5rem;
    /* El título lo escribe el operador: si pega un texto largo sin
       espacios (URL, identificador) debe cortar antes de desbordar
       el card en mobile. */
    overflow-wrap: anywhere;
}
.portal-formulario-descripcion {
    color: var(--c-text-soft);
    margin: 0 0 1rem;
    overflow-wrap: anywhere;
}
.portal-formulario-pendiente {
    background: var(--c-warning-bg);
    border: 1px dashed var(--c-warning);
    color: var(--c-warning-text);
    padding: .85rem 1rem;
    border-radius: 6px;
    font-size: .9rem;
    margin: 0;
}

/* Banner de errores (volver al form con problemas de validación) */
.portal-formulario-errores,
.portal-contacto-errores {
    background: var(--c-danger-light);
    border-left: 4px solid var(--c-danger);
    color: var(--c-danger-text);
    padding: .85rem 1rem;
    border-radius: 6px;
    margin: 0 0 1rem;
    font-size: .92rem;
}
.portal-formulario-errores ul,
.portal-contacto-errores ul {
    margin: .35rem 0 0 1.25rem;
    padding: 0;
}
.portal-formulario-errores li,
.portal-contacto-errores li {
    margin-bottom: .15rem;
    /* Los mensajes de validación pueden incluir nombres de archivo
       sin espacios; permitir corte para que no rompan el ancho. */
    overflow-wrap: anywhere;
}

/* Página de "gracias" del formulario de contacto, tras envío exitoso. */
.portal-contacto-gracias {
    padding: 4rem 0;
}
.portal-contacto-gracias-box {
    max-width: 520px;
    margin: 0 auto;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 14px;
    padding: 2.5rem 2rem;
    text-align: center;
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .05);
}
.portal-contacto-gracias-icono {
    font-size: 3.5rem;
    color: var(--c-success, var(--c-primary));
    display: block;
    margin-bottom: .75rem;
}
.portal-contacto-gracias-titulo {
    color: var(--c-text-brand);
    font-size: 1.75rem;
    font-weight: 700;
    margin: 0 0 1rem;
}
.portal-contacto-gracias-mensaje {
    color: var(--c-text);
    font-size: 1rem;
    line-height: 1.6;
    margin: 0 0 1.75rem;
}
.portal-contacto-gracias-link {
    display: inline-flex;
    align-items: center;
    color: var(--c-text-brand);
    font-weight: 600;
    text-decoration: none;
}
.portal-contacto-gracias-link:hover {
    color: var(--c-primary-dark);
    text-decoration: underline;
}

/* Form propiamente dicho */
.portal-formulario-form {
    display: grid;
    gap: 1rem;
    margin-top: .5rem;
}
.portal-formulario-campo {
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.portal-formulario-label {
    font-weight: 600;
    color: var(--c-text);
    font-size: .92rem;
}
.portal-formulario-req {
    color: var(--c-danger);
    margin-left: .15rem;
}
.portal-formulario-control {
    width: 100%;
    padding: .6rem .8rem;
    /* Mismo criterio que `.portal-contacto-campo input`: borde con
       suficiente contraste contra el fondo de la card, derivado del
       color de texto fuerte del tenant para mantener identidad por
       theme sin hardcodear gris. */
    border: 1.5px solid color-mix(in srgb, var(--c-text-strong) 25%, var(--c-bg-card));
    border-radius: 6px;
    background: var(--c-bg-card);
    color: var(--c-text);
    font-size: 1rem;
    font-family: inherit;
    transition: border-color .15s ease, box-shadow .15s ease;
}
.portal-formulario-control:hover {
    border-color: color-mix(in srgb, var(--c-text-strong) 40%, var(--c-bg-card));
}
.portal-formulario-control:focus {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 3px rgba(var(--c-primary-rgb), .15);
}
.portal-formulario-file {
    padding: .4rem .55rem;
    background: var(--c-bg-subtle);
    cursor: pointer;
    /* El control nativo de archivo puede ensanchar el campo en mobile
       según el largo del nombre del archivo elegido. */
    max-width: 100%;
}
.portal-formulario-ayuda {
    color: var(--c-muted);
    font-size: .8rem;
    overflow-wrap: anywhere;
}
.portal-formulario-submit {
    /* Fondo configurable: el bloque del page builder elige modo "solido"
       o "gradiente" y le pasa los colores como custom-properties inline
       (--boton-color o --boton-grad-inicio / --boton-grad-fin). Si no
       hay clase modificadora, cae al gradiente institucional default. */
    background: var(--c-gradient);
    color: var(--c-text-light) !important;
    border: 0;
    border-radius: 6px;
    padding: .65rem 1.6rem;
    font-weight: 600;
    cursor: pointer;
    align-self: flex-start;
    transition: filter .15s ease, transform .05s ease;
}
.portal-formulario-submit--solido {
    background: var(--boton-color, var(--c-primary));
}
.portal-formulario-submit--gradiente {
    background: linear-gradient(135deg,
        var(--boton-grad-inicio, var(--c-primary)) 0%,
        var(--boton-grad-fin, var(--c-accent)) 100%);
}
.portal-formulario-submit:hover { filter: brightness(1.1); }
.portal-formulario-submit:active { transform: translateY(1px); }


/* Página de gracias post-envío */
.portal-formulario-gracias-shell {
    padding: 4rem 1rem;
}
.portal-formulario-gracias {
    max-width: 560px;
    margin: 0 auto;
    text-align: center;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 10px;
    padding: 2.5rem 2rem;
}
.portal-formulario-gracias-icono {
    color: var(--c-success);
    font-size: 3rem;
    margin-bottom: .75rem;
    line-height: 1;
}
.portal-formulario-gracias-titulo {
    color: var(--c-text-brand);
    margin: 0 0 .5rem;
    font-size: 1.7rem;
    font-weight: 700;
}
.portal-formulario-gracias-mensaje {
    color: var(--c-text-soft);
    margin: 0 0 1.5rem;
    line-height: 1.55;
}
.portal-formulario-gracias-cta {
    display: inline-block;
    background: var(--c-gradient);
    color: var(--c-text-light) !important;
    padding: .65rem 1.4rem;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
    transition: filter .15s ease;
}
.portal-formulario-gracias-cta:hover {
    filter: brightness(1.1);
    text-decoration: none;
}

/* ───── ≤ 575.98px: ajustes mobile del formulario de carga ──────── */
@media (max-width: 575.98px) {
    /* El padding lateral de 1.75rem se come ~28px de cada lado en un
       viewport de 360-390px; lo bajamos para dejar más aire al input. */
    .portal-formulario {
        padding: 1.15rem 1rem;
    }
    .portal-formulario-titulo {
        font-size: 1.2rem;
    }
    /* El submit ocupa el ancho completo para ser más fácil de tocar
       y para evitar que un texto de botón largo lo desborde. */
    .portal-formulario-submit {
        align-self: stretch;
        width: 100%;
        text-align: center;
    }
    /* Card de gracias: misma lógica de padding ajustado. */
    .portal-formulario-gracias {
        padding: 2rem 1rem;
    }
    .portal-formulario-gracias-titulo {
        font-size: 1.4rem;
    }
    .portal-formulario-gracias-shell {
        padding: 2.5rem .75rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Formulario de contacto (mod_bandeja_contacto)
   ═══════════════════════════════════════════════════════════════════ */

.portal-contacto {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 10px;
    padding: 1.5rem 1.75rem;
}
.portal-contacto-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 1rem;
}
.portal-contacto-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}
.portal-contacto-campo--full { grid-column: 1 / -1; }
.portal-contacto-campo {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    /* Sin `min-width: 0` los grid items respetan el ancho intrínseco
       de sus hijos (input/textarea con size/cols del UA) y bloquean
       el shrinking, causando overflow horizontal en mobile. */
    min-width: 0;
}
.portal-contacto-campo label {
    font-size: .82rem;
    font-weight: 600;
    color: var(--c-text-strong);
    word-break: break-word;
}
.portal-contacto-campo input,
.portal-contacto-campo textarea {
    /* Borde visible: `--c-border` por defecto suele ser un gris muy
       claro (#dee2e6) que contra fondo blanco prácticamente no se ve.
       Para inputs queremos contraste para que el operador y el
       visitante reconozcan que es un control editable. Derivamos un
       gris medio a partir del color de texto fuerte del tenant
       (~25% mezclado contra el fondo de card) — escala con el theme
       sin necesidad de hardcodear. */
    border: 1.5px solid color-mix(in srgb, var(--c-text-strong) 25%, var(--c-bg-card));
    border-radius: 6px;
    padding: .6rem .8rem;
    font: inherit;
    color: var(--c-text);
    background: var(--c-bg-card);
    transition: border-color .15s ease, box-shadow .15s ease;
    /* Forzamos ancho 100% y box-sizing border-box: sin esto, el
       `size=20` del user-agent en <input> y `cols=20` en <textarea>
       generan un ancho intrínseco fijo en caracteres que ignora la
       celda del grid y desborda en mobile. */
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}
.portal-contacto-campo input:hover,
.portal-contacto-campo textarea:hover {
    border-color: color-mix(in srgb, var(--c-text-strong) 40%, var(--c-bg-card));
}
.portal-contacto-campo input:focus,
.portal-contacto-campo textarea:focus {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--c-primary) 18%, transparent);
}
/* ── Botón de envío ─────────────────────────────────────────────────
   Dos modos: sólido (un color de la paleta vía `--boton-color`) o
   gradiente (entre `--boton-grad-inicio` y `--boton-grad-fin`). Ambas
   variantes usan variables CSS inyectadas inline desde el template
   para que el color venga de la paleta del tenant sin un selector
   distinto por opción. */
.portal-contacto-btn {
    margin-top: 1rem;
    color: var(--c-text-light);
    border: 0;
    padding: .75rem 1.5rem;
    border-radius: 6px;
    font-weight: 600;
    cursor: pointer;
    transition: filter .15s ease, transform .05s ease;
}
.portal-contacto-btn--solido {
    background: var(--boton-color, var(--c-primary));
}
.portal-contacto-btn--gradiente {
    background: linear-gradient(
        90deg,
        var(--boton-grad-inicio, var(--c-primary)),
        var(--boton-grad-fin,    var(--c-accent))
    );
}
.portal-contacto-btn:hover  { filter: brightness(1.08); }
.portal-contacto-btn:active { transform: translateY(1px); }

/* ── Layout con mapa ────────────────────────────────────────────────
   Sin mapa: el form ocupa todo el ancho (default).
   Con mapa: layout en 2 columnas (form + mapa) según `mapa_posicion`,
   o stack vertical si la posición es `abajo`. En mobile colapsamos
   siempre a una columna. */
.portal-contacto-layout {
    display: grid;
    gap: 1.25rem;
}
.portal-contacto--con-mapa.portal-contacto--mapa-izquierda .portal-contacto-layout,
.portal-contacto--con-mapa.portal-contacto--mapa-derecha   .portal-contacto-layout {
    grid-template-columns: 1fr 1fr;
    align-items: start;
}
.portal-contacto--con-mapa.portal-contacto--mapa-izquierda .portal-contacto-form { order: 2; }
.portal-contacto--con-mapa.portal-contacto--mapa-izquierda .portal-contacto-mapa { order: 1; }
.portal-contacto--con-mapa.portal-contacto--mapa-derecha   .portal-contacto-form { order: 1; }
.portal-contacto--con-mapa.portal-contacto--mapa-derecha   .portal-contacto-mapa { order: 2; }
/* Abajo: stack natural (form arriba, mapa debajo), una sola columna. */

.portal-contacto-mapa {
    width: 100%;
    /* min-width:0 evita que en el layout 2-columnas (mapa al lado del
       form) el wrapper se quede pegado al ancho intrínseco del iframe
       y empuje a desbordar el grid en viewports angostos. */
    min-width: 0;
    border-radius: 8px;
    overflow: hidden;
    background: var(--c-bg-subtle);
}
.portal-contacto-mapa iframe {
    display: block;
    width: 100%;
    /* max-width: 100% defensivo: algunos navegadores móviles tratan
       el iframe como reemplazado con dimensiones intrínsecas si el
       embed las sugiere; con esto garantizamos que nunca desborde. */
    max-width: 100%;
    border: 0;
}
.portal-contacto-mapa--bajo  iframe { height: 220px; }
.portal-contacto-mapa--medio iframe { height: 380px; }
.portal-contacto-mapa--alto  iframe { height: 560px; }

@media (max-width: 768px) {
    .portal-contacto-grid { grid-template-columns: 1fr; }
    .portal-contacto--con-mapa.portal-contacto--mapa-izquierda .portal-contacto-layout,
    .portal-contacto--con-mapa.portal-contacto--mapa-derecha   .portal-contacto-layout {
        grid-template-columns: 1fr;
    }
    /* En mobile, el mapa siempre va abajo del formulario aunque la
       config diga "izquierda" / "derecha": mejor lectura en pantalla
       angosta. */
    .portal-contacto--con-mapa .portal-contacto-form { order: 1; }
    .portal-contacto--con-mapa .portal-contacto-mapa { order: 2; }
}
/* En viewports tipo phone (≤575.98px) reducimos el padding horizontal
   del wrapper para no comer ancho útil de los inputs antes del
   breakpoint extra-small (480px). */
@media (max-width: 575.98px) {
    .portal-contacto { padding: 1.1rem 1rem; }
}
@media (max-width: 480px) {
    .portal-contacto { padding: 1rem .9rem; }
    .portal-contacto-titulo { font-size: 1.2rem; }
    .portal-contacto-mapa--bajo  iframe { height: 180px; }
    .portal-contacto-mapa--medio iframe { height: 220px; }
    .portal-contacto-mapa--alto  iframe { height: 320px; }
}


/* ═══════════════════════════════════════════════════════════════════
   Fallback genérico (bloques sin template propio)
   ═══════════════════════════════════════════════════════════════════ */

.portal-bloque-pendiente {
    background: var(--c-bg-subtle);
    border: 1px dashed var(--c-border);
    color: var(--c-muted);
    padding: 1rem 1.25rem;
    border-radius: 8px;
    font-size: .9rem;
    margin-block: .75rem;
    display: flex;
    align-items: center;
    gap: .65rem;
}
.portal-bloque-pendiente i {
    color: var(--c-warning);
    font-size: 1.2rem;
}
.portal-bloque-pendiente strong { color: var(--c-text-strong); }


/* ═══════════════════════════════════════════════════════════════════
   Página pública /noticias/ — listado y detalle
   ═══════════════════════════════════════════════════════════════════
   Estilos del portal de noticias del tenant. La página es una vista
   hardcoded (no se compone con el page builder), por eso vive como
   sección propia con sus reglas. El listado es una grilla de hasta 4
   columnas con cards de imagen + título + bajada; el detalle muestra
   la imagen destacada en grande, título + bajada, cuerpo CKE5 y un
   link de "volver al listado".
   ═══════════════════════════════════════════════════════════════════ */

/* ── Listado ─────────────────────────────────────────────────────── */
.portal-noticias-pagina {
    padding: 2.5rem 0 4rem;
}
.portal-noticias-pagina-header {
    margin-bottom: 2rem;
}

/* Layout principal + lateral derecho.
   - Sin lateral (default): el main ocupa el 100%, comportamiento
     original.
   - Con lateral (`--con-lateral`, lo activa la vista cuando el
     tenant configuró `feed_rss_externo`): grid 2 columnas, main +
     aside fija a la derecha. En tablet/mobile el aside cae debajo
     del main para no comerse el ancho útil del contenido principal. */
.portal-noticias-pagina-layout {
    display: block;
}
.portal-noticias-pagina--con-lateral .portal-noticias-pagina-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 2rem;
    align-items: start;
}
.portal-noticias-pagina-main {
    min-width: 0;  /* evita que un grid hijo desborde el track */
}
.portal-noticias-pagina-aside {
    position: sticky;
    top: 1rem;
    /* Limitamos el alto al viewport menos el espacio del header para
       que el sidebar entre en pantalla; si el feed trae muchos items
       el contenido scrollea dentro del aside, no la página. */
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
}

/* Con lateral, el grid principal de noticias baja a 3 columnas en
   desktop (sin lateral son 4) porque pierde ~320px de ancho útil. */
.portal-noticias-pagina--con-lateral .portal-noticias-pagina-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1100px) {
    /* Tablet landscape: el sidebar pasa a stack debajo (full width
       del aside, ya no fijo a 320px). El grid principal vuelve a sus
       breakpoints normales heredados. */
    .portal-noticias-pagina--con-lateral .portal-noticias-pagina-layout {
        grid-template-columns: 1fr;
    }
    .portal-noticias-pagina-aside {
        position: static;
        max-height: none;
        overflow: visible;
    }
}

/* Variante "sidebar" del bloque RSS: tipografía más chica para
   adaptarse al ancho reducido del aside (≈ 280-320px). El resto del
   styling lo hereda de `.portal-rss` base. */
.portal-rss--sidebar .portal-rss-titulo {
    font-size: 1.15rem;
    margin-bottom: .75rem;
}
.portal-rss--sidebar .portal-rss-item {
    padding: .55rem .7rem .6rem;
}
.portal-rss--sidebar .portal-rss-item-titulo {
    font-size: .92rem;
}
.portal-rss--sidebar .portal-rss-item-descripcion {
    font-size: .85rem;
}
.portal-noticias-pagina-titulo {
    color: var(--c-text-brand);
    font-size: clamp(1.85rem, 3vw, 2.4rem);
    font-weight: 700;
    margin: 0 0 .35rem;
    letter-spacing: -.5px;
}
.portal-noticias-pagina-subtitulo {
    color: var(--c-muted);
    font-size: 1rem;
    margin: 0 0 1.5rem;
}

/* Buscador — centrado en el header, píldora con input + botón submit
   con sólo la lupa al final (antes había una lupa decorativa a la
   izquierda + un botón "Buscar" con texto, eran dos "Buscar" en la
   misma píldora). */
.portal-noticias-buscador {
    display: flex;
    align-items: center;
    gap: .35rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 999px;
    padding: .25rem .35rem .25rem 1.25rem;
    max-width: 560px;
    margin-inline: auto;
    transition: border-color .15s ease, box-shadow .15s ease;
}
.portal-noticias-buscador:focus-within {
    border-color: var(--c-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 15%, transparent);
}
.portal-noticias-buscador-input {
    flex: 1 1 auto;
    border: 0;
    background: transparent;
    outline: none;
    font-size: .95rem;
    padding: .6rem 0;
    color: var(--c-text-strong);
    min-width: 0;
}
.portal-noticias-buscador-clear {
    color: var(--c-muted);
    text-decoration: none;
    width: 30px; height: 30px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    transition: background .15s ease, color .15s ease;
}
.portal-noticias-buscador-clear:hover {
    background: var(--c-bg-subtle);
    color: var(--c-text-strong);
}
.portal-noticias-buscador-btn {
    border: 0;
    background: var(--c-primary);
    color: var(--c-text-light);
    border-radius: 50%;
    width: 38px;
    height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex: 0 0 auto;
    transition: background .15s ease, transform .15s ease;
}
.portal-noticias-buscador-btn:hover {
    background: var(--c-primary-dark);
    transform: scale(1.05);
}
.portal-noticias-buscador-btn i {
    font-size: 1.05rem;
    line-height: 1;
}

/* El bloque del título también lo centramos visualmente para que el
   buscador centrado no quede descolgado contra el título alineado a
   la izquierda. */
.portal-noticias-pagina-header {
    text-align: center;
}
.portal-noticias-pagina-resultado {
    text-align: center;
}

.portal-noticias-pagina-resultado {
    margin: 1rem 0 0;
    color: var(--c-muted);
    font-size: .9rem;
}
.portal-noticias-pagina-resultado strong { color: var(--c-text-strong); }

/* Grid de cards (hasta 4 columnas, responsive) */
.portal-noticias-pagina-grid {
    display: grid;
    gap: 1.4rem;
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1100px) {
    .portal-noticias-pagina-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 840px) {
    .portal-noticias-pagina-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 540px) {
    .portal-noticias-pagina-grid { grid-template-columns: 1fr; }
}

/* Card de noticia: bordes redondeados + box-shadow, hover con elevación */
.portal-noticia-card {
    background: var(--c-bg-card);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .07),
                0 .15rem .35rem rgba(0, 0, 0, .04);
    transition: transform .2s ease, box-shadow .2s ease;
    border: 1px solid color-mix(in srgb, var(--c-border) 55%, transparent);
}
.portal-noticia-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 .85rem 2.1rem rgba(0, 0, 0, .1),
                0 .25rem .55rem rgba(0, 0, 0, .06);
}
.portal-noticia-card-link {
    display: flex;
    flex-direction: column;
    height: 100%;
    text-decoration: none;
    color: inherit;
}
.portal-noticia-card-link:hover { text-decoration: none; }
/* Thumb del card: la imagen ocupa todo el contenedor (cover).
   Preferimos zoom suave sobre el letterbox blanco — si el aspect-ratio
   de la imagen difiere mucho del 16/10 del thumb se recorta, pero
   nunca quedan bordes vacíos al costado. En el detalle, la imagen
   recupera su tamaño natural. */
.portal-noticia-card-thumb {
    width: 100%;
    aspect-ratio: 16 / 10;
    background-color: var(--c-bg-subtle);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.portal-noticia-card-thumb--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: color-mix(in srgb, var(--c-primary) 35%, transparent);
    font-size: 2.5rem;
    background: color-mix(in srgb, var(--c-primary) 6%, var(--c-bg-subtle));
}
.portal-noticia-card-body {
    padding: 1rem 1.15rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: .4rem;
    flex: 1 1 auto;
}
.portal-noticia-card-fecha {
    color: var(--c-muted);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .4px;
}
.portal-noticia-card-titulo {
    color: var(--c-text-brand);
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1.3;
    margin: 0;
}
.portal-noticia-card-bajada {
    color: var(--c-text);
    font-size: .9rem;
    line-height: 1.45;
    margin: 0;
}

/* ── Noticia destacada (página 1 sin búsqueda) ──────────────────
   Pieza grande con imagen-banner a la izquierda y un body con chip
   de categoría + título + bajada + fecha a la derecha. En mobile
   colapsa a una sola columna (imagen arriba, texto abajo). */
.portal-noticia-destacada {
    margin: 0 0 2rem;
    border-radius: 12px;
    overflow: hidden;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    box-shadow: 0 .25rem .85rem rgba(0, 0, 0, .05);
    transition: transform .18s ease, box-shadow .18s ease;
}
.portal-noticia-destacada:hover {
    transform: translateY(-2px);
    box-shadow: 0 .55rem 1.25rem rgba(0, 0, 0, .08);
}
.portal-noticia-destacada-link {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: 0;
    color: inherit;
    text-decoration: none;
}
@media (max-width: 768px) {
    .portal-noticia-destacada-link { grid-template-columns: 1fr; }
}
.portal-noticia-destacada-img-wrap {
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--c-bg-subtle);
}
.portal-noticia-destacada-img-wrap--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-primary-lighter);
    font-size: 3rem;
}
.portal-noticia-destacada-img {
    width: 100%;
    height: 100%;
    /* Cover: la imagen llena el slot 16/9 sin dejar bordes blancos.
       Si el aspect-ratio difiere, se recorta — el operador puede
       elegir una "imagen de portada" diseñada para este encuadre. */
    object-fit: cover;
    display: block;
    transition: transform .35s ease;
}
.portal-noticia-destacada:hover .portal-noticia-destacada-img {
    transform: scale(1.03);
}
.portal-noticia-destacada-body {
    padding: 1.5rem 1.75rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .65rem;
}
.portal-noticia-destacada-titulo {
    color: var(--c-text-strong);
    font-size: clamp(1.35rem, 2.4vw, 1.8rem);
    font-weight: 800;
    line-height: 1.2;
    margin: 0;
}
.portal-noticia-destacada-bajada {
    color: var(--c-text);
    font-size: 1rem;
    line-height: 1.45;
    margin: 0;
}
.portal-noticia-destacada-fecha {
    color: var(--c-muted);
    font-size: .8rem;
    font-weight: 500;
    margin-top: .25rem;
}

/* ── Chip de categoría ──────────────────────────────────────────
   Pieza institucional pequeña, uppercase, fondo primary. Se usa en
   las cards de la grilla y en la destacada. */
.portal-noticia-chip {
    display: inline-block;
    align-self: flex-start;
    padding: .25rem .55rem;
    background: var(--c-primary);
    color: var(--c-text-light);
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .04em;
    border-radius: 3px;
    line-height: 1.3;
    text-transform: uppercase;
}
.portal-noticia-chip--destacada {
    font-size: .75rem;
    padding: .3rem .7rem;
}

/* Chip dentro del card body: alinear al inicio sin romper el grid. */
.portal-noticia-card-body .portal-noticia-chip {
    margin-bottom: .35rem;
}

/* ── Paginación ─────────────────────────────────────────────────
   Tres piezas: prev, info (página X de N), next. En mobile se apila. */
.portal-noticias-pag {
    margin-top: 2rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    flex-wrap: wrap;
}
.portal-noticias-pag-btn {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .5rem 1rem;
    border: 1px solid var(--c-border);
    border-radius: 6px;
    color: var(--c-text-strong);
    background: var(--c-bg-card);
    text-decoration: none;
    font-weight: 600;
    font-size: .9rem;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.portal-noticias-pag-btn:hover {
    background: var(--c-primary);
    border-color: var(--c-primary);
    color: var(--c-text-light);
}
.portal-noticias-pag-btn.is-disabled {
    color: var(--c-muted);
    background: var(--c-bg-subtle);
    pointer-events: none;
    border-style: dashed;
}
.portal-noticias-pag-info {
    color: var(--c-text);
    font-size: .9rem;
}

/* Estado vacío del listado */
.portal-noticias-pagina-vacia {
    text-align: center;
    padding: 3.5rem 1rem;
    color: var(--c-muted);
}
.portal-noticias-pagina-vacia i {
    font-size: 3rem;
    color: color-mix(in srgb, var(--c-muted) 60%, transparent);
    display: block;
    margin-bottom: .75rem;
}
.portal-noticias-pagina-vacia p {
    margin: 0 0 1rem;
    font-size: 1rem;
}
.portal-noticias-pagina-vacia-link {
    color: var(--c-text-brand);
    text-decoration: underline;
    font-weight: 600;
}

/* ── Detalle ─────────────────────────────────────────────────────── */
.portal-noticia-detalle {
    padding-bottom: 4rem;
}
/* Hero antiguo eliminado: el detalle no usa hero ahora. La imagen
   destacada se renderiza dentro del contenedor a tamaño normativo
   vía la galería (carrusel). El layout es: chips → título → bajada
   → fecha → galería → cuerpo. */
.portal-noticia-detalle-contenedor {
    max-width: 820px;
    padding-top: 2.25rem;
    /* Espacio a la izquierda para las share bubbles flotantes en
       desktop. En mobile las bubbles se reposicionan al pie del
       header (no necesitan margen). */
    position: relative;
}
.portal-noticia-detalle-volver {
    display: inline-flex;
    align-items: center;
    color: var(--c-text-brand);
    font-weight: 600;
    font-size: .9rem;
    text-decoration: none;
    margin-bottom: 1.25rem;
    transition: color .15s ease;
}
.portal-noticia-detalle-volver:hover {
    color: var(--c-primary-dark);
    text-decoration: underline;
}
.portal-noticia-detalle-fecha {
    display: inline-flex;
    align-items: center;
    color: var(--c-muted);
    font-size: .85rem;
    margin: 0 0 .75rem;
    text-transform: uppercase;
    letter-spacing: .4px;
}
.portal-noticia-detalle-titulo {
    color: var(--c-text-brand);
    font-size: clamp(1.85rem, 3.5vw, 2.6rem);
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.5px;
    margin: 0 0 1rem;
}
.portal-noticia-detalle-bajada {
    color: var(--c-text-strong);
    font-size: 1.15rem;
    font-weight: 500;
    line-height: 1.55;
    margin: 0 0 1.75rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--c-border);
}
.portal-noticia-detalle-cuerpo {
    color: var(--c-text);
    font-size: 1.02rem;
    line-height: 1.75;
}
.portal-noticia-detalle-cuerpo p { margin: 0 0 1.1rem; }
.portal-noticia-detalle-cuerpo h2,
.portal-noticia-detalle-cuerpo h3 {
    color: var(--c-text-brand);
    margin: 1.75rem 0 .85rem;
    font-weight: 700;
}
.portal-noticia-detalle-cuerpo h2 { font-size: 1.5rem; }
.portal-noticia-detalle-cuerpo h3 { font-size: 1.2rem; }
/* Links del cuerpo de noticia: heredan color del contenedor para
   funcionar tanto en fondos claros como oscuros del portal. Sobreescribo
   todos los pseudo-estados de `a` para vencer el `:visited` default
   del browser (mismo criterio que en `.portal-texto-cuerpo`). */
.portal-noticia-detalle-cuerpo a,
.portal-noticia-detalle-cuerpo a:link,
.portal-noticia-detalle-cuerpo a:visited,
.portal-noticia-detalle-cuerpo a:hover,
.portal-noticia-detalle-cuerpo a:focus,
.portal-noticia-detalle-cuerpo a:active {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}
.portal-noticia-detalle-cuerpo a:hover { text-decoration-thickness: 2px; }
.portal-noticia-detalle-cuerpo img {
    max-width: 100%;
    height: auto;
    border-radius: 8px;
    margin: 1rem 0;
}
.portal-noticia-detalle-cuerpo blockquote {
    border-left: 4px solid var(--c-primary);
    padding: .35rem 1rem;
    color: var(--c-muted);
    margin: 1.25rem 0;
    font-style: italic;
}
.portal-noticia-detalle-cuerpo ul,
.portal-noticia-detalle-cuerpo ol {
    margin: 0 0 1.1rem 1.25rem;
}
.portal-noticia-detalle-pie {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--c-border);
}

/* ── Header del detalle: chips + título + bajada + fecha ─────────── */
.portal-noticia-detalle-header {
    margin-bottom: 1.75rem;
}
.portal-noticia-detalle-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: .85rem;
    align-items: center;
}
.portal-noticia-detalle-chip {
    display: inline-block;
    padding: .25rem .55rem;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 1.3;
    border-radius: 3px;
    text-transform: uppercase;
    text-decoration: none;
}
/* La categoría va con fondo primary y texto en --c-bg (contraste claro).
   Selectores extra-específicos (incluyendo `a.`) para ganarle al
   `a { color: var(--c-text-brand); }` global del portal que estaba
   pintando el texto del mismo color que el fondo. */
a.portal-noticia-detalle-chip--categoria,
.portal-noticia-detalle-chips .portal-noticia-detalle-chip--categoria {
    background: var(--c-primary);
    color: var(--c-text-light) !important;
    text-decoration: none;
}
a.portal-noticia-detalle-chip--categoria:hover,
a.portal-noticia-detalle-chip--categoria:focus-visible,
.portal-noticia-detalle-chips .portal-noticia-detalle-chip--categoria:hover {
    background: var(--c-primary-dark);
    color: var(--c-text-light);
    text-decoration: none;
}
.portal-noticia-detalle-chip--tema,
a.portal-noticia-detalle-chip--tema {
    background: transparent;
    color: var(--c-text-brand);
    border: 1px solid color-mix(in srgb, var(--c-primary) 30%, transparent);
    text-decoration: none;
}
a.portal-noticia-detalle-chip--tema:hover,
a.portal-noticia-detalle-chip--tema:focus-visible {
    background: color-mix(in srgb, var(--c-primary) 10%, transparent);
    color: var(--c-text-brand);
    text-decoration: none;
}
/* Separadores tipo "|" entre chips emulando el estilo Jujuy. La línea
   se dibuja como un pseudo-elemento, no como un caracter — así el
   espacio horizontal se mantiene parejo si el operador agrega temas. */
.portal-noticia-detalle-chips > .portal-noticia-detalle-chip + .portal-noticia-detalle-chip {
    position: relative;
    margin-left: .5rem;
}
.portal-noticia-detalle-chips > .portal-noticia-detalle-chip + .portal-noticia-detalle-chip::before {
    content: "|";
    position: absolute;
    left: -.65rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--c-border);
    font-weight: 400;
}

/* La fecha pasa al pie del header (debajo de la bajada) en lugar de
   arriba — replicando el orden del diseño Jujuy. */
.portal-noticia-detalle-header .portal-noticia-detalle-fecha {
    display: block;
    margin: 1rem 0 0;
    padding-top: .85rem;
    border-top: 1px solid var(--c-border);
}

/* ── Share bubbles flotantes ──────────────────────────────────────
   Columna vertical fija a la izquierda del contenedor en desktop;
   se reposiciona como una barra horizontal arriba del cuerpo en
   mobile (cuando no hay espacio lateral). */
.portal-noticia-detalle-compartir {
    position: absolute;
    top: 8.5rem;   /* alinea con la altura del título grande */
    left: -3.75rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    z-index: 2;
}
.portal-noticia-compartir-btn {
    /* Tamaño fijo. `flex: 0 0 auto` + dimensiones explícitas evitan
       que el contenedor padre (aside flex) lo estire. */
    flex: 0 0 38px;
    width: 38px;
    height: 38px;
    box-sizing: border-box;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--c-primary);
    color: var(--c-text-light) !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    line-height: 1;
    text-decoration: none;
    box-shadow: 0 .15rem .35rem rgba(0, 0, 0, .08);
    transition: transform .15s ease, filter .15s ease, background-color .15s ease;
}
.portal-noticia-compartir-btn:hover,
.portal-noticia-compartir-btn:focus-visible {
    transform: translateY(-2px);
    filter: brightness(1.1);
    color: var(--c-text-light);
    text-decoration: none;
    outline: none;
}
/* Asegurar que el ícono ocupe su tamaño esperado, no se expanda. */
.portal-noticia-compartir-btn > i {
    line-height: 1;
}
/* Si querés brand colors por red social en vez del primary uniforme,
   estos modificadores los aplican. Por default todas usan primary
   para mantener la armonía institucional. */
.portal-noticia-compartir-btn--wa:hover { background: #25D366; }
.portal-noticia-compartir-btn--mail:hover { background: var(--c-accent); }

@media (max-width: 1100px) {
    /* Ya no entra a la izquierda del contenedor: pasa arriba en
       horizontal, debajo del título/bajada. */
    .portal-noticia-detalle-compartir {
        position: static;
        flex-direction: row;
        margin: 0 0 1.5rem;
    }
}

/* ── Galería del detalle (carrusel) ────────────────────────────── */
.portal-noticia-detalle-galeria {
    position: relative;
    margin: 0 0 1.75rem;
    background: var(--c-bg-subtle);
    border-radius: 10px;
    overflow: hidden;
}
.portal-noticia-detalle-galeria-track {
    position: relative;
    min-height: 200px;
}
.portal-noticia-detalle-galeria-slide {
    display: none;
}
.portal-noticia-detalle-galeria-slide.is-activa {
    display: block;
}
.portal-noticia-detalle-galeria-img {
    /* Imagen en su tamaño "normativo": ocupa el ancho del contenedor
       y mantiene su aspect-ratio natural. No se recorta. */
    display: block;
    width: 100%;
    height: auto;
    max-height: 70vh;
    object-fit: contain;
    background: var(--c-bg-subtle);
}
.portal-noticia-detalle-galeria-epigrafe {
    padding: .65rem 1rem .85rem;
    /* `--c-text-disabled` para diferenciar el epígrafe del cuerpo de
       la nota (más tenue, italic) sin desaparecerlo. */
    background-color: var(--c-border) !important;
    color: var(--c-text-disabled) !important;
    font-size: .8rem;
    font-style: italic;
    text-align: center;
    margin: 0;
}

.portal-noticia-detalle-galeria-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 0;
    background: rgba(0, 0, 0, .55);
    color: var(--c-text-light);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    transition: background .15s ease;
}
.portal-noticia-detalle-galeria-nav:hover {
    background: rgba(0, 0, 0, .8);
}
.portal-noticia-detalle-galeria-nav--prev { left: .65rem; }
.portal-noticia-detalle-galeria-nav--next { right: .65rem; }

.portal-noticia-detalle-galeria-dots {
    position: absolute;
    bottom: 3.5rem;   /* arriba del epígrafe, no encima */
    left: 0;
    right: 0;
    display: flex;
    gap: .35rem;
    justify-content: center;
}
.portal-noticia-detalle-galeria-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    border: 0;
    background: rgba(255, 255, 255, .55);
    cursor: pointer;
    padding: 0;
    transition: background .15s ease, transform .15s ease;
}
.portal-noticia-detalle-galeria-dot.is-activa {
    background: var(--c-bg);
    transform: scale(1.25);
}

/* ── Tags al pie ─────────────────────────────────────────────────── */
.portal-noticia-detalle-pie-tags {
    margin: 2rem 0 0;
    padding-top: 1.25rem;
    border-top: 1px solid var(--c-border);
    color: var(--c-muted);
    font-size: .9rem;
}
.portal-noticia-detalle-pie-tag,
a.portal-noticia-detalle-pie-tag {
    display: inline-block;
    padding: .2rem .5rem;
    margin: 0 .25rem;
    background: var(--c-bg-subtle);
    color: var(--c-text);
    border-radius: 3px;
    font-size: .8rem;
    text-decoration: none;
    transition: background .15s ease, color .15s ease;
}
a.portal-noticia-detalle-pie-tag:hover {
    background: var(--c-primary);
    color: var(--c-text-light);
    text-decoration: none;
}

/* ── Las más leídas (widget al pie del detalle) ──────────────────
   Lista numerada vertical estilo "ranking", basada en el contador de
   visitas anónimas. Cada item: un círculo con el número (CSS counter)
   + chip de categoría + título. Sólo aparece si hay al menos una
   noticia con vistas > 0 (la view ya filtra). */
.portal-noticia-detalle-mas-leidas {
    margin: 2.5rem 0 0;
    padding-top: 1.5rem;
    border-top: 1px solid var(--c-border);
}
.portal-noticia-detalle-mas-leidas-titulo {
    color: var(--c-text-brand);
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0 0 1rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}
.portal-noticia-detalle-mas-leidas-lista {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: maslidas;
}
.portal-noticia-detalle-mas-leidas-item {
    counter-increment: maslidas;
    display: flex;
    gap: .85rem;
    align-items: flex-start;
    padding: .85rem 0;
    border-bottom: 1px solid var(--c-border);
}
.portal-noticia-detalle-mas-leidas-item:last-child {
    border-bottom: 0;
}
.portal-noticia-detalle-mas-leidas-num {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--c-primary);
    color: var(--c-text-light);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: .9rem;
    line-height: 1;
}
.portal-noticia-detalle-mas-leidas-num::before {
    content: counter(maslidas);
}
.portal-noticia-detalle-mas-leidas-link {
    flex: 1 1 auto;
    text-decoration: none;
    color: var(--c-text-strong);
    display: flex;
    flex-direction: column;
    gap: .2rem;
    line-height: 1.35;
}
.portal-noticia-detalle-mas-leidas-link:hover .portal-noticia-detalle-mas-leidas-tit {
    color: var(--c-text-brand);
}
.portal-noticia-detalle-mas-leidas-cat {
    color: var(--c-muted);
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
}
.portal-noticia-detalle-mas-leidas-tit {
    font-size: 1rem;
    font-weight: 600;
    color: var(--c-text-strong);
    transition: color .15s ease;
}

/* ── Notas relacionadas ──────────────────────────────────────────
   Sección a ancho completo (sale del contenedor estrecho del detalle)
   para parecerse al cierre de Jujuy. Reusa el card del listado. */
.portal-noticia-detalle-relacionadas {
    margin-top: 3.5rem;
    padding: 2.5rem 0 0.5rem;
    background: var(--c-bg-subtle);
    border-top: 1px solid var(--c-border);
}
.portal-noticia-detalle-relacionadas-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 1.25rem;
}
.portal-noticia-detalle-relacionadas-grid {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 900px) {
    .portal-noticia-detalle-relacionadas-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 600px) {
    .portal-noticia-detalle-relacionadas-grid {
        grid-template-columns: 1fr;
    }
}

/* ════════════════════════════════════════════════════════════════════
   RESPONSIVE — overrides consolidados por bloque
   ════════════════════════════════════════════════════════════════════
   Tres breakpoints estándar usados acá:

     ≤ 1024px  →  tablet landscape: grilla 4-col baja a 3, hero conserva
                  proporciones, paddings empiezan a achicarse.
     ≤ 768px   →  tablet portrait: las grillas se aplanan a 2 columnas,
                  el sidebar/lateral pasa a fluir abajo del contenido,
                  fuentes algo más chicas.
     ≤ 480px   →  mobile: todas las grillas a 1 columna, paddings
                  mínimos, fuentes ajustadas para legibilidad sin
                  scroll horizontal.

   Estos overrides van DESPUÉS de las reglas base de cada bloque para
   ganarles por orden. La especificidad se mantiene igual o más baja
   adrede — el objetivo es que sean reglas "de quitar" (pasan grilla
   a 1fr, reducen padding, etc.), no de pelear contra el operador. */

/* ───── ≤ 1024px (tablet landscape) ──────────────────────────────── */
@media (max-width: 1024px) {
    /* Cards destacadas: 4 col → 3 col máximo. El `minmax(200px, 1fr)`
       ya lo achicaba pero forzamos el grid-template para que las cards
       no queden muy chicas al apretar la ventana. */
    .portal-cards--cols-4 .portal-cards-grid {
        grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    }

    /* Accesos directos: 4 → 3 columnas. */
    .portal-accesos--cols-4 .portal-accesos-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    /* Noticias grid: idem. */
    .portal-noticias--cols-4 .portal-noticias-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    /* Slider hero: el titular y subtítulo bajan un escalón de tipografía
       para no rebalsar el contenedor. */
    .portal-slider-hero-titulo  { font-size: clamp(1.6rem, 4vw, 2.5rem); }
    .portal-slider-hero-subtitulo { font-size: clamp(.95rem, 2vw, 1.15rem); }

    /* Bloque autoridades — variante jerárquica: el titular reduce su
       proporción para que la grilla de subordinados a la derecha siga
       siendo legible. */
    .portal-autoridades--jerarquico-titular .portal-autoridades-jer {
        grid-template-columns: 1.1fr 1fr;
    }
}

/* ───── ≤ 768px (tablet portrait) ────────────────────────────────── */
@media (max-width: 768px) {
    /* Banner promocional: padding más chico, banner-inner se centra. */
    .portal-banner-inner {
        padding: 1.5rem 1rem;
    }
    .portal-banner-texto {
        font-size: 1.15rem;
        line-height: 1.4;
    }
    .portal-banner--altura-baja  { min-height: 80px; }
    .portal-banner--altura-media { min-height: 140px; }
    .portal-banner--altura-alta  { min-height: 220px; }

    /* Texto libre con fondo: el padding interno se reduce para no
       gastar tanto ancho útil en mobile. */
    .portal-texto[class*="portal-texto--fondo-"]:not(.portal-texto--fondo-transparente) {
        padding: 1.25rem 1.1rem;
    }
    .portal-texto-titulo { font-size: 1.25rem; }

    /* Cards destacadas: 3/4 col → 2 col. */
    .portal-cards--cols-3 .portal-cards-grid,
    .portal-cards--cols-4 .portal-cards-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* Noticias grid: 3/4 col → 2 col. */
    .portal-noticias--cols-3 .portal-noticias-grid,
    .portal-noticias--cols-4 .portal-noticias-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* Accesos directos: 3/4 col → 2 col. */
    .portal-accesos--cols-3 .portal-accesos-grid,
    .portal-accesos--cols-4 .portal-accesos-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    /* En las píldoras el flex-row se mantiene pero achicamos el padding. */
    .portal-accesos--pildora-icono-izq .portal-acceso {
        padding: .55rem .9rem .55rem .55rem;
    }
    .portal-accesos--pildora-icono-izq .portal-acceso-icono {
        width: 38px; height: 38px; font-size: 1.1rem;
    }

    /* Bloque autoridades — variante jerárquica colapsa a stack:
       titular full-width arriba, grilla de hijos abajo. */
    .portal-autoridades--jerarquico-titular .portal-autoridades-jer {
        grid-template-columns: 1fr;
    }
    .portal-autoridades--jerarquico-titular .portal-autoridad-titular {
        flex-direction: column;
        text-align: center;
        align-items: center;
    }
    .portal-autoridades--jerarquico-titular .portal-autoridad-titular-foto {
        width: 140px; height: 140px;
    }
    /* Mosaico circular y cuadradas: si tenían 4-5 por fila bajan a 2. */
    .portal-autoridades--mosaico-circular .portal-autoridades-grid,
    .portal-autoridades--cuadradas-uniforme .portal-autoridades-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* Slider hero: en mobile el contenido pasa a stack y el botón
       CTA ocupa el ancho completo (más fácil de tocar en touch). */
    .portal-slider-hero-content {
        padding: 1.5rem 1.25rem;
    }
    .portal-slider-hero-cta {
        width: 100%;
        justify-content: center;
    }

    /* Fix de overflow horizontal: el template slider_hero.html renderiza el
       CTA con la clase .portal-hero-cta (definida en estilosPortal.css con
       flex-shrink: 0 y padding 1.5rem). En viewports angostos el ancho
       intrínseco del botón supera el ancho del .portal-container y se
       recorta por la derecha. Acá lo dejamos achicarse, cap al 100% del
       padre y permitimos wrap de labels largos. */
    .portal-hero-cta {
        max-width: 100%;
        flex-shrink: 1;
        padding: .65rem 1.1rem;
        overflow-wrap: anywhere;
        word-break: break-word;
    }

    /* Listado de documentos: en mobile reducimos padding lateral del
       card y achicamos el gap interno para que entre cómodo en la zona
       lateral / viewport ~390px. Mantenemos la fila horizontal (ícono
       a la izq, info al medio, descarga a la der): el título corta con
       ellipsis y la meta hace word-break si es necesario. */
    .portal-documento {
        gap: .6rem;
        padding: .55rem .65rem;
    }
    .portal-documento-icono {
        font-size: 1.25rem;
    }

    /* Footer del portal — el grid de columnas se reordena en 2 columnas
       para no apilar todo en una sola tira larga. */
    .portal-footer-inner {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1.5rem;
    }

    /* Formularios de carga: el grid 2 col → 1 col. */
    .portal-formulario-form,
    .portal-formulario .row {
        grid-template-columns: 1fr;
    }

    /* Card overlay flotante (cards destacadas): el bloque flotante
       reduce padding interno y el aspect-ratio se afloja para que la
       imagen no quede demasiado baja en celulares. */
    .portal-cards--imagen-overlay-flotante .portal-card-overlay-bloque {
        left: .65rem;
        right: .65rem;
        padding: .55rem .75rem;
    }
    .portal-cards--imagen-overlay-flotante .portal-card-overlay-titulo {
        font-size: .9rem;
    }
}

/* ───── ≤ 480px (mobile portrait) ────────────────────────────────── */
@media (max-width: 480px) {
    /* Regla general: las grillas de cualquier bloque pasan a 1 columna.
       Cubre cards destacadas, noticias, accesos directos en todas sus
       variantes, autoridades, etc. */
    .portal-cards-grid,
    .portal-noticias-grid,
    .portal-accesos-grid,
    .portal-autoridades-grid,
    .portal-autoridades-jer-grilla {
        grid-template-columns: 1fr !important;
    }

    /* Reducción de tipografía para que los títulos no rompan la línea
       de forma rara. */
    .portal-cards-titulo,
    .portal-noticias-titulo,
    .portal-accesos-titulo,
    .portal-autoridades-titulo {
        font-size: 1.35rem;
    }

    /* Banner promocional: padding muy ajustado y texto un punto más
       chico, para que entre en pantallas de 360px. */
    .portal-banner-inner { padding: 1rem .85rem; }
    .portal-banner-texto { font-size: 1rem; line-height: 1.35; }
    .portal-banner--altura-baja  { min-height: 64px; }
    .portal-banner--altura-media { min-height: 110px; }
    .portal-banner--altura-alta  { min-height: 180px; }

    /* Texto libre: padding mínimo en variantes con fondo. */
    .portal-texto[class*="portal-texto--fondo-"]:not(.portal-texto--fondo-transparente) {
        padding: 1rem .85rem;
        border-radius: 8px;
    }
    .portal-texto-titulo { font-size: 1.1rem; }
    .portal-texto-cuerpo { font-size: .95rem; }

    /* Slider hero: titulares más chicos para mobile. */
    .portal-slider-hero-titulo    { font-size: clamp(1.4rem, 6vw, 1.9rem); }
    .portal-slider-hero-subtitulo { font-size: clamp(.9rem, 3vw, 1.05rem); }
    .portal-slider-hero-content   { padding: 1.1rem 1rem; }

    /* Hero real (clases que sí existen en el DOM): en pantallas chicas el
       CTA pasa a ocupar todo el ancho útil del bloque para evitar el
       recorte por la derecha. Padding más compacto, label centrado. */
    .portal-hero-cta {
        display: block;
        width: 100%;
        max-width: 100%;
        padding: .7rem 1rem;
        text-align: center;
    }
    /* El subtítulo respeta saltos del operador (white-space: pre-wrap en
       estilosPortal.css); reforzamos wrap de URLs/palabras largas para
       que no empujen el ancho del bloque. */
    .portal-hero-titulo,
    .portal-hero-subtitulo {
        overflow-wrap: anywhere;
        word-break: break-word;
    }

    /* Accesos directos — variante "círculos centrados": el círculo
       sigue pero más chico. */
    .portal-accesos--circulos-centrados .portal-acceso-icono {
        width: 64px; height: 64px; font-size: 1.7rem;
    }

    /* Accesos directos — variante "píldora ícono izq": cuando hay
       muchas píldoras, se apilan en una sola fila por píldora. */
    .portal-accesos--pildora-icono-izq .portal-acceso {
        padding: .5rem .8rem .5rem .5rem;
    }
    .portal-accesos--pildora-icono-izq .portal-acceso-titulo {
        font-size: .9rem;
    }

    /* Bloque autoridades: foto del titular más chica para no comerse
       toda la pantalla. */
    .portal-autoridades--jerarquico-titular .portal-autoridad-titular-foto {
        width: 110px; height: 110px;
    }
    .portal-autoridades--jerarquico-titular .portal-autoridad-titular-nombre {
        font-size: 1.1rem;
    }
    .portal-autoridad-foto {
        width: 90px; height: 90px;
    }

    /* Formulario de contacto: padding del wrapper se reduce. */
    .portal-contacto {
        padding: 1rem 1rem;
        border-radius: 8px;
    }
    .portal-contacto-titulo { font-size: 1.2rem; }
    .portal-contacto-grid { grid-template-columns: 1fr; gap: .75rem; }

    /* Video embebido: bordes redondeados más sutiles en pantallas
       chicas y padding mínimo del wrapper. */
    .portal-video-frame {
        border-radius: 6px;
    }
    .portal-video-titulo { font-size: 1.15rem; }

    /* Footer: 1 columna en mobile. */
    .portal-footer-inner {
        grid-template-columns: 1fr;
        gap: 1.25rem;
        text-align: center;
    }
    .portal-footer-redes {
        justify-content: center;
    }
    .portal-footer-logos {
        justify-content: center;
        flex-wrap: wrap;
    }

    /* Listado de documentos: tipografía un punto menos para mobile. */
    .portal-documentos-titulo  { font-size: .98rem; }
    .portal-documentos-meta    { font-size: .8rem; }

    /* Card overlay flotante: aspect ratio más cuadrado en mobile para
       que el bloque flotante respire. */
    .portal-cards--imagen-overlay-flotante {
        --card-overlay-aspect: 4 / 3;
    }
}

/* ════════════════════════════════════════════════════════════════════
   ACORDEÓN — bloque colapsable para FAQs y secciones informativas
   ════════════════════════════════════════════════════════════════════ */

.portal-acordeon {
    margin: 1.5rem 0;
}
.portal-acordeon-titulo {
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--c-text);
    margin: 0 0 1rem;
    line-height: 1.2;
    /* Texto libre del operador: si carga un título sin espacios
       (p.ej. una URL), permitimos cortar la palabra para que no
       desborde el bloque en mobile. */
    overflow-wrap: anywhere;
}
.portal-acordeon-items {
    display: flex;
    flex-direction: column;
    gap: .6rem;
}

/* Cada item es un card con borde sutil y border-radius suave. El
   header tiene color de fondo y el body se colapsa con max-height.
   La sombra al hover sugiere que es interactivo. */
.portal-acordeon-item {
    border: 1px solid var(--c-border, rgba(0, 0, 0, 0.1));
    border-radius: 6px;
    overflow: hidden;
    background: var(--c-bg-card, #fff);
    transition: box-shadow .18s ease;
}
.portal-acordeon-item:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}

/* El header es un <button> para accesibilidad nativa (tab + enter +
   espacio). Quitamos los estilos de botón del browser y aplicamos
   los nuestros. Background y color vienen inline desde el template
   (paleta o hex personalizado). */
.portal-acordeon-header {
    width: 100%;
    border: 0;
    padding: .95rem 1.1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    cursor: pointer;
    text-align: left;
    font-size: 1.02rem;
    font-weight: 600;
    line-height: 1.4;
    font-family: inherit;
    transition: filter .18s ease;
}
.portal-acordeon-header:hover {
    /* Sutil oscurecimiento del color de fondo en hover. Funciona tanto
       sobre colores claros (los oscurece) como oscuros (apenas se nota)
       — no necesita reglas separadas por variante de color. */
    filter: brightness(0.95);
}
.portal-acordeon-header:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: -2px;
}
.portal-acordeon-header-titulo {
    flex: 1;
    /* min-width:0 permite que el flex item se encoja por debajo de su
       contenido cuando hay palabras largas; sin esto, una URL en el
       título corre al icono fuera del viewport en mobile chico. */
    min-width: 0;
    overflow-wrap: anywhere;
}
.portal-acordeon-header-icono {
    display: inline-flex;
    align-items: center;
    transition: transform .25s ease;
    font-size: 1.1rem;
    flex-shrink: 0;
}
.portal-acordeon-item.is-abierto .portal-acordeon-header-icono {
    transform: rotate(180deg);
}

/* El body se colapsa con max-height 0 y transición. `overflow: hidden`
   permite que el contenido interno no asome durante la animación.
   El max-height real (scrollHeight) lo asigna el JS al abrir. */
.portal-acordeon-body {
    max-height: 0;
    overflow: hidden;
    transition: max-height .3s ease;
    background: var(--c-bg-card, #fff);
}
.portal-acordeon-body-inner {
    padding: 1.1rem 1.2rem 1.3rem;
    color: var(--c-text);
    line-height: 1.65;
}
/* El contenido viene de CKEditor con sus propios márgenes — los
   normalizamos para que el primero/último no generen aire excesivo. */
.portal-acordeon-body-inner > *:first-child { margin-top: 0; }
.portal-acordeon-body-inner > *:last-child  { margin-bottom: 0; }
.portal-acordeon-body-inner p {
    margin: 0 0 .8rem;
}
.portal-acordeon-body-inner ul,
.portal-acordeon-body-inner ol {
    margin: 0 0 .8rem 1.2rem;
}
/* El contenido viene de CKEditor: el operador puede insertar
   imágenes, videos o iframes con dimensiones propias. Forzamos que
   nunca excedan el ancho del body para que no desborden en mobile. */
.portal-acordeon-body-inner img,
.portal-acordeon-body-inner video,
.portal-acordeon-body-inner iframe,
.portal-acordeon-body-inner svg {
    max-width: 100%;
    height: auto;
}
/* Las tablas no se pueden encoger sin reflowear; en su lugar dejamos
   que scrolleen horizontalmente dentro del body. overflow-wrap en las
   celdas evita que palabras largas inflen el ancho de la tabla. */
.portal-acordeon-body-inner table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}
.portal-acordeon-body-inner td,
.portal-acordeon-body-inner th {
    overflow-wrap: anywhere;
}

@media (max-width: 575.98px) {
    .portal-acordeon-titulo { font-size: 1.3rem; }
    .portal-acordeon-header {
        padding: .8rem .9rem;
        font-size: .95rem;
    }
    .portal-acordeon-body-inner {
        padding: .9rem 1rem 1rem;
    }
}

/* ════════════════════════════════════════════════════════════════════
   MATERIAL DESCARGABLE — grilla visual de piezas descargables
   ════════════════════════════════════════════════════════════════════ */

.portal-material {
    margin: 2rem 0;
}
.portal-material-header {
    text-align: center;
    margin-bottom: 1.5rem;
}
.portal-material-titulo {
    font-size: 2rem;
    font-weight: 700;
    color: var(--c-text-brand);
    margin: 0 0 .5rem;
    line-height: 1.15;
}
.portal-material-descripcion {
    color: var(--c-text-soft, #666);
    font-size: 1rem;
    margin: 0;
}

/* Toolbar de filtros (pill-buttons + dropdown). Layout flex que en
   mobile colapsa a columna. Replica el look de INPROJUY: pílulas
   horizontales para tipo y select a la derecha para categoría. */
.portal-material-toolbar {
    background: var(--c-bg, #f7f8fa);
    border-radius: 12px;
    padding: 1rem 1.4rem;
    margin-bottom: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}
.portal-material-tipos {
    display: flex;
    gap: .5rem;
    flex-wrap: wrap;
}
.portal-material-tipo {
    background: transparent;
    color: var(--c-text);
    border: 1px solid transparent;
    padding: .55rem 1.2rem;
    border-radius: 24px;
    font-size: .95rem;
    font-weight: 600;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.portal-material-tipo:hover {
    background: var(--c-bg-card, #fff);
    border-color: var(--c-border);
}
.portal-material-tipo.is-active {
    background: var(--c-primary);
    color: var(--c-text-light);
}
.portal-material-tipo.is-active .portal-material-tipo-icono {
    /* En estado activo el chip de color del tipo se atenúa para no
       competir con el botón blanco-sobre-color. */
    opacity: .75;
}
.portal-material-tipo-icono {
    display: inline-block;
    width: 10px;
    height: 14px;
    border-radius: 2px;
}
.portal-material-categoria {
    background: var(--c-bg-card, #fff);
    border: 1px solid var(--c-border);
    border-radius: 6px;
    padding: .55rem .9rem;
    font-size: .95rem;
    color: var(--c-text);
    cursor: pointer;
    min-width: 220px;
}

/* Grilla principal. Las columnas las define el operador en el catálogo
   (2/3/4); en mobile colapsa a 1 sola columna por CSS. */
.portal-material-grid {
    display: grid;
    gap: 1.4rem;
    /* Cuando el bloque queda dentro de un contenedor flex/grid (zonas
       del page builder), `min-width: 0` permite que la grilla shrinkee
       en vez de empujar el viewport hacia la derecha. */
    min-width: 0;
}
.portal-material-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
.portal-material-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
.portal-material-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }

/* Card de cada pieza. Layout vertical: preview arriba, body al medio,
   botón "Descargar" al pie. Shadow sutil + hover lift para que se
   sienta interactiva. */
.portal-material-card {
    background: var(--c-bg-card, #fff);
    border-radius: 10px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    transition: transform .18s ease, box-shadow .18s ease;
    /* Las cards viven en un grid (1fr en mobile). Sin `min-width: 0`,
       cuando el contenido interno (PDF preview o títulos largos sin
       espacios) tiene un ancho intrínseco, la card pelea contra el
       1fr y termina más ancha que su columna → la grilla se desborda
       a la derecha y el viewport mobile se rompe. */
    min-width: 0;
}
.portal-material-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
}
.portal-material-card-preview {
    aspect-ratio: 1 / 1;
    background: var(--c-bg, #f1f3f5);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.portal-material-card-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Para PDFs (data-tipo="pdf") usamos `contain` en lugar de `cover` para
   que se vea la página entera del documento, no un recorte cuadrado de
   un PDF que típicamente es landscape. El bg del preview hace de
   letterbox de los bordes que quedan vacíos. Sin esto, en mobile el
   recorte 1:1 muestra solo el costado izquierdo de la primera página y
   el operador percibe que "el bloque se rompe" cuando en realidad la
   card sí está adentro del viewport — lo que está mal es el crop. */
.portal-material-card[data-tipo="pdf"] .portal-material-card-preview img {
    object-fit: contain;
    object-position: center top;
    padding: .25rem;
}
.portal-material-card-placeholder {
    font-size: 4rem;
    color: var(--c-text-soft, #adb5bd);
    opacity: .6;
}
.portal-material-card-body {
    padding: 1rem 1.1rem .8rem;
    flex: 1;
    display: flex;
    flex-direction: column;
}
.portal-material-card-titulo {
    font-size: 1rem;
    font-weight: 700;
    color: var(--c-text);
    margin: 0 0 .4rem;
    line-height: 1.3;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.portal-material-card-descripcion {
    font-size: .88rem;
    color: var(--c-text-soft, #666);
    margin: 0 0 .8rem;
    line-height: 1.4;
    flex: 1;
    /* Igual que el título: cortar URLs o palabras larguísimas pegadas
       para que el min-content de la card no exceda su columna en mobile. */
    word-break: break-word;
    overflow-wrap: anywhere;
}
.portal-material-card-badges {
    display: flex;
    gap: .4rem;
    align-items: center;
}
.portal-material-badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 6px;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .3px;
    text-transform: uppercase;
    /* Slugs de categoría sin espacios (ej. "Informes-Anuales-2024") podrían
       hacer que el badge sea más ancho que la card en mobile. */
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-material-badge--pdf    { background: #fee2e2; color: #b91c1c; }
.portal-material-badge--imagen { background: #dbeafe; color: #1e40af; }
.portal-material-badge--otro   { background: #e5e7eb; color: #374151; }

/* Botón "Descargar" al pie del card. Pleno ancho con color institucional. */
.portal-material-card-descargar {
    background: var(--c-primary);
    color: var(--c-text-light);
    text-decoration: none;
    padding: .8rem 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    font-weight: 600;
    font-size: .95rem;
    transition: filter .15s ease;
}
.portal-material-card-descargar:hover {
    filter: brightness(1.1);
    color: var(--c-text-light);
}
.portal-material-card-descargar:focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: -3px;
}

/* Estado vacío cuando los filtros no matchean nada. */
.portal-material-vacio {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--c-text-soft, #666);
    font-size: 1rem;
    background: var(--c-bg, #f7f8fa);
    border-radius: 8px;
    margin-top: 1rem;
}

@media (max-width: 900px) {
    .portal-material-grid--cols-4 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 640px) {
    .portal-material-titulo { font-size: 1.5rem; }
    .portal-material-grid--cols-2,
    .portal-material-grid--cols-3,
    .portal-material-grid--cols-4 {
        grid-template-columns: repeat(2, 1fr);
        gap: 1rem;
    }
    .portal-material-toolbar {
        flex-direction: column;
        align-items: stretch;
    }
    .portal-material-categoria { min-width: 0; width: 100%; }
}
@media (max-width: 480px) {
    .portal-material-grid--cols-2,
    .portal-material-grid--cols-3,
    .portal-material-grid--cols-4 {
        grid-template-columns: 1fr;
    }
    .portal-material-titulo { font-size: 1.25rem; }
    .portal-material-toolbar { padding: .75rem .85rem; }
    .portal-material-tipo { padding: .45rem .85rem; font-size: .9rem; }
    .portal-material-card-body { padding: .85rem .85rem .7rem; }
    .portal-material-card-badges { flex-wrap: wrap; }
}

/* Breakpoint estándar del proyecto (576px). Cierra el gap entre los
   breakpoints internos del bloque (480/640) y el que usa el resto del
   portal: en 390-430px de la preview mobile del page builder, refuerza
   defensivamente que el toolbar y la pill de categoría no impongan un
   ancho mínimo que empuje el viewport. */
@media (max-width: 575.98px) {
    .portal-material-toolbar {
        min-width: 0;
        padding: .75rem .85rem;
    }
    .portal-material-categoria {
        min-width: 0;
        width: 100%;
    }
    .portal-material-card-badges { flex-wrap: wrap; }
}


/* ═══════════════════════════════════════════════════════════════════
   Visor de documentos embebido — PDF inline o Office Online viewer
   ═══════════════════════════════════════════════════════════════════ */
.portal-visor-documento {
    margin: 1.5rem 0;
    /* min-width:0 evita que el iframe (con su ancho intrínseco) fuerce
       a la sección a desbordar cuando el bloque vive dentro de un flex
       o grid (zonas del page builder, .portal-bloque-wrap). */
    min-width: 0;
    max-width: 100%;
}
.portal-visor-documento-header {
    margin-bottom: .85rem;
}
.portal-visor-documento-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 .25rem;
}
.portal-visor-documento-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .92rem;
}
.portal-visor-documento-frame {
    background: var(--c-bg-subtle);
    border: 1px solid var(--c-border-light);
    border-radius: 8px;
    overflow: hidden;
    /* width:100% + max-width:100% blindan al contenedor para que el iframe
       no pueda empujarlo más allá del slot del page builder. */
    width: 100%;
    max-width: 100%;
}
.portal-visor-documento-frame--400  { height: 400px; }
.portal-visor-documento-frame--600  { height: 600px; }
.portal-visor-documento-frame--800  { height: 800px; }
.portal-visor-documento-frame--1000 { height: 1000px; }
.portal-visor-documento-iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
    /* Refuerzo defensivo: algunos visores (PDF.js, Office Online) setean
       atributos width/height en el iframe que pueden ganarle al CSS
       cuando el bloque está en una columna estrecha. */
    max-width: 100%;
}
.portal-visor-documento-fallback {
    background: var(--c-bg-subtle);
    border: 1px dashed var(--c-border-light);
    border-radius: 8px;
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--c-muted);
}
.portal-visor-documento-fallback i {
    font-size: 2.5rem;
    display: block;
    margin-bottom: .5rem;
}
.portal-visor-documento-descarga {
    margin-top: .85rem;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .55rem 1.1rem;
    background: var(--c-primary);
    color: var(--c-text-light);
    border-radius: 999px;
    font-size: .9rem;
    font-weight: 600;
    text-decoration: none;
    transition: opacity .15s ease, transform .15s ease;
}
.portal-visor-documento-descarga:hover {
    opacity: .92;
    transform: translateY(-1px);
    color: var(--c-text-light);
    text-decoration: none;
}
/* Mobile (≤575.98px): blindaje extra contra overflow horizontal del iframe.
   Aunque iframe ya tiene width:100%, algunos visores externos (PDF.js,
   Office Online) inyectan width attribute o min-width interno que puede
   filtrarse al contenedor en viewports angostos. */
@media (max-width: 575.98px) {
    .portal-visor-documento,
    .portal-visor-documento-frame,
    .portal-visor-documento-iframe {
        max-width: 100%;
        min-width: 0;
    }
}
@media (max-width: 480px) {
    .portal-visor-documento-titulo { font-size: 1.2rem; }
    .portal-visor-documento-fallback {
        padding: 1.5rem .75rem;
        font-size: .9rem;
    }
    .portal-visor-documento-fallback i { font-size: 1.8rem; }
    /* Reducimos el alto de iframes grandes para que no dominen el viewport en mobile */
    .portal-visor-documento-frame--800,
    .portal-visor-documento-frame--1000 { height: 600px; }
}


/* ═══════════════════════════════════════════════════════════════════
   Tabla comparativa
   ═══════════════════════════════════════════════════════════════════ */
.portal-tabla-comparativa {
    margin: 1.5rem 0;
}
.portal-tabla-comparativa-header {
    margin-bottom: 1.25rem;
}
.portal-tabla-comparativa-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 .35rem;
    line-height: 1.2;
}
.portal-tabla-comparativa-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .95rem;
    line-height: 1.5;
}
/* Wrapper con scroll horizontal en mobile — tablas anchas no rompen
   el layout, el visitante hace scroll dentro. */
.portal-tabla-comparativa-wrap {
    overflow-x: auto;
    border-radius: 12px;
    background: var(--c-bg-card);
    box-shadow: 0 .15rem .5rem rgba(0,0,0,.04);
}
.portal-tabla-comparativa-tabla {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    background: var(--c-bg-card);
}
.portal-tabla-comparativa-tabla th,
.portal-tabla-comparativa-tabla td {
    padding: 1rem 1.15rem;
    text-align: left;
    vertical-align: middle;
    font-size: .92rem;
    /* El operador puede pegar URLs, codigos o palabras sin espacios en
       las celdas — permitimos que partan en cualquier lado antes de
       desbordar el ancho de la tabla (especialmente critico en la
       variante cards donde no hay scroll horizontal de rescate). */
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
}
.portal-tabla-comparativa-tabla tbody td {
    border-top: 1px solid var(--c-border-light);
}
.portal-tabla-comparativa-tabla tbody td:first-child {
    font-weight: 600;
    color: var(--c-text-strong);
}
.portal-tabla-comparativa-si {
    color: #16a34a;
    font-size: 1.3rem;
}
.portal-tabla-comparativa-no {
    color: #dc2626;
    font-size: 1.3rem;
}

/* ── Color del encabezado de la tabla ──────────────────────────────
   El operador elige uno de los 8 tonos institucionales en el catálogo
   (slug = `--head-<slug>`). Cada modificador setea tres variables:
     --c-tabla-head-bg     → background del thead th (sólido o gradiente).
     --c-tabla-head-fg     → color del texto del thead th.
     --c-tabla-head-label  → color sólido para etiquetas de columna en
                              el layout cards / mobile (los gradientes no
                              renderizan en texto, por eso necesitamos
                              una versión sólida del mismo tono).
   Esos vars se consumen desde las reglas thead de las tres variantes
   y desde las etiquetas inline del layout "cards" en mobile. */
.portal-tabla-comparativa--head-transparente  { --c-tabla-head-bg: transparent;
                                                --c-tabla-head-fg: var(--c-text-strong);
                                                --c-tabla-head-label: var(--c-text-strong); }
.portal-tabla-comparativa--head-primary       { --c-tabla-head-bg: var(--c-primary);
                                                --c-tabla-head-fg: var(--c-text-light);
                                                --c-tabla-head-label: var(--c-primary); }
.portal-tabla-comparativa--head-primary-suave { --c-tabla-head-bg: var(--c-primary-lighter);
                                                --c-tabla-head-fg: var(--c-text-brand);
                                                --c-tabla-head-label: var(--c-primary); }
.portal-tabla-comparativa--head-accent        { --c-tabla-head-bg: var(--c-accent);
                                                --c-tabla-head-fg: var(--c-text-light);
                                                --c-tabla-head-label: var(--c-accent); }
.portal-tabla-comparativa--head-accent-suave  { --c-tabla-head-bg: color-mix(in srgb, var(--c-accent) 25%, var(--c-bg));
                                                --c-tabla-head-fg: var(--c-text-brand);
                                                --c-tabla-head-label: var(--c-accent-dark, var(--c-accent)); }
.portal-tabla-comparativa--head-grad-primary  { --c-tabla-head-bg: linear-gradient(135deg,
                                                    var(--c-primary-light) 0%,
                                                    var(--c-primary) 55%,
                                                    var(--c-primary-dark) 100%);
                                                --c-tabla-head-fg: var(--c-text-light);
                                                --c-tabla-head-label: var(--c-primary); }
.portal-tabla-comparativa--head-grad-accent   { --c-tabla-head-bg: linear-gradient(135deg,
                                                    var(--c-accent2, var(--c-accent)) 0%,
                                                    var(--c-accent) 55%,
                                                    var(--c-accent-dark) 100%);
                                                --c-tabla-head-fg: var(--c-text-light);
                                                --c-tabla-head-label: var(--c-accent); }
.portal-tabla-comparativa--head-grad-mixto    { --c-tabla-head-bg: linear-gradient(135deg,
                                                    var(--c-primary) 0%,
                                                    var(--c-primary-dark) 45%,
                                                    var(--c-accent) 100%);
                                                --c-tabla-head-fg: var(--c-text-light);
                                                --c-tabla-head-label: var(--c-primary); }

/* ── Variante: Clásica (default) ────────────────────────────────
   Header en tono lighter, filas alternadas suaves. */
.portal-tabla-comparativa--clasica .portal-tabla-comparativa-wrap {
    border: 1px solid var(--c-border-light);
}
.portal-tabla-comparativa--clasica thead th {
    /* Color picker del catálogo: --c-tabla-head-bg / --c-tabla-head-fg.
       Fallback al look "clásico" tradicional cuando el bloque viene de
       una versión vieja sin color_encabezado. */
    background: var(--c-tabla-head-bg, var(--c-primary-lighter));
    color: var(--c-tabla-head-fg, var(--c-text-brand));
    font-weight: 700;
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    border-bottom: 2px solid var(--c-primary);
}
.portal-tabla-comparativa--clasica tbody tr:nth-child(odd) {
    background: var(--c-bg-card);
}
.portal-tabla-comparativa--clasica tbody tr:nth-child(even) {
    background: var(--c-bg-subtle);
}
.portal-tabla-comparativa--clasica thead th.is-destacada {
    background: var(--c-primary);
    color: var(--c-text-light);
}
.portal-tabla-comparativa--clasica tbody td.is-destacada {
    background: var(--c-primary-lighter);
    color: var(--c-text-strong);
    font-weight: 600;
}

/* ── Variante: Destacada ───────────────────────────────────────
   Header con gradiente de acento, filas con borde definido, sombra
   suave general. Look más "premium" / planes-pago. */
.portal-tabla-comparativa--destacada .portal-tabla-comparativa-wrap {
    border: 0;
    box-shadow: 0 .5rem 1.5rem rgba(0,0,0,.08);
}
.portal-tabla-comparativa--destacada thead th {
    /* Mismo picker que en clásica — fallback al gradiente original. */
    background: var(--c-tabla-head-bg,
        linear-gradient(135deg, var(--c-primary), var(--c-accent, var(--c-primary))));
    color: var(--c-tabla-head-fg, var(--c-text-light));
    font-weight: 700;
    font-size: .9rem;
    border-bottom: 0;
    padding: 1.15rem 1.15rem;
}
.portal-tabla-comparativa--destacada tbody tr {
    background: var(--c-bg-card);
}
.portal-tabla-comparativa--destacada tbody tr:hover {
    background: var(--c-bg-subtle);
}
.portal-tabla-comparativa--destacada thead th.is-destacada {
    background: var(--c-accent, var(--c-primary));
    position: relative;
}
.portal-tabla-comparativa--destacada thead th.is-destacada::after {
    content: '★';
    position: absolute;
    top: .35rem;
    right: .55rem;
    font-size: .8rem;
    opacity: .85;
}
.portal-tabla-comparativa--destacada tbody td.is-destacada {
    background: color-mix(in srgb, var(--c-accent, var(--c-primary)) 10%, transparent);
    box-shadow: inset 3px 0 0 var(--c-accent, var(--c-primary)),
                inset -3px 0 0 var(--c-accent, var(--c-primary));
    font-weight: 600;
}

/* ── Variante: Cards ───────────────────────────────────────────
   Cada fila se renderea como una card independiente. Para mobile o
   tablas con muchas columnas — evita scroll horizontal molesto. */
.portal-tabla-comparativa--cards .portal-tabla-comparativa-wrap {
    overflow: visible;
    background: transparent;
    box-shadow: none;
}
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla,
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla thead,
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla tbody,
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla tr,
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla th,
.portal-tabla-comparativa--cards .portal-tabla-comparativa-tabla td {
    display: block;
}
.portal-tabla-comparativa--cards thead {
    /* Ocultamos visualmente el header global; el label de cada celda
       se construye con `::before` desde el data-attribute. */
    position: absolute;
    left: -9999px;
}
.portal-tabla-comparativa--cards tbody tr {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    padding: .85rem 1rem;
    margin-bottom: .85rem;
    box-shadow: 0 .15rem .35rem rgba(0,0,0,.04);
}
.portal-tabla-comparativa--cards tbody td {
    border: 0;
    padding: .35rem 0;
    display: flex;
    /* Wrap habilitado para que valores largos bajen de linea en vez de
       empujar el ancho de la card cuando el operador pega textos largos. */
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: .85rem;
    text-align: right;
}
.portal-tabla-comparativa--cards tbody td:first-child {
    color: var(--c-text-brand);
    font-size: 1.05rem;
    border-bottom: 1px solid var(--c-border-light);
    padding-bottom: .55rem;
    margin-bottom: .35rem;
    text-align: left;
    display: block;
}
.portal-tabla-comparativa--cards tbody td:not(:first-child)::before {
    content: attr(data-label);
    /* La etiqueta de columna en cards usa el mismo tono institucional
       que el color elegido para el encabezado — así el operador no
       pierde la identidad visual de la tabla cuando colapsa a cards. */
    color: var(--c-tabla-head-label, var(--c-primary));
    font-weight: 700;
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .03em;
}
.portal-tabla-comparativa--cards tbody td.is-destacada {
    background: var(--c-primary-lighter);
    margin: 0 -1rem;
    padding-left: 1rem;
    padding-right: 1rem;
    border-radius: 4px;
}


/* ═══════════════════════════════════════════════════════════════════
   Pasos / proceso — vertical (timeline) y horizontal
   ═══════════════════════════════════════════════════════════════════ */
.portal-pasos {
    margin: 1.5rem 0;
}
.portal-pasos-header {
    margin-bottom: 1.25rem;
}
.portal-pasos-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 .25rem;
}
.portal-pasos-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .92rem;
}
.portal-pasos-lista {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: paso-num;
}
.portal-pasos-numero {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--portal-c-acento, var(--c-primary));
    color: var(--c-text-light);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.1rem;
}
.portal-pasos-numero i {
    font-size: 1.2rem;
}
.portal-pasos-item-titulo {
    color: var(--c-text-strong);
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0 0 .25rem;
    /* El operador puede pegar URLs/códigos largos sin espacios; forzamos
       el quiebre para que no desborden la columna en mobile. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-pasos-item-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .92rem;
    line-height: 1.5;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* ── Variante vertical: timeline con línea uniendo los círculos ── */
.portal-pasos--vertical .portal-pasos-lista {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    position: relative;
}
.portal-pasos--vertical .portal-pasos-item {
    display: flex;
    gap: 1.1rem;
    align-items: flex-start;
    position: relative;
}
/* Línea vertical que une los círculos. Pseudo-elemento en el wrapper,
   con altura calculada con flex. */
.portal-pasos--vertical .portal-pasos-item:not(:last-child)::before {
    content: '';
    position: absolute;
    left: 22px;            /* centro del círculo de 44px */
    top: 44px;
    bottom: -1.5rem;       /* alcanza el círculo del siguiente */
    width: 2px;
    background: var(--c-border-light);
    z-index: 0;
}
.portal-pasos--vertical .portal-pasos-numero {
    position: relative;
    z-index: 1;
}
.portal-pasos--vertical .portal-pasos-contenido {
    flex: 1;
    padding-top: .35rem;
    /* Sin esto, el flex item conserva su ancho intrínseco y un título
       largo desborda el bloque entero en mobile. */
    min-width: 0;
}

/* ── Variante horizontal: cards en fila con flecha entre cada paso ── */
.portal-pasos--horizontal .portal-pasos-lista {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1rem;
}
.portal-pasos--horizontal .portal-pasos-item {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    padding: 1.25rem 1rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .65rem;
}
.portal-pasos--horizontal .portal-pasos-contenido {
    width: 100%;
    min-width: 0;
}


/* ═══════════════════════════════════════════════════════════════════
   Tabs (pestañas)
   ═══════════════════════════════════════════════════════════════════ */
.portal-tabs {
    margin: 1.5rem 0;
}
.portal-tabs-header {
    margin-bottom: 1rem;
}
.portal-tabs-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0;
}
.portal-tabs-cabecera {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    margin-bottom: 1rem;
}
.portal-tabs-boton {
    background: transparent;
    border: 0;
    padding: .65rem 1rem;
    font-size: .92rem;
    font-weight: 600;
    color: var(--c-muted);
    cursor: pointer;
    transition: color .15s ease, background .15s ease, border-color .15s ease;
}
.portal-tabs-boton:hover,
.portal-tabs-boton:focus-visible {
    color: var(--c-text-brand);
    outline: none;
}
.portal-tabs-boton.is-active {
    color: var(--c-text-brand);
}
.portal-tabs-panel {
    color: var(--c-text-soft);
    line-height: 1.65;
    /* Contenedor visible: fondo card + borde + padding para que se
       lea como "bloque con contenido dinámico" en lugar de texto
       suelto sobre la página. */
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    padding: 1.5rem 1.75rem;
}
.portal-tabs-panel[hidden],
.portal-tabs-panel:not(.is-active) {
    display: none;
}
.portal-tabs-panel > *:first-child { margin-top: 0; }
.portal-tabs-panel > *:last-child { margin-bottom: 0; }

/* ── Variante "líneas" (default): subrayado en la pestaña activa ── */
.portal-tabs--lineas .portal-tabs-cabecera {
    border-bottom: 1px solid var(--c-border-light);
    gap: 0;
}
.portal-tabs--lineas .portal-tabs-boton {
    border-bottom: 3px solid transparent;
    margin-bottom: -1px;
}
.portal-tabs--lineas .portal-tabs-boton.is-active {
    border-bottom-color: var(--c-primary);
}

/* ── Variante "píldoras": botones redondeados ── */
.portal-tabs--pildoras .portal-tabs-boton {
    border-radius: 999px;
    background: var(--c-bg-subtle);
}
.portal-tabs--pildoras .portal-tabs-boton.is-active {
    background: var(--c-primary);
    color: var(--c-text-light);
}

/* ── Variante "cards": cada pestaña como bloque cuadrado ── */
.portal-tabs--cards .portal-tabs-cabecera {
    gap: .25rem;
    background: var(--c-bg-subtle);
    padding: .25rem;
    border-radius: 8px;
    display: inline-flex;
    /* inline-flex por defecto es nowrap y pisa el flex-wrap del
       selector base; lo re-declaramos para que la cabecera con
       muchas pestañas o títulos largos no overflow-ee horizontal
       en viewports angostos. */
    flex-wrap: wrap;
    max-width: 100%;
}
.portal-tabs--cards .portal-tabs-boton {
    border-radius: 6px;
}
.portal-tabs--cards .portal-tabs-boton.is-active {
    background: var(--c-bg-card);
    color: var(--c-text-brand);
    box-shadow: 0 .15rem .35rem rgba(0,0,0,.06);
}


/* ═══════════════════════════════════════════════════════════════════
   Citas / testimonio
   ═══════════════════════════════════════════════════════════════════ */
.portal-citas {
    margin: 1.5rem 0;
    padding: 1.5rem 1.25rem;
    border-radius: 12px;
    position: relative;
}
.portal-cita {
    margin: 0;
    text-align: center;
    max-width: 760px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
}
.portal-cita-icono {
    display: block;
    font-size: 3rem;
    line-height: 1;
    color: var(--c-text-brand);
    opacity: .25;
    margin: 0 auto .5rem;
}
.portal-cita-texto {
    margin: 0 0 1.25rem;
    font-size: 1.25rem;
    line-height: 1.5;
    color: var(--c-text-strong);
    font-style: italic;
    font-weight: 500;
    /* El operador puede pegar texto largo o URLs sin espacios:
       forzamos corte para que la cita nunca empuje el ancho. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-cita-texto::before { content: '“'; margin-right: .15rem; }
.portal-cita-texto::after  { content: '”'; margin-left: .15rem; }
.portal-cita-autor {
    display: inline-flex;
    align-items: center;
    gap: .75rem;
    margin-top: .35rem;
    /* Si el nombre/cargo no entran junto a la foto en mobile,
       que se acomoden debajo en lugar de desbordar. */
    flex-wrap: wrap;
    justify-content: center;
    max-width: 100%;
}
.portal-cita-autor-foto {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid var(--c-primary-lighter);
}
.portal-cita-autor-info {
    text-align: left;
    line-height: 1.25;
    /* min-width:0 habilita que el flex item se achique;
       overflow-wrap corta palabras larguísimas si las hay. */
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-cita-autor-nombre {
    color: var(--c-text-strong);
    font-size: .95rem;
    display: block;
}
.portal-cita-autor-cargo {
    color: var(--c-muted);
    font-size: .82rem;
    font-style: italic;
}

/* ── Variante carrusel ────────────────────────────────────────────── */
.portal-citas--carrusel .portal-citas-track {
    position: relative;
    min-height: 220px;
}
.portal-citas--carrusel .portal-cita--slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    transform: translateY(8px);
    pointer-events: none;
    transition: opacity .35s ease, transform .35s ease;
}
.portal-citas--carrusel .portal-cita--slide.is-active {
    position: relative;
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.portal-citas-dots {
    display: flex;
    justify-content: center;
    gap: .35rem;
    margin-top: 1rem;
}
.portal-citas-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--c-border);
    border: 0;
    padding: 0;
    cursor: pointer;
    transition: background .15s ease, transform .15s ease;
}
.portal-citas-dot:hover { background: var(--c-primary); }
.portal-citas-dot.is-active {
    background: var(--c-primary);
    transform: scale(1.35);
}

/* ── Ajustes mobile (xs) ──────────────────────────────────────────── */
@media (max-width: 575.98px) {
    .portal-citas {
        /* Padding lateral más chico en mobile para ganar ancho útil. */
        padding: 1.25rem .85rem;
    }
    .portal-cita-texto {
        /* La cita pesa visualmente: bajamos un escalón el tamaño. */
        font-size: 1.1rem;
    }
    .portal-cita-icono {
        font-size: 2.25rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   FAQ — Preguntas frecuentes (con schema.org/FAQPage)
   ═══════════════════════════════════════════════════════════════════ */
.portal-faq { margin: 1.5rem 0; }
.portal-faq-header { margin-bottom: 1.25rem; }
.portal-faq-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 .35rem;
}
.portal-faq-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .95rem;
}
.portal-faq-lista {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.portal-faq-item {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    overflow: hidden;
    transition: border-color .15s ease;
}
.portal-faq-item[open] { border-color: var(--c-primary); }
.portal-faq-pregunta {
    list-style: none;
    cursor: pointer;
    padding: 1rem 1.15rem;
    font-weight: 600;
    color: var(--c-text-strong);
    display: flex;
    align-items: center;
    gap: .75rem;
    font-size: 1rem;
    word-break: break-word;
}
.portal-faq-pregunta::-webkit-details-marker { display: none; }
/* `min-width:0` rompe el default `min-width:auto` de los flex items: sin
   esto, el span no puede encoger por debajo del ancho intrínseco de su
   contenido y una pregunta larga (o con URL) desborda el item — que tiene
   `overflow:hidden` y termina recortando el texto a la derecha en mobile.
   `overflow-wrap:anywhere` garantiza el wrap aunque la palabra no tenga
   puntos de corte naturales. */
.portal-faq-pregunta > span {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}
.portal-faq-icono {
    flex: 0 0 auto;
    color: var(--c-text-brand);
    transition: transform .25s ease;
}
.portal-faq-item[open] .portal-faq-icono {
    transform: rotate(45deg);
}
.portal-faq-respuesta {
    padding: 0 1.15rem 1.15rem;
    color: var(--c-text-soft);
    line-height: 1.6;
    font-size: .95rem;
    border-top: 1px solid var(--c-border-light);
    padding-top: 1rem;
    /* La respuesta recibe HTML libre del operador (CKEditor). Sin estas
       protecciones, una URL larga pegada como texto plano desborda el
       item en mobile y se recorta por el `overflow:hidden` del padre. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-faq-respuesta > *:last-child { margin-bottom: 0; }
@media (max-width: 480px) {
    .portal-faq-titulo { font-size: 1.3rem; }
    .portal-faq-pregunta {
        padding: .85rem .8rem;
        font-size: .95rem;
        gap: .5rem;
    }
    .portal-faq-respuesta {
        padding: 0 .8rem .85rem;
        padding-top: .85rem;
        font-size: .92rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Comparador antes / después
   ═══════════════════════════════════════════════════════════════════ */
.portal-antes-despues {
    margin: 1.5rem 0;
    /* `--ad-max-w` se sobreescribe por modificador de tamaño y limita
       el ancho del comparador para que no se vuelva gigantesco cuando
       la imagen es muy grande. Se centra en el espacio disponible. */
    --ad-max-w: 720px;
}
.portal-antes-despues--compacto { --ad-max-w: 480px; }
.portal-antes-despues--mediano  { --ad-max-w: 720px; }
.portal-antes-despues--grande   { --ad-max-w: 960px; }
.portal-antes-despues--completo { --ad-max-w: 100%; }
.portal-antes-despues-comparador,
.portal-antes-despues-header {
    max-width: var(--ad-max-w);
    margin-left: auto;
    margin-right: auto;
}
.portal-antes-despues-header {
    margin-bottom: 1rem;
}
.portal-antes-despues-titulo {
    color: var(--c-text-brand);
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 .25rem;
    /* El operador puede pegar URLs o palabras muy largas: forzamos
       quiebre para no romper el layout en mobile. */
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-antes-despues-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .95rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-antes-despues-comparador {
    position: relative;
    width: 100%;
    overflow: hidden;
    border-radius: 10px;
    box-shadow: 0 .35rem 1rem rgba(0,0,0,.1);
    user-select: none;
    cursor: ew-resize;
    background: var(--c-bg-subtle);
}
.portal-antes-despues-comparador.is-dragging { cursor: grabbing; }
.portal-antes-despues-img {
    display: block;
    width: 100%;
    height: auto;
    pointer-events: none;
}
.portal-antes-despues-img--antes {
    /* Capa de fondo. */
}
.portal-antes-despues-clip {
    position: absolute;
    inset: 0;
    /* Clipeamos la capa DESPUÉS desde la izquierda para que el lado
       derecho sea "después" y el izquierdo sea "antes" (la base que
       hay detrás). El JS de drag mantiene este sentido. */
    clip-path: inset(0 0 0 50%);
    will-change: clip-path;
}
.portal-antes-despues-clip .portal-antes-despues-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top left;
}
.portal-antes-despues-label {
    position: absolute;
    top: .85rem;
    background: rgba(0,0,0,.65);
    color: #fff;
    font-size: .82rem;
    font-weight: 600;
    padding: .25rem .65rem;
    border-radius: 999px;
    pointer-events: none;
    text-transform: uppercase;
    letter-spacing: .03em;
    /* En mobile el comparador queda angosto y las labels pueden chocar
       entre si; limitamos a 40% del ancho del comparador y permitimos
       quiebre defensivo si el operador escribe textos largos. */
    max-width: 40%;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.portal-antes-despues-label--izq { left: .85rem; }
.portal-antes-despues-label--der { right: .85rem; }
.portal-antes-despues-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 3px;
    background: rgba(255,255,255,.85);
    transform: translateX(-50%);
    will-change: left;
}
.portal-antes-despues-handle-circulo {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: #fff;
    color: var(--c-text-brand);
    box-shadow: 0 .25rem .65rem rgba(0,0,0,.25);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .15rem;
    font-size: .9rem;
    cursor: ew-resize;
}
.portal-antes-despues-handle:focus-visible .portal-antes-despues-handle-circulo {
    outline: 3px solid var(--c-primary);
    outline-offset: 3px;
}

/* Ajustes mobile: tipografia del header mas chica y labels mas pegadas
   al borde para ganar ancho util en pantallas xs (<=575.98px). */
@media (max-width: 575.98px) {
    .portal-antes-despues-titulo { font-size: 1.25rem; }
    .portal-antes-despues-descripcion { font-size: .9rem; }
    .portal-antes-despues-label {
        font-size: .72rem;
        padding: .2rem .5rem;
        top: .5rem;
    }
    .portal-antes-despues-label--izq { left: .5rem; }
    .portal-antes-despues-label--der { right: .5rem; }
}


/* ═══════════════════════════════════════════════════════════════════
   Galería con lightbox
   ═══════════════════════════════════════════════════════════════════ */
.portal-galeria { margin: 1.5rem 0; }
.portal-galeria-header { margin-bottom: 1rem; }
.portal-galeria-titulo {
    color: var(--c-text-brand);
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 .25rem;
}
.portal-galeria-descripcion {
    color: var(--c-text-soft);
    margin: 0;
    font-size: .95rem;
}
.portal-galeria-grid {
    display: grid;
    gap: .65rem;
}
.portal-galeria-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
.portal-galeria-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
.portal-galeria-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
.portal-galeria-grid--cols-5 { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 720px) {
    .portal-galeria-grid--cols-3,
    .portal-galeria-grid--cols-4,
    .portal-galeria-grid--cols-5 {
        grid-template-columns: repeat(2, 1fr);
    }
}
@media (max-width: 460px) {
    .portal-galeria-grid--cols-2,
    .portal-galeria-grid--cols-3,
    .portal-galeria-grid--cols-4,
    .portal-galeria-grid--cols-5 {
        grid-template-columns: 1fr;
    }
}
.portal-galeria-item {
    position: relative;
    aspect-ratio: 4 / 3;
    background: var(--c-bg-subtle);
    border: 0;
    padding: 0;
    border-radius: 8px;
    overflow: hidden;
    cursor: zoom-in;
    transition: transform .15s ease, box-shadow .15s ease;
}
.portal-galeria-item:hover {
    transform: translateY(-2px);
    box-shadow: 0 .4rem 1rem rgba(0,0,0,.12);
}
.portal-galeria-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .35s ease;
}
.portal-galeria-item:hover img { transform: scale(1.04); }
.portal-galeria-item-caption {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: .55rem .75rem;
    background: linear-gradient(180deg, transparent, rgba(0,0,0,.7));
    color: #fff;
    font-size: .82rem;
    line-height: 1.25;
    text-align: left;
    pointer-events: none;
}

/* ── Lightbox compartido (PortalLightbox global) ─────────────────
   Modal único reutilizado por galeria_lightbox y carrusel_imagenes.
   Las clases con prefijo `portal-lightbox-*` reemplazan a las viejas
   `portal-galeria-lb-*` para todas las nuevas integraciones. */
.portal-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: rgba(0,0,0,.92);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}
.portal-lightbox[hidden] { display: none; }
.portal-lightbox-figura {
    position: relative;
    margin: 0;
    max-width: 95vw;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.portal-lightbox-img {
    max-width: 100%;
    max-height: 80vh;
    object-fit: contain;
    border-radius: 6px;
    box-shadow: 0 1.25rem 2.5rem rgba(0,0,0,.5);
    background: #000;
}
.portal-lightbox-caption {
    margin-top: .85rem;
    color: rgba(255,255,255,.9);
    font-size: .95rem;
    text-align: center;
    max-width: 800px;
}
.portal-lightbox-caption[hidden] { display: none; }
.portal-lightbox-contador {
    position: absolute;
    top: -2.2rem;
    right: 0;
    color: rgba(255,255,255,.7);
    font-size: .85rem;
    font-weight: 600;
}
.portal-lightbox-cerrar,
.portal-lightbox-nav {
    position: absolute;
    background: rgba(255,255,255,.1);
    border: 1px solid rgba(255,255,255,.25);
    color: #fff;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    transition: background .15s ease, transform .15s ease;
    z-index: 2;
}
.portal-lightbox-cerrar:hover,
.portal-lightbox-nav:hover {
    background: rgba(255,255,255,.2);
    transform: scale(1.05);
}
.portal-lightbox-cerrar { top: 1.5rem; right: 1.5rem; }
.portal-lightbox-nav { top: 50%; transform: translateY(-50%); }
.portal-lightbox-nav--prev { left: 1.5rem; }
.portal-lightbox-nav--next { right: 1.5rem; }
.portal-lightbox-nav:hover { transform: translateY(-50%) scale(1.05); }
.portal-lightbox-nav[hidden] { display: none; }
@media (max-width: 600px) {
    .portal-lightbox-cerrar { top: .85rem; right: .85rem; width: 40px; height: 40px; }
    .portal-lightbox-nav { width: 40px; height: 40px; }
    .portal-lightbox-nav--prev { left: .5rem; }
    .portal-lightbox-nav--next { right: .5rem; }
}

/* Botón-wrapper del slide del carrusel cuando lightbox está activo. */
.portal-carrusel-slide-trigger {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
    cursor: zoom-in;
    display: block;
}
.portal-carrusel-slide-trigger:focus-visible {
    outline: 3px solid var(--c-primary);
    outline-offset: -3px;
}

/* Lightbox overlay (clase vieja, mantenida para galeria_lightbox
   hasta que migre al sistema compartido). */
.portal-galeria-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: rgba(0,0,0,.92);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}
.portal-galeria-lightbox[hidden] { display: none; }
.portal-galeria-lb-figura {
    position: relative;
    margin: 0;
    max-width: 95vw;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.portal-galeria-lb-img {
    max-width: 100%;
    max-height: 80vh;
    object-fit: contain;
    border-radius: 6px;
    box-shadow: 0 1.25rem 2.5rem rgba(0,0,0,.5);
    background: #000;
}
.portal-galeria-lb-caption {
    margin-top: .85rem;
    color: rgba(255,255,255,.9);
    font-size: .95rem;
    text-align: center;
    max-width: 800px;
}
.portal-galeria-lb-caption[hidden] { display: none; }
.portal-galeria-lb-contador {
    position: absolute;
    top: -2.2rem;
    right: 0;
    color: rgba(255,255,255,.7);
    font-size: .85rem;
    font-weight: 600;
}
.portal-galeria-lb-cerrar,
.portal-galeria-lb-nav {
    position: absolute;
    background: rgba(255,255,255,.1);
    border: 1px solid rgba(255,255,255,.25);
    color: #fff;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    transition: background .15s ease, transform .15s ease;
    z-index: 2;
}
.portal-galeria-lb-cerrar:hover,
.portal-galeria-lb-nav:hover {
    background: rgba(255,255,255,.2);
    transform: scale(1.05);
}
.portal-galeria-lb-cerrar {
    top: 1.5rem;
    right: 1.5rem;
}
.portal-galeria-lb-nav { top: 50%; transform: translateY(-50%); }
.portal-galeria-lb-nav--prev { left: 1.5rem; }
.portal-galeria-lb-nav--next { right: 1.5rem; }
.portal-galeria-lb-nav:hover { transform: translateY(-50%) scale(1.05); }
.portal-galeria-lb-nav[hidden] { display: none; }
@media (max-width: 600px) {
    .portal-galeria-lb-cerrar { top: .85rem; right: .85rem; width: 40px; height: 40px; }
    .portal-galeria-lb-nav { width: 40px; height: 40px; }
    .portal-galeria-lb-nav--prev { left: .5rem; }
    .portal-galeria-lb-nav--next { right: .5rem; }
}


/* ═══════════════════════════════════════════════════════════════════
   Hero con video de fondo
   ═══════════════════════════════════════════════════════════════════ */
.portal-hero-video {
    position: relative;
    width: 100%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-text-light);
    margin: 0 0 1.5rem;
    /* Fallback final: si el <video> falla AND la <img> de fallback
       también falla (URL rota, 404), el container queda con el primary
       institucional sólido — preserva la legibilidad del titulo/CTA
       en vez de mostrar un rectángulo en blanco/transparente. */
    background-color: var(--c-primary);
}
.portal-hero-video--compacto { min-height: 40vh; }
.portal-hero-video--mediano  { min-height: 60vh; }
.portal-hero-video--grande   { min-height: 80vh; }
.portal-hero-video--viewport { min-height: 100vh; }
.portal-hero-video-bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
}
.portal-hero-video-overlay {
    position: absolute;
    inset: 0;
    background: #000;
    z-index: 1;
}
.portal-hero-video--overlay-ninguno .portal-hero-video-overlay { opacity: 0; }
.portal-hero-video--overlay-leve    .portal-hero-video-overlay { opacity: .25; }
.portal-hero-video--overlay-medio   .portal-hero-video-overlay { opacity: .5; }
.portal-hero-video--overlay-fuerte  .portal-hero-video-overlay { opacity: .75; }
.portal-hero-video-contenido {
    position: relative;
    z-index: 2;
    max-width: 900px;
    padding: 2rem 1.5rem;
    width: 100%;
}
.portal-hero-video--align-izquierda .portal-hero-video-contenido { text-align: left;  margin-right: auto; }
.portal-hero-video--align-centro    .portal-hero-video-contenido { text-align: center; }
.portal-hero-video--align-derecha   .portal-hero-video-contenido { text-align: right; margin-left: auto; }
.portal-hero-video-titulo {
    font-size: clamp(2rem, 5vw, 3.5rem);
    font-weight: 800;
    line-height: 1.1;
    margin: 0 0 .85rem;
    text-shadow: 0 .15rem .85rem rgba(0,0,0,.4);
    color: #fff;
    /* Respetar Enter / tabs / espacios múltiples del editor. */
    white-space: pre-wrap;
    /* El operador puede pegar URLs o palabras largas sin espacios; sin
       esto el título desborda el viewport en mobile y dispara scroll
       horizontal en toda la página. */
    overflow-wrap: anywhere;
}
.portal-hero-video-subtitulo {
    font-size: clamp(1rem, 2vw, 1.25rem);
    line-height: 1.55;
    margin: 0 0 1.25rem;
    text-shadow: 0 .1rem .55rem rgba(0,0,0,.4);
    color: rgba(255,255,255,.95);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.portal-hero-video-cta {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .8rem 1.6rem;
    background: var(--c-primary);
    color: #fff;
    border-radius: 999px;
    font-size: 1rem;
    font-weight: 600;
    text-decoration: none;
    box-shadow: 0 .35rem 1rem rgba(0,0,0,.3);
    transition: transform .18s ease, box-shadow .18s ease, opacity .18s ease;
}
.portal-hero-video-cta:hover {
    transform: translateY(-2px);
    box-shadow: 0 .55rem 1.35rem rgba(0,0,0,.4);
    color: #fff;
    text-decoration: none;
    opacity: .95;
}
/* Ajustes mobile: padding lateral más compacto y CTA que rompe línea
   si el operador eligió un label largo. */
@media (max-width: 575.98px) {
    .portal-hero-video-contenido {
        padding: 1.5rem 1rem;
    }
    .portal-hero-video-cta {
        max-width: 100%;
        flex-wrap: wrap;
        justify-content: center;
        text-align: center;
        overflow-wrap: anywhere;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Defensa global contra overflow horizontal
   ═══════════════════════════════════════════════════════════════════
   El portal expone contenido cargado por operadores: PDFs, imágenes,
   videos, iframes de Google Maps, embebidos de YouTube/Vimeo, tablas
   de CKEditor. Cualquiera de esos elementos sin `max-width: 100%`
   empuja el ancho del body en viewports chicos y rompe la
   responsividad — el viewport entero queda con scroll horizontal y
   los bloques que sí son fluidos se ven cortados a la derecha porque
   el body es más ancho que la pantalla.
   Estas reglas ponen un techo de "no más ancho que tu padre" a los
   sospechosos habituales sin tocar su altura/aspect ratio. Las
   ponemos al final del archivo para que ganen sobre cualquier rule
   anterior por orden de cascada. */
.portal-main :where(img, video, iframe, table, pre, canvas, svg) {
    max-width: 100%;
}
/* Para imágenes/videos liberamos el alto para que se ajuste al ratio
   nativo cuando bajan de ancho. Iframes, tablas y pre conservan su
   altura porque la decidió el bloque que las contiene (visor de
   documentos, mapa de Google Maps, código de richtext). */
.portal-main :where(img, video, canvas) {
    height: auto;
}
/* `min-width: 0` en cadena para que el ancho de los hijos no fuerce
   al padre a crecer más que su columna del layout. Sin esto, en grids
   1fr (mobile), un nieto con contenido intrínseco wide (PDF preview,
   título sin espacios, tabla CKEditor) puede empujar el grid item por
   encima de 100% y la fila completa overflowea. Es el truco estándar
   para "que flex/grid items sí puedan achicarse" — por default
   `min-width: auto` evita que el item baje de su contenido. */
.portal-main,
.portal-main :where(section, article, .portal-container) {
    min-width: 0;
}


/* ═══════════════════════════════════════════════════════════════════
   Modal de Acceso directo
   ═══════════════════════════════════════════════════════════════════
   Se abre cuando el operador tildó "Abrir en modal" en un acceso de
   la grilla. Muestra título + contenido richtext (con imágenes de
   biblioteca embebidas). Un modal por bloque — los multiples grillas
   en la misma página tienen instancias independientes. */
.portal-acceso-modal {
    position: fixed;
    inset: 0;
    z-index: 10000;   /* arriba del a11y widget (9990) y de cualquier sticky */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.portal-acceso-modal[hidden] { display: none; }
.portal-acceso-modal-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, .55);
    cursor: pointer;
}
.portal-acceso-modal-dialog {
    position: relative;
    background: var(--c-bg-card, #fff);
    color: var(--c-text);
    border-radius: 12px;
    max-width: 720px;
    width: 100%;
    max-height: calc(100vh - 2rem);
    display: flex;
    flex-direction: column;
    box-shadow: 0 1rem 3rem rgba(0, 0, 0, .35);
    /* Animación de entrada — slide up sutil, ~180ms. */
    animation: portal-acceso-modal-aparecer .18s ease-out;
}
@keyframes portal-acceso-modal-aparecer {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
}
.portal-acceso-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.15rem 1.5rem;
    border-bottom: 1px solid var(--c-border-light);
    flex-shrink: 0;
}
.portal-acceso-modal-titulo {
    margin: 0;
    color: var(--c-text-brand);
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1.25;
    /* El operador puede haber dejado el título largo — lo dejamos
       wrappear en lugar de overflowear. */
    word-wrap: break-word;
}
.portal-acceso-modal-cerrar {
    background: transparent;
    border: 0;
    font-size: 1.85rem;
    line-height: 1;
    color: var(--c-muted);
    cursor: pointer;
    padding: 0 .35rem;
    transition: color .12s ease;
    flex-shrink: 0;
}
.portal-acceso-modal-cerrar:hover,
.portal-acceso-modal-cerrar:focus-visible {
    color: var(--c-text-strong);
    outline: none;
}
.portal-acceso-modal-cuerpo {
    padding: 1.25rem 1.5rem 1.5rem;
    color: var(--c-text);
    line-height: 1.6;
    overflow-y: auto;
    /* Estilos básicos para el contenido richtext que viene de CKE5:
       headings, listas, imágenes embebidas de biblioteca. Las
       imágenes se constrinen al ancho del modal y arrancan en bloque
       para evitar floats raros. */
}
.portal-acceso-modal-cuerpo > *:first-child { margin-top: 0; }
.portal-acceso-modal-cuerpo > *:last-child { margin-bottom: 0; }
.portal-acceso-modal-cuerpo img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 1rem auto;
    border-radius: 6px;
}
/* Imágenes con resize de CKEditor — vienen envueltas en
   `<figure class="image image_resized" style="width:X%">`. La figura
   guarda el ancho elegido por el operador; la imagen interna se
   estira al 100% de la figura. Centramos la figura por default. */
.portal-acceso-modal-cuerpo figure.image {
    margin: 1rem auto;
    max-width: 100%;
}
.portal-acceso-modal-cuerpo figure.image img {
    width: 100%;
    height: auto;
    margin: 0;
}
.portal-acceso-modal-cuerpo figure.image.image-style-side {
    float: right;
    margin: .5rem 0 1rem 1rem;
    max-width: 50%;
}
.portal-acceso-modal-cuerpo figure.image.image-style-align-left {
    margin-left: 0; margin-right: auto;
}
.portal-acceso-modal-cuerpo figure.image.image-style-align-right {
    margin-left: auto; margin-right: 0;
}
.portal-acceso-modal-cuerpo h1,
.portal-acceso-modal-cuerpo h2,
.portal-acceso-modal-cuerpo h3,
.portal-acceso-modal-cuerpo h4 {
    color: var(--c-text-brand);
    margin: 1.25rem 0 .5rem;
}
.portal-acceso-modal-cuerpo p { margin: 0 0 .85rem; }
.portal-acceso-modal-cuerpo ul,
.portal-acceso-modal-cuerpo ol { margin: 0 0 1rem; padding-left: 1.5rem; }
.portal-acceso-modal-cuerpo a { color: var(--c-text-brand); text-decoration: underline; }

@media (max-width: 640px) {
    .portal-acceso-modal { padding: .5rem; }
    .portal-acceso-modal-header { padding: 1rem 1.1rem; }
    .portal-acceso-modal-titulo { font-size: 1.15rem; }
    .portal-acceso-modal-cuerpo { padding: 1rem 1.1rem 1.2rem; }
}


/* ═══════════════════════════════════════════════════════════════════
   Mobile fixes — Tabs y Tabla comparativa
   ═══════════════════════════════════════════════════════════════════
   Reglas que sólo aplican abajo de 640px para resolver dos problemas
   de layout en pantallas chicas:
     - Tabs: con flex-wrap horizontal las pestañas quedaban
       amontonadas y el contenido del panel se squeezeaba contra el
       borde, con botones internos overflow-eando.
     - Tabla comparativa (variantes "clasica" y "destacada"): el
       wrapper con `overflow-x: auto` clip-eaba la primera columna
       cuando el visitante scrolleaba, dejando contenido ilegible.
   La estrategia es: en mobile, ambos bloques mutan a un layout
   stackeado/vertical que prioriza legibilidad sobre el diseño original.
*/
@media (max-width: 640px) {

    /* ── Tabs en mobile ──────────────────────────────────────────────
       Botones full-width stackeados. Todas las variantes terminan
       luciendo como "píldoras verticales" — patrón estándar de tabs
       en mobile (iOS/Material). Cada selector pisa explícitamente al
       de su variante en desktop, sin recurrir a !important. */
    .portal-tabs-cabecera,
    .portal-tabs--lineas .portal-tabs-cabecera,
    .portal-tabs--cards .portal-tabs-cabecera {
        display: flex;
        flex-direction: column;
        flex-wrap: nowrap;
        gap: .35rem;
        background: transparent;
        padding: 0;
        border-bottom: 0;
    }
    .portal-tabs-boton {
        width: 100%;
        text-align: left;
        background: var(--c-bg-subtle);
        border: 0;
        border-radius: 6px;
        padding: .7rem .9rem;
        font-size: .9rem;
        /* Títulos tipeados por el operador pueden incluir palabras
           o URLs largas sin espacios; sin esto desbordan el botón
           full-width y rompen el ancho del bloque en mobile. */
        overflow-wrap: anywhere;
        word-break: break-word;
    }
    .portal-tabs-boton.is-active {
        background: var(--c-primary);
        color: var(--c-text-light);
    }
    .portal-tabs--lineas .portal-tabs-boton {
        border-bottom: 0;
        margin-bottom: 0;
    }
    /* Panel: padding más chico + min-width 0 para que el flex/grid
       de adentro no force al panel a crecer más allá del viewport.
       Contenido cargado vía richtext (imágenes, tablas, código) se
       contiene al 100% del ancho para no overflow-ear. */
    .portal-tabs-panel {
        padding: 1rem 1.1rem;
        min-width: 0;
        overflow-wrap: anywhere;
    }
    .portal-tabs-panel :where(img, video, table, pre, iframe) {
        max-width: 100%;
        height: auto;
    }

    /* ── Tabla comparativa en mobile ─────────────────────────────────
       Forzamos el layout "cards" para todas las variantes — cada fila
       se convierte en una tarjeta apilada con labels inline.
       El template ya emite `data-label` en cada celda, así que las
       etiquetas se generan con ::before sin tocar el HTML. */
    .portal-tabla-comparativa-wrap {
        overflow: visible;
        background: transparent;
        box-shadow: none;
        border: 0;
        border-radius: 0;
    }
    .portal-tabla-comparativa-tabla,
    .portal-tabla-comparativa-tabla thead,
    .portal-tabla-comparativa-tabla tbody,
    .portal-tabla-comparativa-tabla tr,
    .portal-tabla-comparativa-tabla th,
    .portal-tabla-comparativa-tabla td {
        display: block;
    }
    .portal-tabla-comparativa-tabla thead {
        /* Ocultamos visualmente el header global — quedaría redundante
           con los labels inline. Lo dejamos en el DOM para screen
           readers (mejor que display:none). */
        position: absolute;
        left: -9999px;
    }
    .portal-tabla-comparativa-tabla tbody tr {
        background: var(--c-bg-card);
        border: 1px solid var(--c-border-light);
        border-radius: 10px;
        padding: .85rem 1rem;
        margin-bottom: .85rem;
        box-shadow: 0 .15rem .35rem rgba(0,0,0,.04);
    }
    .portal-tabla-comparativa-tabla tbody td {
        border: 0;
        padding: .55rem 0;
        /* Stack vertical: la etiqueta de columna queda arriba (como un
           mini-título de columna) y el valor abajo. Antes era flex
           space-between, lo que hacía que la etiqueta quedara chiquita
           a la izquierda y se perdiera. */
        display: block;
        text-align: left;
        font-size: .95rem;
        border-bottom: 1px solid var(--c-border-light);
    }
    .portal-tabla-comparativa-tabla tbody td:last-child {
        border-bottom: 0;
        padding-bottom: 0;
    }
    .portal-tabla-comparativa-tabla tbody td:first-child {
        color: var(--c-text-brand);
        font-size: 1.05rem;
        font-weight: 700;
        border-bottom: 1px solid var(--c-border-light);
        padding-bottom: .55rem;
        margin-bottom: .55rem;
        text-align: left;
        display: block;
    }
    .portal-tabla-comparativa-tabla tbody td:not(:first-child)::before {
        content: attr(data-label);
        /* Etiqueta como título de mini-sección — toma el color elegido
           por el operador en el picker del encabezado para mantener
           identidad visual cuando la tabla colapsa a cards. */
        color: var(--c-tabla-head-label, var(--c-primary));
        font-weight: 700;
        font-size: .82rem;
        text-transform: uppercase;
        letter-spacing: .04em;
        display: block;
        margin-bottom: .2rem;
    }
    .portal-tabla-comparativa-tabla tbody td.is-destacada {
        background: var(--c-primary-lighter);
        margin: 0 -1rem;
        padding-left: 1rem;
        padding-right: 1rem;
        border-radius: 4px;
        /* Sin el inset shadow de la variante "destacada" — sólo
           confunde en cards. */
        box-shadow: none;
    }
}


/* ════════════════════════════════════════════════════════════════════
   Banner de noticias destacadas (banner_noticias_destacadas)

   Dos variantes según la zona donde el operador montó el bloque:
     .portal-banner-noticias--ancho   → imagen + chip + título + bajada
     .portal-banner-noticias--lateral → solo thumb + título, vertical

   Si hay 2+ noticias se agrega .portal-banner-noticias--carrusel y
   los slides rotan con la clase .is-active manejada por JS (template
   inline al pie del bloque).
   ════════════════════════════════════════════════════════════════════ */

.portal-banner-noticias {
    position: relative;
    border-radius: 12px;
    overflow: hidden;
}

/* Slides apilados ocupando la misma área; solo .is-active es visible.
   Para la transición usamos opacity (no display) así el fade queda
   suave y el reflow no salta entre cards de tamaños distintos. */
.portal-banner-noticias-slides {
    position: relative;
    display: block;
}
.portal-banner-noticias-slide {
    position: absolute;
    inset: 0;
    display: block;
    opacity: 0;
    visibility: hidden;
    transition: opacity .35s ease;
    text-decoration: none;
    color: inherit;
}
.portal-banner-noticias-slide.is-active {
    position: relative;          /* el primero define la altura del bloque */
    opacity: 1;
    visibility: visible;
}
/* Cuando hay un solo slide (no carrusel), no hace falta superponerlos */
.portal-banner-noticias:not(.portal-banner-noticias--carrusel)
    .portal-banner-noticias-slide {
    position: relative;
    opacity: 1;
    visibility: visible;
}

/* ── Variante ancha (zonas no-lateral) ──────────────────────────── */
.portal-banner-noticias--ancho {
    /* Activamos container queries para que el carrusel pueda
       adaptar su forma según el espacio real que tiene disponible.
       Necesario para el caso "dos banners de noticias lado a lado en
       la misma zona" — el carrusel ancho 21:9 panorámico queda
       desparejo al lado del cubo cuadrado. Con CQ, cuando el ancho
       del bloque es chico (zona dividida) el carrusel pasa a un
       formato más square que matchea al cubo. */
    container-type: inline-size;
}
.portal-banner-noticias--ancho .portal-banner-noticias-slide {
    aspect-ratio: 21 / 9;
    min-height: 220px;
}

/* Cuando el carrusel ancho tiene espacio acotado (zona dividida en
   2+ columnas, o mobile en desktop), pasamos a formato cuadrado +
   max-width 480px + centrado — mismo armazón que .portal-cubo-noticias
   para que ambos se vean uniformes cuando comparten una fila. El
   threshold 700px alinea con el ancho típico que tiene un bloque en
   una zona dividida en 2 columnas (sin llegar a romper en mobile). */
@container (max-width: 700px) {
    .portal-banner-noticias--ancho {
        max-width: 480px;
        margin: 1.5rem auto;
    }
    .portal-banner-noticias--ancho .portal-banner-noticias-slide {
        aspect-ratio: 1 / 1;
    }
    /* En formato cuadrado, la bajada ocupa demasiado espacio
       (3-4 líneas comen el alto disponible). La recortamos a 1
       línea para que título + chip + fecha sigan siendo legibles. */
    .portal-banner-noticias--ancho .portal-banner-noticias-bajada {
        -webkit-line-clamp: 1;
    }
}
.portal-banner-noticias--ancho .portal-banner-noticias-img-wrap {
    position: absolute;
    inset: 0;
}
.portal-banner-noticias--ancho .portal-banner-noticias-img-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-banner-noticias--ancho .portal-banner-noticias-img-wrap--placeholder {
    background: linear-gradient(135deg, var(--c-accent), var(--c-primary));
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-text-light);
    font-size: 4rem;
    opacity: .35;
}
.portal-banner-noticias--ancho .portal-banner-noticias-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0,0,0,.78) 0%,
                                         rgba(0,0,0,.35) 55%,
                                         rgba(0,0,0,.05) 100%);
}
.portal-banner-noticias--ancho .portal-banner-noticias-inner {
    position: absolute;
    inset: auto 0 0 0;
    color: var(--c-text-light);
    padding-block: 1.5rem;
    z-index: 2;
}
.portal-banner-noticias--ancho .portal-banner-noticias-chip {
    display: inline-block;
    padding: .2rem .6rem;
    background: var(--c-accent);
    color: var(--c-bg-card);
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .04em;
    border-radius: 4px;
    margin-bottom: .5rem;
}
.portal-banner-noticias--ancho .portal-banner-noticias-titulo {
    font-size: clamp(1.3rem, 2.2vw, 1.9rem);
    font-weight: 700;
    line-height: 1.2;
    margin: 0 0 .5rem;
    color: inherit;
}
.portal-banner-noticias--ancho .portal-banner-noticias-bajada {
    font-size: .95rem;
    line-height: 1.45;
    margin: 0 0 .6rem;
    color: inherit;
    opacity: .92;
    /* Limitar a 2 líneas para no romper el aspect-ratio del slide */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.portal-banner-noticias--ancho .portal-banner-noticias-fecha {
    font-size: .78rem;
    opacity: .8;
}

/* ── Variante lateral (zona angosta) ──────────────────────────────
   Layout vertical: thumb cuadrado/portrait arriba, título debajo.
   Sin bajada, sin fecha, sin chip — la columna es angosta y la
   prioridad es que el visitante reconozca rápido qué hay para leer. */
.portal-banner-noticias--lateral {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
}
.portal-banner-noticias--lateral .portal-banner-noticias-slide {
    display: block;
    padding: 0;
}
.portal-banner-noticias--lateral .portal-banner-noticias-img-wrap {
    width: 100%;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--c-bg-subtle);
}
.portal-banner-noticias--lateral .portal-banner-noticias-img-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-banner-noticias--lateral .portal-banner-noticias-img-wrap--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-primary);
    font-size: 2rem;
    opacity: .4;
}
.portal-banner-noticias--lateral .portal-banner-noticias-titulo {
    font-size: .95rem;
    font-weight: 600;
    line-height: 1.3;
    margin: 0;
    padding: .75rem 1rem 1rem;
    color: var(--c-text);
}
.portal-banner-noticias--lateral .portal-banner-noticias-slide:hover
    .portal-banner-noticias-titulo {
    color: var(--c-primary);
}

/* ── Dots de navegación del carrusel ──────────────────────────────
   Aparecen abajo a la derecha en ancho (sobre la imagen) o centrados
   debajo del título en lateral. */
.portal-banner-noticias-dots {
    display: flex;
    gap: .35rem;
    justify-content: center;
    z-index: 3;
}
.portal-banner-noticias--ancho .portal-banner-noticias-dots {
    position: absolute;
    right: 1.25rem;
    bottom: .75rem;
}
.portal-banner-noticias--lateral .portal-banner-noticias-dots {
    padding: 0 1rem .75rem;
}
.portal-banner-noticias-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    border: 0;
    background: rgba(255, 255, 255, .55);
    cursor: pointer;
    padding: 0;
    transition: background .15s ease, transform .15s ease;
}
.portal-banner-noticias--lateral .portal-banner-noticias-dot {
    background: var(--c-border);
}
.portal-banner-noticias-dot.is-active {
    background: var(--c-bg-card);
    transform: scale(1.2);
}
.portal-banner-noticias--lateral .portal-banner-noticias-dot.is-active {
    background: var(--c-primary);
}
.portal-banner-noticias-dot:hover {
    background: var(--c-accent);
}

@media (prefers-reduced-motion: reduce) {
    .portal-banner-noticias-slide { transition: none; }
}

/* En mobile el ancho del slide es chico y un titulo con un token
   largo sin espacios (URL pegada, hashtag, codigo de tramite) puede
   forzar overflow horizontal. `overflow-wrap: anywhere` permite
   cortar la palabra cuando no hay otra forma de que entre. Aplica a
   las tres variantes (ancho, lateral, cubo) porque las tres muestran
   texto cargado por el operador. */
@media (max-width: 575.98px) {
    .portal-banner-noticias-titulo,
    .portal-banner-noticias-bajada,
    .portal-banner-noticias-chip,
    .portal-cubo-noticias-titulo,
    .portal-cubo-noticias-chip {
        overflow-wrap: anywhere;
    }
}

/* ══════════════════════════════════════════════════════════════════
   Módulo Datos y visualización — bloques KPI y Gráfico
   ══════════════════════════════════════════════════════════════════
   Bloques opt-in cuando el tenant tiene `mod_datos_viz` en su lista
   de módulos activos. KPI = indicadores con número grande; Gráfico =
   canvas que Chart.js puebla.
*/

/* ══════════════════════════════════════════════════════════════════
   Variante "Cubo 3D" del banner de noticias
   ══════════════════════════════════════════════════════════════════
   Cubo simple de 4 caras que gira continuamente sobre el eje Y. Las
   4 caras son cuadradas (lado = `--c-lado`); el radio del cubo es
   lado/2, así que cada cara queda en `translateZ(lado/2)` con su
   respectiva rotación (0°, 90°, 180°, 270°).
   El cubo tiene un tamaño máximo acotado (`max-width`) y queda
   centrado dentro del bloque, así no se "come" toda la zona.
   Si hay menos de 4 noticias, el template repite los items para
   llenar las 4 caras (ver filter `rellenar_a` en pagebuilder_tags).
*/
.portal-cubo-noticias {
    position: relative;
    width: 100%;
    max-width: 480px;
    margin: 1.5rem auto;
    --c-duracion: 24s;
    /* `--c-half` (mitad del lado del cubo, en px) lo setea JS al
       cargar y al hacer resize. Default razonable mientras el JS no
       corrió todavía: 240px = la mitad del max-width. CSS NO acepta
       porcentajes en translateZ() (solo longitudes), por eso el
       valor llega en px desde el cliente. */
    --c-half: 240px;
    /* Clip de la proyección 3D que se sale del bounding box del cubo
       cuando está en zonas angostas (half-width, mobile). La rotación
       sobre Y axis + translateZ proyecta las caras laterales más allá
       de los bordes del wrapper en pantallas chicas, invadiendo el
       bloque vecino. `overflow:hidden` acá NO rompe el preserve-3d
       de `.portal-cubo-noticias-faces` (descendiente) porque la regla
       de flattening del spec aplica al elemento con preserve-3d, no
       a sus ancestros. */
    overflow: hidden;
}
.portal-cubo-noticias-stage {
    position: relative;
    width: 100%;
    /* Cuadrado para que las caras sean cuadradas (lado = ancho).
       Usamos padding-bottom por compat con browsers que no soportan
       aspect-ratio; el resultado es el mismo. */
    padding-bottom: 100%;
    /* Perspective grande = efecto 3D sutil. Con valores chicos
       (1200px) el cubo "se mete" fuerte hacia adentro; con 2800px
       casi parece un flip plano y solo se insinúa la profundidad
       durante la transición entre caras. Más amable a la vista. */
    perspective: 2800px;
    perspective-origin: 50% 50%;
}
.portal-cubo-noticias-faces {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    will-change: transform;
    /* La animación la crea JS via Web Animations API con offsets
       calculados por bloque (depende de `data-intervalo` y
       `data-cubo-giro`). El pause al hover y la desactivación con
       `prefers-reduced-motion` también viven en JS. CSS solo aporta
       la base 3D (transform-style + perspective del stage). */
}

/* Cada cara ocupa todo el stage y se rota+empuja hacia adelante el
   equivalente a "medio cubo" (--c-half px). Como las caras son
   cuadradas y `padding-bottom: 100%`, el lado del cubo = ancho del
   stage, y --c-half = ancho/2 (lo calcula JS en el `init` y en
   cada resize). */
.portal-cubo-noticias-cara {
    position: absolute;
    inset: 0;
    display: block;
    overflow: hidden;
    border-radius: 10px;
    background: var(--c-bg-card, #fff);
    box-shadow: 0 .5rem 1.2rem rgba(0, 0, 0, .18);
    text-decoration: none;
    color: inherit;
    backface-visibility: hidden;
}
.portal-cubo-noticias-cara--0 { transform: rotateY(  0deg) translateZ(var(--c-half)); }
.portal-cubo-noticias-cara--1 { transform: rotateY( 90deg) translateZ(var(--c-half)); }
.portal-cubo-noticias-cara--2 { transform: rotateY(180deg) translateZ(var(--c-half)); }
.portal-cubo-noticias-cara--3 { transform: rotateY(270deg) translateZ(var(--c-half)); }

.portal-cubo-noticias-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.portal-cubo-noticias-img--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    color: var(--c-text-muted, #999);
    background: var(--c-bg-subtle, #f2f2f2);
}
.portal-cubo-noticias-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        180deg,
        rgba(0, 0, 0, 0) 45%,
        rgba(0, 0, 0, .75) 100%
    );
    pointer-events: none;
}
.portal-cubo-noticias-info {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: .85rem 1rem 1rem;
    color: #fff;
    z-index: 1;
}
.portal-cubo-noticias-chip {
    display: inline-block;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .06em;
    padding: .18rem .5rem;
    border-radius: 999px;
    background: var(--c-primary, #003c71);
    color: #fff;
    margin-bottom: .35rem;
}
.portal-cubo-noticias-titulo {
    font-size: 1rem;
    font-weight: 700;
    margin: 0;
    line-height: 1.25;
    text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
    /* Limitar a 3 líneas para que no se desborde la cara cuadrada. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* En zona lateral, el cubo es más chico todavía. */
.portal-cubo-noticias--lateral {
    max-width: 280px;
}
.portal-cubo-noticias--lateral .portal-cubo-noticias-titulo {
    font-size: .9rem;
    -webkit-line-clamp: 2;
}
.portal-cubo-noticias--lateral .portal-cubo-noticias-info {
    padding: .6rem .75rem .7rem;
}

@media (max-width: 575.98px) {
    /* Celulares chicos: el cubo se acota a 320px asi sobra aire lateral
       y los textos sobreimpresos siguen legibles. Breakpoint alineado
       con el estandar del proyecto (Bootstrap xs = 575.98px).
       --c-half explicito como fallback si el JS no llegó a setearlo
       (preview en iframe del page builder donde clientWidth devuelve
       0 al primer paint). 160px = mitad del max-width 320 → un cubo
       de 320x320 con caras correctamente acomodadas. Si JS dispara
       después con el ancho real (ej. 280px en mobile chico), inline
       style gana y reemplaza este 160 por w/2 real. */
    .portal-cubo-noticias {
        max-width: 320px;
        --c-half: 160px;
    }
}

/* Tablet/zonas angostas (no-mobile pero menos ancho que el max-width
   del cubo 480px). Sin este fallback, JS-en-iframe puede dejar
   --c-half=240 cuando el cubo real mide ~360px → cubo "más gordo en
   Z que en XY" → distorsion visible. */
@media (min-width: 576px) and (max-width: 991.98px) {
    .portal-cubo-noticias { --c-half: 200px; }
}

/* ── Controles manuales del cubo ──────────────────────────────────
   Siempre presentes (los dots indican qué cara está visible). Cuando
   la animación está pausada por el toggle de accesibilidad o por el
   operador (`autoplay=0`), las flechas prev/next se hacen prominentes
   y la sección recibe la clase `.is-pausado`. */
.portal-cubo-noticias-controles {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    margin-top: .75rem;
}
.portal-cubo-noticias-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid var(--c-border);
    background: var(--c-bg-card, #fff);
    color: var(--c-text, #222);
    cursor: pointer;
    transition: background .15s, color .15s, transform .1s;
    /* Por default ocultas — solo aparecen cuando está pausado. Se
       puede sobreescribir con `is-pausado`. */
    opacity: 0;
    pointer-events: none;
}
.portal-cubo-noticias-btn:hover {
    background: var(--c-primary, #003c71);
    color: #fff;
}
.portal-cubo-noticias-btn:active {
    transform: scale(.94);
}
.portal-cubo-noticias.is-pausado .portal-cubo-noticias-btn {
    opacity: 1;
    pointer-events: auto;
}
.portal-cubo-noticias-dots {
    display: flex;
    gap: .4rem;
}
.portal-cubo-noticias-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    border: 1px solid var(--c-border);
    background: transparent;
    padding: 0;
    cursor: pointer;
    transition: background .2s, transform .15s;
}
.portal-cubo-noticias-dot.is-active {
    background: var(--c-primary, #003c71);
    border-color: var(--c-primary, #003c71);
    transform: scale(1.2);
}
.portal-cubo-noticias-dot:hover {
    background: var(--c-primary, #003c71);
    opacity: .7;
}

/* ── KPI ─────────────────────────────────────────────────────────── */
.portal-kpi {
    display: grid;
    gap: 1rem;
    margin: 1.5rem 0;
}
.portal-kpi--cols-1 { grid-template-columns: 1fr; }
.portal-kpi--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.portal-kpi--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.portal-kpi--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Mobile: siempre apilados verticales — los KPIs requieren espacio
   para que el número grande no se corte. Usamos 768px (tablet) en vez
   del xs estándar porque a partir de tablet ya conviene una sola columna
   por card para que los números clamp() no se compriman. */
@media (max-width: 768px) {
    .portal-kpi { grid-template-columns: 1fr !important; }
}
/* En mobile chico ajustamos el padding lateral de las cards para que
   números largos cargados por el operador tengan más ancho disponible. */
@media (max-width: 575.98px) {
    .portal-kpi-card { padding: 1.25rem 1rem; }
    .portal-kpi--minimalista .portal-kpi-card { padding-left: .85rem; }
}

.portal-kpi-card {
    padding: 1.5rem 1.25rem;
    border-radius: 12px;
    text-align: left;
    display: flex;
    flex-direction: column;
    gap: .4rem;
    min-height: 0;
    box-shadow: 0 .35rem 1rem rgba(0, 0, 0, .08);
}

/* Variante "tarjeta": fondo color institucional + número en texto claro. */
.portal-kpi--tarjeta .portal-kpi-card {
    color: var(--c-text-light);
}
.portal-kpi--tarjeta .portal-kpi-card--primary       { background: var(--c-primary); }
.portal-kpi--tarjeta .portal-kpi-card--primary-light { background: var(--c-primary-light); }
.portal-kpi--tarjeta .portal-kpi-card--primary-dark  { background: var(--c-primary-dark); }
.portal-kpi--tarjeta .portal-kpi-card--accent        { background: var(--c-accent); }
.portal-kpi--tarjeta .portal-kpi-card--accent-light  { background: var(--c-accent2); }
.portal-kpi--tarjeta .portal-kpi-card--accent-dark   { background: var(--c-accent-dark); }
.portal-kpi--tarjeta .portal-kpi-card--bg {
    background: var(--c-bg-card);
    color: var(--c-text);
    border: 1px solid var(--c-border);
}
.portal-kpi--tarjeta .portal-kpi-card--text {
    background: var(--c-text);
    color: var(--c-bg);
}

/* Variante "minimalista": fondo blanco limpio del tenant, borde lateral
   en el color elegido, número en el mismo color. Antes era
   `background: transparent` — pisaba el fondo de la página y la
   tarjeta no destacaba; ahora levanta sobre el fondo con su propia
   sombra (ver `.portal-kpi-card` base) + blanco --c-bg-card. */
.portal-kpi--minimalista .portal-kpi-card {
    background: var(--c-bg-card);
    border-left: 4px solid var(--c-primary);
    padding-left: 1rem;
}
.portal-kpi--minimalista .portal-kpi-card--primary       { border-left-color: var(--c-primary); }
.portal-kpi--minimalista .portal-kpi-card--primary-light { border-left-color: var(--c-primary-light); }
.portal-kpi--minimalista .portal-kpi-card--primary-dark  { border-left-color: var(--c-primary-dark); }
.portal-kpi--minimalista .portal-kpi-card--accent        { border-left-color: var(--c-accent); }
.portal-kpi--minimalista .portal-kpi-card--accent-light  { border-left-color: var(--c-accent2); }
.portal-kpi--minimalista .portal-kpi-card--accent-dark   { border-left-color: var(--c-accent-dark); }
.portal-kpi--minimalista .portal-kpi-card--primary       .portal-kpi-numero { color: var(--c-primary); }
.portal-kpi--minimalista .portal-kpi-card--primary-light .portal-kpi-numero { color: var(--c-primary-light); }
.portal-kpi--minimalista .portal-kpi-card--primary-dark  .portal-kpi-numero { color: var(--c-primary-dark); }
.portal-kpi--minimalista .portal-kpi-card--accent        .portal-kpi-numero { color: var(--c-accent); }
.portal-kpi--minimalista .portal-kpi-card--accent-light  .portal-kpi-numero { color: var(--c-accent2); }
.portal-kpi--minimalista .portal-kpi-card--accent-dark   .portal-kpi-numero { color: var(--c-accent-dark); }

.portal-kpi-valor {
    display: flex;
    align-items: baseline;
    gap: .25rem;
    line-height: 1;
    flex-wrap: wrap;
}
.portal-kpi-numero {
    font-size: clamp(2rem, 4.5vw, 3rem);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
    /* Valores numéricos largos cargados por el operador (montos con
       separadores, porcentajes, etc.) deben poder quebrar antes que
       desbordar la card en mobile. */
    overflow-wrap: anywhere;
    min-width: 0;
}
.portal-kpi-prefijo,
.portal-kpi-sufijo {
    font-size: clamp(1.1rem, 2vw, 1.4rem);
    font-weight: 600;
    opacity: .85;
}
.portal-kpi-etiqueta {
    font-size: .92rem;
    margin: 0;
    line-height: 1.3;
    opacity: .92;
    /* La etiqueta es texto libre del operador: permitir quiebre dentro
       de palabras largas (nombres técnicos, URLs, etc.). */
    overflow-wrap: anywhere;
    word-break: break-word;
}

.portal-kpi-tendencia {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .82rem;
    font-weight: 600;
    margin: 0;
    padding: .2rem .55rem;
    border-radius: 999px;
    align-self: flex-start;
    background: rgba(255, 255, 255, .15);
}
/* En variante minimalista el fondo de la pill viene de un tinte del
   color del card; en tarjeta hereda el blanco semitransparente. */
.portal-kpi--minimalista .portal-kpi-tendencia {
    background: color-mix(in srgb, currentColor 12%, transparent);
}
.portal-kpi-tendencia--positiva { color: #047857; }
.portal-kpi-tendencia--negativa { color: #b91c1c; }
.portal-kpi-tendencia--neutra   { color: var(--c-text-soft); }
/* Cuando estamos en tarjeta sobre fondo color, las flechas verdes/rojas
   no se leen — las pasamos a un blanco/beige según el caso. */
.portal-kpi--tarjeta .portal-kpi-tendencia--positiva { color: #d1fae5; }
.portal-kpi--tarjeta .portal-kpi-tendencia--negativa { color: #fecaca; }
.portal-kpi--tarjeta .portal-kpi-tendencia--neutra   { color: var(--c-text-light); }
.portal-kpi-tendencia i { font-size: 1em; }

/* ── Gráfico ─────────────────────────────────────────────────────── */
.portal-grafico {
    margin: 1.5rem 0;
    padding: 1.25rem 1.25rem 1rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 12px;
}
.portal-grafico-titulo {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--c-text-strong);
    margin: 0 0 1rem;
    line-height: 1.25;
    /* El titulo lo escribe el operador: puede pegar URLs o codigos sin
       espacios. En mobile (~390px) eso desborda la tarjeta; forzamos
       el corte en cualquier caracter cuando no hay opcion mejor. */
    overflow-wrap: anywhere;
}
.portal-grafico-scroll {
    /* Scroll horizontal cuando el chart tiene muchos puntos en X
       (series temporales largas, datasets con muchas categorías).
       JS setea `min-width` al canvas-wrap proporcional a la cantidad
       de filas — si el min-width supera el ancho disponible, se
       scrollea; sino, ocupa el 100%. */
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    /* Scrollbar más fino, alineado con el resto del portal. */
    scrollbar-width: thin;
    scrollbar-color: var(--c-border) transparent;
}
.portal-grafico-scroll::-webkit-scrollbar { height: 8px; }
.portal-grafico-scroll::-webkit-scrollbar-track { background: transparent; }
.portal-grafico-scroll::-webkit-scrollbar-thumb {
    background: var(--c-border);
    border-radius: 4px;
}
.portal-grafico-canvas-wrap {
    position: relative;
    /* Alto fijo para que el chart no colapse a 0 mientras Chart.js
       inicializa. Responsive: Chart.js redibuja al cambiar el viewport
       gracias a `responsive: true` + `maintainAspectRatio: false`. */
    height: clamp(260px, 38vw, 420px);
    /* Sin `width: 100%` explícito: si el JS setea min-width mayor que
       el scroll container, se respeta; si no setea nada, el div toma
       el ancho natural del scroll container (100% por block flow). */
}
/* Donut y torta usan menos alto + un piso menor — quedan demasiado
   estiradas a 420px en pantallas anchas. NO necesitan scroll: la
   torta es siempre 1 sola visual; JS deshabilita el min-width para
   estos tipos así no aparece scrollbar inútil. */
.portal-grafico--torta .portal-grafico-canvas-wrap,
.portal-grafico--donut .portal-grafico-canvas-wrap {
    height: clamp(240px, 30vw, 360px);
}

.portal-grafico-fuente {
    margin: .85rem 0 0;
    font-size: .78rem;
    color: var(--c-text-soft);
    line-height: 1.4;
    border-top: 1px dashed var(--c-border);
    padding-top: .6rem;
    /* La fuente suele ser una URL o cita; sin esto desborda en mobile. */
    overflow-wrap: anywhere;
}
.portal-grafico-fuente-label {
    font-weight: 600;
    color: var(--c-text-strong);
}
.portal-grafico-fuente-sep {
    margin: 0 .35rem;
    color: var(--c-border);
}
.portal-grafico-fuente-fecha {
    font-style: italic;
}

/* ── Barra de progreso / meta ───────────────────────────────────────
   Indicador único de avance hacia una meta. Dos variantes (lineal y
   segmentada), color institucional, porcentaje y valores opcionales.
*/
.portal-progreso {
    margin: 1.5rem 0;
    padding: 1.25rem 1.25rem 1.1rem;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border);
    border-radius: 12px;
}
.portal-progreso-header {
    margin: 0 0 .85rem;
}
.portal-progreso-titulo {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--c-text-strong);
    margin: 0;
    line-height: 1.3;
    /* Texto libre del operador: protegemos contra palabras/URLs sin
       espacios que de otra forma desbordarian el bloque en mobile. */
    overflow-wrap: anywhere;
}
.portal-progreso-descripcion {
    font-size: .88rem;
    color: var(--c-text-soft);
    margin: .25rem 0 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.portal-progreso-fila {
    display: flex;
    align-items: center;
    gap: 1rem;
}
.portal-progreso-barra {
    flex: 1;
    position: relative;
    overflow: hidden;
}
/* Variante "lineal": una sola barra con relleno gradiente. */
.portal-progreso--lineal .portal-progreso-barra {
    height: 14px;
    background: var(--c-bg-subtle);
    border-radius: 8px;
}
.portal-progreso-fill {
    display: block;
    height: 100%;
    /* `--portal-progreso-pct` lo declara el template como inline style
       en <section> (única forma de pasar un valor server-side a CSS).
       El default 0% cubre el caso de bloques sin datos. */
    width: var(--portal-progreso-pct, 0%);
    border-radius: 8px;
    transition: width .5s cubic-bezier(.4, 0, .2, 1);
    /* Color base institucional — se sobrescribe por variante de color. */
    background: linear-gradient(90deg, var(--c-primary), var(--c-primary-dark));
}
/* Variante "segmentada": 10 segmentos iguales con gap. */
.portal-progreso--segmentada .portal-progreso-barra {
    display: flex;
    gap: 4px;
}
.portal-progreso-segmento {
    flex: 1;
    height: 16px;
    background: var(--c-bg-subtle);
    border-radius: 3px;
    transition: background .3s ease;
}
.portal-progreso-segmento.is-fill {
    background: var(--c-primary);
}

/* Variante "circular": anillo radial pintado con conic-gradient.
   El relleno se controla con la misma CSS var `--portal-progreso-pct`
   que la lineal — el conic-gradient interpreta el % como ángulo,
   y un radial-mask recorta el interior para dejar el anillo.
   El número del % va en `.portal-progreso-anillo-centro`. */
.portal-progreso-fila--circular {
    justify-content: center;
}
.portal-progreso--circular .portal-progreso-anillo {
    /* `--anillo-color` se setea por variante de color más abajo.
       Por defecto, fondo gris sutil + relleno primary. */
    --anillo-color: var(--c-primary);
    --anillo-fondo: var(--c-bg-subtle);
    --anillo-grosor: 14px;
    width: 160px;
    height: 160px;
    /* Tope para contenedores muy angostos (columnas/sidebar del builder).
       Mantenemos cuadrado con aspect-ratio para no romper el conic. */
    max-width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 50%;
    background: conic-gradient(
        var(--anillo-color) 0 var(--portal-progreso-pct, 0%),
        var(--anillo-fondo) var(--portal-progreso-pct, 0%) 100%
    );
    position: relative;
    /* Transición del % al entrar en pantalla, mismo timing que la lineal. */
    transition: background .6s cubic-bezier(.4, 0, .2, 1);
}
.portal-progreso--circular .portal-progreso-anillo::before {
    /* "Calado" del centro — define el grosor visible del anillo. */
    content: '';
    position: absolute;
    inset: var(--anillo-grosor);
    border-radius: 50%;
    background: var(--c-bg-card);
}
.portal-progreso-anillo-centro {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.portal-progreso-anillo-pct {
    position: relative;
    font-size: 1.85rem;
    font-weight: 800;
    color: var(--c-text-strong);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

/* Paleta por color del operador — variante lineal usa gradient, la
   segmentada usa color sólido, la circular sigue la variable
   `--anillo-color`. */
.portal-progreso--primary       .portal-progreso-fill { background: linear-gradient(90deg, var(--c-primary), var(--c-primary-dark)); }
.portal-progreso--primary-light .portal-progreso-fill { background: linear-gradient(90deg, var(--c-primary-light), var(--c-primary)); }
.portal-progreso--primary-dark  .portal-progreso-fill { background: linear-gradient(90deg, var(--c-primary-dark), var(--c-primary)); }
.portal-progreso--accent        .portal-progreso-fill { background: linear-gradient(90deg, var(--c-accent), var(--c-accent-dark)); }
.portal-progreso--accent-light  .portal-progreso-fill { background: linear-gradient(90deg, var(--c-accent2), var(--c-accent)); }
.portal-progreso--accent-dark   .portal-progreso-fill { background: linear-gradient(90deg, var(--c-accent-dark), var(--c-accent)); }

.portal-progreso--primary       .portal-progreso-segmento.is-fill { background: var(--c-primary); }
.portal-progreso--primary-light .portal-progreso-segmento.is-fill { background: var(--c-primary-light); }
.portal-progreso--primary-dark  .portal-progreso-segmento.is-fill { background: var(--c-primary-dark); }
.portal-progreso--accent        .portal-progreso-segmento.is-fill { background: var(--c-accent); }
.portal-progreso--accent-light  .portal-progreso-segmento.is-fill { background: var(--c-accent2); }
.portal-progreso--accent-dark   .portal-progreso-segmento.is-fill { background: var(--c-accent-dark); }

.portal-progreso--primary.portal-progreso--circular       .portal-progreso-anillo { --anillo-color: var(--c-primary); }
.portal-progreso--primary-light.portal-progreso--circular .portal-progreso-anillo { --anillo-color: var(--c-primary-light); }
.portal-progreso--primary-dark.portal-progreso--circular  .portal-progreso-anillo { --anillo-color: var(--c-primary-dark); }
.portal-progreso--accent.portal-progreso--circular        .portal-progreso-anillo { --anillo-color: var(--c-accent); }
.portal-progreso--accent-light.portal-progreso--circular  .portal-progreso-anillo { --anillo-color: var(--c-accent2); }
.portal-progreso--accent-dark.portal-progreso--circular   .portal-progreso-anillo { --anillo-color: var(--c-accent-dark); }

.portal-progreso-pct {
    flex-shrink: 0;
    font-size: 1.4rem;
    font-weight: 800;
    color: var(--c-text-strong);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    min-width: 3.5em;
    text-align: right;
}
.portal-progreso-valores {
    margin: .65rem 0 0;
    font-size: .85rem;
    color: var(--c-text-soft);
    font-variant-numeric: tabular-nums;
    display: flex;
    gap: .35rem;
    align-items: baseline;
    /* Prefijo/sufijo son libres ('$ARS ', ' millones', etc.) — si los
       tres span no entran en una linea, dejamos que se envuelvan en
       vez de desbordar el ancho del bloque. */
    flex-wrap: wrap;
}
.portal-progreso-actual {
    color: var(--c-text-strong);
    font-weight: 600;
}
.portal-progreso-sep {
    color: var(--c-border);
}
.portal-progreso-meta {
    /* dejamos el valor meta más tenue para que el actual destaque */
}

@media (prefers-reduced-motion: reduce) {
    .portal-progreso-fill,
    .portal-progreso-segmento {
        transition: none;
    }
}

/* Ajustes para mobile chico (xs de Bootstrap, ~390-430px de preview).
   Recortamos padding lateral, gap de fila y tamano del anillo para
   que el bloque respire en columnas estrechas y se vea proporcionado. */
@media (max-width: 575.98px) {
    .portal-progreso {
        padding: 1rem .9rem .9rem;
    }
    .portal-progreso-fila {
        gap: .65rem;
    }
    .portal-progreso-pct {
        font-size: 1.2rem;
        min-width: 3em;
    }
    .portal-progreso--circular .portal-progreso-anillo {
        width: 140px;
        height: 140px;
        --anillo-grosor: 12px;
    }
    .portal-progreso-anillo-pct {
        font-size: 1.55rem;
    }
}

/* ── Tabla de datos ─────────────────────────────────────────────────
   Tabla HTML responsive, con scroll horizontal en mobile y filas
   alternadas opcionales. El sorting client-side suma `aria-sort` y
   un glifo (↕/↑/↓) al lado del encabezado clickeado.
*/
.portal-tabla {
    margin: 1.5rem 0;
}
.portal-tabla-caption {
    text-align: left;
    margin: 0 0 .85rem;
}
.portal-tabla-titulo {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--c-text-strong);
    margin: 0;
    line-height: 1.3;
    /* Texto libre del operador, fuera del wrap con scroll. Si pega
       un URL largo o palabra sin espacios, rompemos por carácter
       antes que desbordar el ancho del bloque en mobile. */
    overflow-wrap: anywhere;
}
.portal-tabla-descripcion {
    font-size: .9rem;
    color: var(--c-text-soft);
    margin: .25rem 0 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.portal-tabla-wrap {
    overflow-x: auto;
    border: 1px solid var(--c-border);
    border-radius: 10px;
    background: var(--c-bg-card);
}
.portal-tabla-tabla {
    width: 100%;
    border-collapse: collapse;
    font-size: .92rem;
}
.portal-tabla-tabla thead th {
    text-align: left;
    padding: .75rem 1rem;
    /* `--portal-tabla-header-bg/texto` se inyectan inline en el
       <figure> del bloque, mapeados desde la paleta institucional
       del operador. Si no están definidos (renders viejos antes de
       que existieran estos campos), caemos al patrón anterior:
       fondo institucional al 10% + texto strong. */
    background: var(--portal-tabla-header-bg,
                color-mix(in srgb, var(--c-primary) 10%, var(--c-bg-card)));
    color: var(--portal-tabla-header-texto, var(--c-text-strong));
    font-weight: 700;
    border-bottom: 2px solid var(--c-border);
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 1;
}
.portal-tabla-tabla tbody td {
    padding: .65rem 1rem;
    border-bottom: 1px solid var(--c-border);
    color: var(--c-text);
    vertical-align: top;
    /* Los datos pueden traer URLs / IDs / hashes sin espacios. Si no
       quebramos, una sola celda dilata la columna a un ancho
       enorme y el scroll horizontal del wrap se hace inmanejable
       en mobile. */
    overflow-wrap: anywhere;
}
.portal-tabla-tabla tbody tr:last-child td {
    border-bottom: 0;
}
/* Variante rayada — alterna fondo en filas pares para que ojos
   crucen N columnas sin perder la línea. */
.portal-tabla--rayada .portal-tabla-tabla tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--c-primary) 4%, transparent);
}
.portal-tabla--clasica .portal-tabla-tabla tbody tr:nth-child(even) td {
    /* En la clásica no rayamos — solo líneas separadoras. */
    background: transparent;
}

.portal-tabla-vacio td {
    text-align: center;
    color: var(--c-text-soft);
    font-style: italic;
    padding: 1.5rem !important;
}

/* Sorting: glifo + cursor + hover en encabezados clickables.
   El hover oscurece levemente el bg actual sin importar qué color
   eligió el operador, mezclando 10% de negro sobre el header_bg.
   El glifo hereda `currentColor` del <th> para que use el mismo
   color de texto elegido por el operador. */
.portal-tabla-th-sortable {
    cursor: pointer;
    user-select: none;
    transition: background .12s ease;
}
.portal-tabla-th-sortable:hover {
    background:
        linear-gradient(rgba(0, 0, 0, .08), rgba(0, 0, 0, .08)),
        var(--portal-tabla-header-bg,
            color-mix(in srgb, var(--c-primary) 10%, var(--c-bg-card)));
}
.portal-tabla-th-glifo {
    margin-left: .4rem;
    color: currentColor;
    opacity: .55;
    font-size: .85em;
    font-weight: 400;
}
.portal-tabla-th-sortable[aria-sort="ascending"] .portal-tabla-th-glifo,
.portal-tabla-th-sortable[aria-sort="descending"] .portal-tabla-th-glifo {
    opacity: 1;
    font-weight: 700;
}

.portal-tabla-fuente {
    margin: .85rem 0 0;
    font-size: .78rem;
    color: var(--c-text-soft);
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.portal-tabla-fuente-label {
    font-weight: 600;
    color: var(--c-text-strong);
}
.portal-tabla-fuente-sep {
    margin: 0 .35rem;
    color: var(--c-border);
}
.portal-tabla-fuente-fecha {
    font-style: italic;
}

/* Controles de paginación de la tabla — aparecen solo cuando el JS
   detecta >20 filas. Layout simple: < página X de N >, centrado. */
.portal-tabla-paginacion {
    margin-top: .85rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    flex-wrap: wrap;
}
.portal-tabla-pag-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    background: transparent;
    border: 1px solid var(--c-border);
    border-radius: 6px;
    color: var(--c-text);
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}
.portal-tabla-pag-btn:hover:not(:disabled) {
    background: var(--c-primary);
    color: var(--c-text-light, #fff);
    border-color: var(--c-primary);
}
.portal-tabla-pag-btn:disabled {
    opacity: .4;
    cursor: not-allowed;
}
.portal-tabla-pag-info {
    font-size: .85rem;
    color: var(--c-text-soft);
}
.portal-tabla-pag-info span {
    font-weight: 600;
    color: var(--c-text-strong);
}

.portal-tabla-acciones {
    margin-top: .85rem;
    display: flex;
    justify-content: flex-end;
}
.portal-tabla-descargar {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .45rem .85rem;
    background: transparent;
    border: 1px solid var(--c-border);
    border-radius: 6px;
    color: var(--c-text);
    font-size: .85rem;
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}
.portal-tabla-descargar:hover {
    background: color-mix(in srgb, var(--c-primary) 10%, transparent);
    border-color: var(--c-primary);
    color: var(--c-primary-dark);
}

/* Mobile (≤575.98px, Bootstrap xs): compactamos padding lateral de
   th/td y tipografía del caption para que la tabla aproveche el
   poco ancho disponible. El `white-space: nowrap` del header se
   mantiene a propósito — el wrap ya tiene overflow-x: auto, así
   que preferimos scroll horizontal antes que headers en 2 líneas. */
@media (max-width: 575.98px) {
    .portal-tabla-titulo {
        font-size: 1rem;
    }
    .portal-tabla-descripcion {
        font-size: .85rem;
    }
    .portal-tabla-tabla {
        font-size: .88rem;
    }
    .portal-tabla-tabla thead th {
        padding: .6rem .65rem;
    }
    .portal-tabla-tabla tbody td {
        padding: .55rem .65rem;
    }
    .portal-tabla-acciones {
        justify-content: stretch;
    }
    .portal-tabla-descargar {
        width: 100%;
        justify-content: center;
    }
}

/* ══════════════════════════════════════════════════════════════════
   ANIMACIONES DE ENTRADA (panel transversal "Animación")
   Aplica a bloques del módulo Datos y visualización (KPI, gráfico,
   barra de progreso, tabla). El template del bloque inyecta en su
   root element:
     - clase   `portal-anim portal-anim--<tipo>`
     - atributo `data-portal-anim="<tipo>"`
     - inline  `style="--portal-anim-duration: 600ms; --portal-anim-delay: 0ms;"`

   El estado por defecto (sin clase `is-in`) es "antes de animar":
   opacidad 0 + transform específico al tipo. Cuando entra al viewport
   el JS agrega `is-in` y la transition lleva el elemento al estado
   final (opacidad 1, transform none).

   Accesibilidad: si el visitante tiene `prefers-reduced-motion: reduce`
   activo en su sistema (Windows: "Mostrar animaciones", macOS:
   "Reducir movimiento"), las animaciones se anulan — el elemento
   aparece directamente en estado final sin transición.
   ══════════════════════════════════════════════════════════════════ */

.portal-anim {
    /* Defaults — el template suele sobrescribirlos via inline style. */
    --portal-anim-duration: 600ms;
    --portal-anim-delay: 0ms;
    /* La transition aplica solo a opacity y transform; otros estilos
       del bloque (color del header de tabla, etc.) no se animan. */
    transition:
        opacity var(--portal-anim-duration) ease-out var(--portal-anim-delay),
        transform var(--portal-anim-duration) ease-out var(--portal-anim-delay);
    /* will-change le avisa al navegador que estos van a cambiar —
       permite optimización GPU. Lo limitamos a opacity y transform
       (los únicos que efectivamente animamos). */
    will-change: opacity, transform;
}

/* Estados "antes de animar" por tipo. */
.portal-anim--fade {
    opacity: 0;
}
.portal-anim--slide-up {
    opacity: 0;
    transform: translateY(24px);
}
.portal-anim--scale {
    opacity: 0;
    transform: scale(0.92);
}

/* Estado "después de animar" — el JS lo agrega cuando el elemento
   entra al viewport. */
.portal-anim.is-in {
    opacity: 1;
    transform: none;
}

/* Respeto a la preferencia del sistema: si el usuario pidió "reducir
   movimiento", anulamos todo. El elemento se muestra inmediatamente
   en estado final, sin transición ni transform inicial. */
@media (prefers-reduced-motion: reduce) {
    .portal-anim,
    .portal-anim--fade,
    .portal-anim--slide-up,
    .portal-anim--scale {
        opacity: 1;
        transform: none;
        transition: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   Línea de tiempo (Estructura y navegación)
   ═══════════════════════════════════════════════════════════════════
   Bloque con eventos cronológicos. Tres variantes que comparten DOM
   pero cambian disposición vía clase modifier:

     .portal-timeline--vertical-alternada  → eje central, hitos a izq/der
     .portal-timeline--vertical-compacta   → todo a la derecha del eje
     .portal-timeline--horizontal          → eje horizontal, scroll lateral

   El color del eje viene de `--c-primary` por default; el operador
   puede pisar via `.portal-timeline--color-<slug>` (paleta).

   Estados semánticos por hito (.portal-timeline-hito--estado-<X>):
     - default:   dot color institucional
     - cumplido:  marcador verde + badge "Cumplido"
     - en_curso:  marcador amarillo con pulse + badge "En curso"
     - futuro:    marcador hueco (border only) + badge "Próximo"
*/

.portal-timeline {
    --timeline-color: var(--c-primary);
    --timeline-color-cumplido: #10b981;
    --timeline-color-en-curso: #f59e0b;
    --timeline-marcador-size: 36px;
    --timeline-eje-grosor: 3px;
    --timeline-eje-color: color-mix(in srgb,
        var(--timeline-color) 28%, var(--c-bg-subtle));
    /* Padding para que el eje y los marcadores no toquen el borde
       del bloque/wrapper. */
    padding: 1rem 0;
}
.portal-timeline-titulo {
    color: var(--c-text-brand, var(--c-primary));
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 1.5rem;
}

/* Pisa de la paleta por slug. */
.portal-timeline--color-primary       { --timeline-color: var(--c-primary); }
.portal-timeline--color-primary-light { --timeline-color: var(--c-primary-light); }
.portal-timeline--color-primary-dark  { --timeline-color: var(--c-primary-dark); }
.portal-timeline--color-accent        { --timeline-color: var(--c-accent); }
.portal-timeline--color-accent-light  { --timeline-color: var(--c-accent2); }
.portal-timeline--color-accent-dark   { --timeline-color: var(--c-accent-dark); }

.portal-timeline-eje {
    list-style: none;
    margin: 0;
    padding: 0;
    position: relative;
}
.portal-timeline-hito {
    position: relative;
}

/* Contenedor del contenido textual del hito. Es el item de la grilla
   que ocupa la columna 1fr en las 3 variantes; sin `min-width: 0` una
   palabra/URL larga del operador empuja la columna mas alla del
   viewport y rompe el ancho en mobile. `overflow-wrap: anywhere`
   complementa permitiendo cortar palabras larguisimas. */
.portal-timeline-contenido {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Marcador en el eje (circulo con icono adentro o dot). Compartido
   por las 3 variantes. La posición la define cada variante. */
.portal-timeline-marcador {
    width: var(--timeline-marcador-size);
    height: var(--timeline-marcador-size);
    border-radius: 50%;
    background: var(--timeline-color);
    color: var(--c-text-light);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    box-shadow: 0 0 0 4px var(--c-bg-card),
                0 .2rem .55rem rgba(0, 0, 0, .12);
    flex: 0 0 auto;
    z-index: 2;
}
.portal-timeline-marcador-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--c-text-light);
}
.portal-timeline-hito-titulo {
    color: var(--c-text-strong, var(--c-text));
    font-size: 1.15rem;
    font-weight: 700;
    margin: 0 0 .25rem;
    line-height: 1.3;
    /* Texto del operador: cortar palabras muy largas (URLs en el
       titulo, terminos tecnicos sin espacios) para evitar desbordes
       en mobile. */
    overflow-wrap: anywhere;
}
.portal-timeline-hito-link {
    color: inherit;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    border-bottom: 1px solid transparent;
    transition: color .15s ease, border-color .15s ease;
}
.portal-timeline-hito-link:hover,
.portal-timeline-hito-link:focus-visible {
    color: var(--timeline-color);
    border-bottom-color: currentColor;
}
.portal-timeline-fecha {
    display: inline-block;
    font-size: .85rem;
    font-weight: 600;
    color: var(--timeline-color);
    text-transform: uppercase;
    letter-spacing: .03em;
    margin-bottom: .35rem;
}
.portal-timeline-hito-descripcion {
    color: var(--c-text);
    font-size: .95rem;
    line-height: 1.55;
    margin: 0;
    overflow-wrap: anywhere;
}

/* Badge de estado al pie del contenido. */
.portal-timeline-badge {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    margin-top: .65rem;
    padding: .2rem .55rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 600;
    line-height: 1;
}
.portal-timeline-badge--cumplido {
    background: color-mix(in srgb, var(--timeline-color-cumplido) 18%, transparent);
    color: var(--timeline-color-cumplido);
}
.portal-timeline-badge--en-curso {
    background: color-mix(in srgb, var(--timeline-color-en-curso) 18%, transparent);
    color: var(--timeline-color-en-curso);
}
.portal-timeline-badge--futuro {
    background: var(--c-bg-subtle);
    color: var(--c-text-soft, #57534e);
}

/* Estados por hito: pisan el color del marcador. */
.portal-timeline-hito--estado-cumplido .portal-timeline-marcador {
    background: var(--timeline-color-cumplido);
}
.portal-timeline-hito--estado-en_curso .portal-timeline-marcador {
    background: var(--timeline-color-en-curso);
    animation: portal-timeline-pulse 2s ease-in-out infinite;
}
.portal-timeline-hito--estado-futuro .portal-timeline-marcador {
    background: var(--c-bg-card);
    border: 2px solid var(--timeline-eje-color);
    color: var(--timeline-eje-color);
}
.portal-timeline-hito--estado-futuro .portal-timeline-marcador-dot {
    background: var(--timeline-eje-color);
}
@keyframes portal-timeline-pulse {
    0%, 100% { box-shadow: 0 0 0 4px var(--c-bg-card),
                            0 .2rem .55rem rgba(0, 0, 0, .12),
                            0 0 0 0 color-mix(in srgb, var(--timeline-color-en-curso) 45%, transparent); }
    50%      { box-shadow: 0 0 0 4px var(--c-bg-card),
                            0 .2rem .55rem rgba(0, 0, 0, .12),
                            0 0 0 10px transparent; }
}
@media (prefers-reduced-motion: reduce) {
    .portal-timeline-hito--estado-en_curso .portal-timeline-marcador {
        animation: none;
    }
}


/* ── Variante "vertical alternada" (default) ───────────────────────
   Eje central, hitos a izquierda y derecha alternando. En mobile
   colapsa: eje a la izquierda, todos los hitos a la derecha. */
.portal-timeline--vertical-alternada .portal-timeline-eje::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: var(--timeline-eje-grosor);
    background: var(--timeline-eje-color);
    border-radius: 999px;
}
.portal-timeline--vertical-alternada .portal-timeline-hito {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: start;
    gap: 1.25rem;
    margin-bottom: 2.5rem;
}
.portal-timeline--vertical-alternada .portal-timeline-hito:last-child {
    margin-bottom: 0;
}
.portal-timeline--vertical-alternada .portal-timeline-marcador {
    grid-column: 2;
    grid-row: 1;
    margin-top: .15rem;
}
.portal-timeline--vertical-alternada .portal-timeline-contenido {
    grid-row: 1;
}
/* Impares → izquierda. Pares → derecha. */
.portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-contenido {
    grid-column: 1;
    text-align: right;
    padding-right: .5rem;
}
.portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(even) .portal-timeline-contenido {
    grid-column: 3;
    text-align: left;
    padding-left: .5rem;
}
.portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-badge {
    margin-left: auto;
}


/* ── Variante "vertical compacta" ──────────────────────────────────
   Eje a la izquierda, contenido a la derecha. Misma estructura para
   todos los hitos. */
.portal-timeline--vertical-compacta .portal-timeline-eje::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--timeline-marcador-size) / 2);
    width: var(--timeline-eje-grosor);
    transform: translateX(-50%);
    background: var(--timeline-eje-color);
    border-radius: 999px;
}
.portal-timeline--vertical-compacta .portal-timeline-hito {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    gap: 1.25rem;
    margin-bottom: 1.75rem;
}
.portal-timeline--vertical-compacta .portal-timeline-hito:last-child {
    margin-bottom: 0;
}
.portal-timeline--vertical-compacta .portal-timeline-marcador {
    margin-top: .15rem;
}
.portal-timeline--vertical-compacta .portal-timeline-contenido {
    padding-top: .15rem;
}


/* ── Variante "horizontal scrollable" ──────────────────────────────
   Eje horizontal con scroll lateral. Cada hito es una tarjeta con
   el marcador arriba (sobre el eje) y el contenido debajo. */
.portal-timeline--horizontal {
    position: relative;
}
.portal-timeline--horizontal .portal-timeline-eje {
    display: flex;
    overflow-x: auto;
    overflow-y: visible;
    padding: 0 1rem .5rem;
    gap: 2rem;
    /* Estética del scrollbar — visible pero discreto. */
    scrollbar-width: thin;
    scrollbar-color: var(--timeline-eje-color) transparent;
    /* Snap suave para que cada hito quede "centrado" al swipe. */
    scroll-snap-type: x mandatory;
}
.portal-timeline--horizontal .portal-timeline-eje::-webkit-scrollbar {
    height: 6px;
}
.portal-timeline--horizontal .portal-timeline-eje::-webkit-scrollbar-thumb {
    background: var(--timeline-eje-color);
    border-radius: 999px;
}
.portal-timeline--horizontal .portal-timeline-eje::before {
    content: "";
    position: absolute;
    left: 1rem;
    right: 1rem;
    top: calc(var(--timeline-marcador-size) / 2);
    height: var(--timeline-eje-grosor);
    background: var(--timeline-eje-color);
    border-radius: 999px;
    z-index: 0;
}
.portal-timeline--horizontal .portal-timeline-hito {
    flex: 0 0 260px;
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: .85rem;
    position: relative;
    z-index: 1;
}
.portal-timeline--horizontal .portal-timeline-marcador {
    margin-bottom: .25rem;
}
.portal-timeline--horizontal .portal-timeline-contenido {
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    padding: .85rem 1rem;
    width: 100%;
    box-sizing: border-box;
    box-shadow: 0 .25rem .65rem rgba(0, 0, 0, .04);
}


/* ── Variante "horizontal alternada" ───────────────────────────────
   Eje horizontal con los hitos alternando ARRIBA y ABAJO del eje.
   Más vistosa que la horizontal simple cuando hay varios hitos
   seguidos. Cada hito ocupa el alto completo del bloque, con el
   marcador centrado sobre el eje y la tarjeta posicionada arriba
   (impares) o abajo (pares) usando absolute.

   IMPORTANTE: el contenedor del scroll horizontal NO puede tener
   `overflow-x: auto` con `overflow-y: visible` — el spec del CSS
   fuerza `auto` también al eje vertical, recortando las tarjetas
   que se extienden arriba/abajo. Por eso el alto del contenedor
   tiene que ser SUFICIENTE para que el contenido absolute entre
   completo. Con --timeline-alt-hito-alto: 400px y tarjetas de
   ~140px de alto, queda margen cómodo. */
.portal-timeline--horizontal-alternada {
    --timeline-alt-hito-alto: 400px;
    --timeline-alt-card-max: 140px;
}
.portal-timeline--horizontal-alternada .portal-timeline-eje {
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;  /* explícito; mezclar con visible da auto */
    padding: 0 1rem;
    gap: 2rem;
    height: var(--timeline-alt-hito-alto);
    align-items: center;
    scrollbar-width: thin;
    scrollbar-color: var(--timeline-eje-color) transparent;
    scroll-snap-type: x mandatory;
}
.portal-timeline--horizontal-alternada .portal-timeline-eje::-webkit-scrollbar {
    height: 6px;
}
.portal-timeline--horizontal-alternada .portal-timeline-eje::-webkit-scrollbar-thumb {
    background: var(--timeline-eje-color);
    border-radius: 999px;
}
/* Eje horizontal al medio del bloque. */
.portal-timeline--horizontal-alternada .portal-timeline-eje::before {
    content: "";
    position: absolute;
    left: 1rem;
    right: 1rem;
    top: 50%;
    height: var(--timeline-eje-grosor);
    background: var(--timeline-eje-color);
    border-radius: 999px;
    transform: translateY(-50%);
    z-index: 0;
}
.portal-timeline--horizontal-alternada .portal-timeline-hito {
    flex: 0 0 240px;
    scroll-snap-align: start;
    position: relative;
    height: 100%;
    display: block;
    z-index: 1;
}
/* Marcador centrado vertical y horizontalmente sobre el eje. */
.portal-timeline--horizontal-alternada .portal-timeline-marcador {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}
/* La tarjeta (contenido) flota arriba o abajo del marcador con una
   pequeña separación. Conectada por una línea vertical fina. El
   `max-height` evita que un título larguísimo + descripción inline
   haga crecer la card más allá del espacio disponible y la recorte
   el overflow del scroll horizontal. */
.portal-timeline--horizontal-alternada .portal-timeline-contenido {
    position: absolute;
    left: 0;
    right: 0;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-radius: 10px;
    padding: .75rem .9rem;
    box-sizing: border-box;
    box-shadow: 0 .25rem .65rem rgba(0, 0, 0, .04);
    text-align: center;
    max-height: var(--timeline-alt-card-max);
    overflow: hidden;
}
.portal-timeline--horizontal-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-contenido {
    /* Impar: tarjeta arriba del eje. Distancia al borde superior
       del contenedor = (alto/2) - 30 - max-height = 200 - 30 - 140
       = 30px. La card cabe holgada con 30px de margen. */
    bottom: calc(50% + 30px);
}
.portal-timeline--horizontal-alternada .portal-timeline-hito:nth-child(even) .portal-timeline-contenido {
    /* Par: tarjeta abajo del eje. Mismo cálculo simétrico. */
    top: calc(50% + 30px);
}
/* Línea conectora entre la tarjeta y el marcador. */
.portal-timeline--horizontal-alternada .portal-timeline-hito::after {
    content: "";
    position: absolute;
    left: 50%;
    width: var(--timeline-eje-grosor);
    background: var(--timeline-eje-color);
    transform: translateX(-50%);
    z-index: 0;
}
.portal-timeline--horizontal-alternada .portal-timeline-hito:nth-child(odd)::after {
    bottom: 50%;
    top: calc(50% - 12px - var(--timeline-marcador-size) / 2);
}
.portal-timeline--horizontal-alternada .portal-timeline-hito:nth-child(even)::after {
    top: 50%;
    bottom: calc(50% - 12px - var(--timeline-marcador-size) / 2);
}
/* Hint cuando es clickeable: centrado debajo del título. */
.portal-timeline--horizontal-alternada .portal-timeline-hint {
    justify-content: center;
}


/* ── Responsive (mobile ≤ 640px) ────────────────────────────────
   En pantallas angostas:
     - tamaños generales más pequeños (marcador, título);
     - vertical-alternada colapsa a layout tipo compacta;
     - LAS DOS variantes horizontales colapsan a vertical-compacta —
       hacer scroll horizontal con el dedo en mobile es incómodo y
       las tarjetas absolute no entran en el ancho del viewport. La
       línea de tiempo queda apilada verticalmente con eje a la izq. */
@media (max-width: 640px) {

    /* ── Ajustes generales para mobile ── */
    .portal-timeline {
        --timeline-marcador-size: 28px;
        padding: .5rem 0;
    }
    .portal-timeline-titulo {
        font-size: 1.3rem;
        margin-bottom: 1.1rem;
    }
    .portal-timeline-marcador {
        font-size: .85rem;
    }
    .portal-timeline-hito-titulo,
    .portal-timeline-detalle-titulo {
        font-size: 1.05rem;
    }
    .portal-timeline-fecha,
    .portal-timeline-detalle-fecha {
        font-size: .78rem;
    }

    /* ── vertical-alternada colapsa a layout compacta ── */
    .portal-timeline--vertical-alternada .portal-timeline-eje::before {
        left: calc(var(--timeline-marcador-size) / 2);
        transform: translateX(-50%);
    }
    .portal-timeline--vertical-alternada .portal-timeline-hito {
        grid-template-columns: auto 1fr;
        gap: 1rem;
        margin-bottom: 1.75rem;
    }
    .portal-timeline--vertical-alternada .portal-timeline-marcador {
        grid-column: 1;
    }
    .portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-contenido,
    .portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(even) .portal-timeline-contenido {
        grid-column: 2;
        text-align: left;
        padding: 0;
    }
    .portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-badge {
        margin-left: 0;
    }
    .portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-hint {
        align-self: flex-start;
    }

    /* ── horizontales colapsan a layout vertical-compacta ──
       Override completo: eje vertical a la izq, hitos apilados
       verticalmente con marcador estático + contenido al costado. */
    .portal-timeline--horizontal,
    .portal-timeline--horizontal-alternada {
        --timeline-alt-hito-alto: auto;
    }
    .portal-timeline--horizontal .portal-timeline-eje,
    .portal-timeline--horizontal-alternada .portal-timeline-eje {
        display: block;
        height: auto;
        overflow: visible;
        padding: 0;
        gap: 0;
        scroll-snap-type: none;
        position: relative;
    }
    /* Reset del eje horizontal que dibujábamos al medio. */
    .portal-timeline--horizontal .portal-timeline-eje::before,
    .portal-timeline--horizontal-alternada .portal-timeline-eje::before {
        top: 0;
        bottom: 0;
        left: calc(var(--timeline-marcador-size) / 2);
        right: auto;
        width: var(--timeline-eje-grosor);
        height: auto;
        transform: translateX(-50%);
    }
    /* Hitos como grid 2 columnas (marcador + contenido), apilados. */
    .portal-timeline--horizontal .portal-timeline-hito,
    .portal-timeline--horizontal-alternada .portal-timeline-hito {
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: start;
        gap: 1rem;
        margin-bottom: 1.5rem;
        flex: none;
        width: auto;
        height: auto;
        text-align: left;
        scroll-snap-align: none;
        position: relative;
    }
    /* Reset del marcador absolute. */
    .portal-timeline--horizontal-alternada .portal-timeline-marcador {
        position: static;
        transform: none;
        margin: 0;
    }
    /* Reset del contenido absolute (de horizontal-alternada) +
       quitar caja con borde/fondo (de horizontal) — en mobile el
       contenido va inline en la grilla del hito como en vertical-compacta. */
    .portal-timeline--horizontal .portal-timeline-contenido,
    .portal-timeline--horizontal-alternada .portal-timeline-contenido {
        position: static;
        background: transparent;
        border: none;
        padding: .15rem 0 0;
        box-shadow: none;
        max-height: none;
        overflow: visible;
        text-align: left;
        width: auto;
    }
    /* Quitar las líneas conectoras verticales de horizontal-alternada
       (las dibujábamos entre el eje y la tarjeta). En mobile no
       aplican porque el layout es vertical. */
    .portal-timeline--horizontal-alternada .portal-timeline-hito::after {
        display: none;
    }
    /* Hint centrado vuelve a izq (consistente con resto). */
    .portal-timeline--horizontal-alternada .portal-timeline-hint {
        justify-content: flex-start;
    }
}


/* ── Vacío ─────────────────────────────────────────────────────── */
.portal-timeline-vacio {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--c-text-soft, #57534e);
    background: var(--c-bg-subtle);
    border-radius: 10px;
    border: 1px dashed var(--c-border);
}
.portal-timeline-vacio i {
    font-size: 2rem;
    opacity: .5;
    margin-bottom: .5rem;
    display: block;
}
.portal-timeline-vacio p {
    margin: 0;
}


/* ── Modo "Detalle en modal/popover" ───────────────────────────────
   Cuando el operador elige modo modal o flotante, los hitos con
   contenido extra se hacen clickeables. El JS abre el modal/popover
   inyectando el detalle desde el `<template>` adyacente.

   Estilos:
     - Hint "Ver detalle" visible en cada hito clickeable.
     - Hover/focus elevación sutil.
     - Cursor pointer + outline visible para teclado.
*/
.portal-timeline-hito--clickeable {
    cursor: pointer;
    border-radius: 8px;
    transition: background .15s ease, transform .15s ease;
}
.portal-timeline-hito--clickeable:hover,
.portal-timeline-hito--clickeable:focus-visible {
    background: color-mix(in srgb, var(--timeline-color) 6%, transparent);
    outline: none;
}
.portal-timeline-hito--clickeable:focus-visible {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--timeline-color) 30%, transparent);
}
.portal-timeline-hint {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    margin-top: .35rem;
    font-size: .82rem;
    color: var(--timeline-color);
    font-weight: 600;
    opacity: .85;
}
/* En la variante alternada con hito impar (contenido a la derecha del
   eje, alineado a la derecha), el hint también se alinea a la
   derecha. */
.portal-timeline--vertical-alternada .portal-timeline-hito:nth-child(odd) .portal-timeline-hint {
    align-self: flex-end;
}

/* ── Modal ─────────────────────────────────────────────────────── */
.portal-timeline-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.portal-timeline-modal[hidden] {
    /* `hidden` desktop ya pone display:none, pero algunos UAs lo
       sobrescriben si el elemento tiene display:flex declarado. */
    display: none;
}
.portal-timeline-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, .55);
    animation: portal-timeline-modal-fade-in .2s ease;
}
.portal-timeline-modal-dialog {
    position: relative;
    background: var(--c-bg-card);
    border-radius: 12px;
    max-width: 600px;
    width: 100%;
    max-height: 85vh;
    overflow: auto;
    box-shadow: 0 1rem 3rem rgba(0, 0, 0, .25);
    animation: portal-timeline-modal-pop-in .2s cubic-bezier(.4, 0, .2, 1);
}
.portal-timeline-modal-body {
    padding: 1.5rem 1.5rem 1.25rem;
}
.portal-timeline-modal-cerrar {
    position: absolute;
    top: .65rem;
    right: .65rem;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    background: var(--c-bg-subtle);
    color: var(--c-text);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background .15s ease;
    z-index: 1;
}
.portal-timeline-modal-cerrar:hover,
.portal-timeline-modal-cerrar:focus-visible {
    background: var(--c-border);
    outline: none;
}
@keyframes portal-timeline-modal-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes portal-timeline-modal-pop-in {
    from { opacity: 0; transform: translateY(8px) scale(.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* ── Popover flotante ──────────────────────────────────────────── */
.portal-timeline-popover {
    /* `position: fixed` y no absolute para sortear el problema de
       ancestors con `transform` (el wrapper del bloque puede tener
       el transform de la animación de entrada — eso crea un nuevo
       contexto de posicionamiento que rompe los cálculos con
       `absolute`). El JS pasa coordenadas del viewport directamente.
       Al hacer scroll, el JS cierra el popover (sino quedaría
       flotando lejos del hito). */
    position: fixed;
    z-index: 900;
    max-width: 340px;
    min-width: 220px;
    background: var(--c-bg-card);
    border-radius: 10px;
    border: 1px solid var(--c-border-light);
    box-shadow: 0 .65rem 1.75rem rgba(0, 0, 0, .15);
    padding: 1rem 1.1rem .9rem;
    animation: portal-timeline-pop-in .15s ease;
}
.portal-timeline-popover[hidden] {
    display: none;
}
.portal-timeline-popover-cerrar {
    position: absolute;
    top: .35rem;
    right: .35rem;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--c-text-soft);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background .15s ease;
    font-size: .85rem;
}
.portal-timeline-popover-cerrar:hover,
.portal-timeline-popover-cerrar:focus-visible {
    background: var(--c-bg-subtle);
    outline: none;
}
/* Flecha del popover apuntando al hito. La posición/lado se
   ajusta vía clases que el JS agrega: --arriba / --abajo /
   --izq / --der según donde quede el popover relativo al hito. */
.portal-timeline-popover-flecha {
    position: absolute;
    width: 12px;
    height: 12px;
    background: var(--c-bg-card);
    border: 1px solid var(--c-border-light);
    border-bottom: none;
    border-right: none;
}
.portal-timeline-popover--arriba .portal-timeline-popover-flecha {
    bottom: -7px;
    left: 50%;
    transform: translateX(-50%) rotate(225deg);
}
.portal-timeline-popover--abajo .portal-timeline-popover-flecha {
    top: -7px;
    left: 50%;
    transform: translateX(-50%) rotate(45deg);
}
.portal-timeline-popover--izq .portal-timeline-popover-flecha {
    right: -7px;
    top: 50%;
    transform: translateY(-50%) rotate(135deg);
}
.portal-timeline-popover--der .portal-timeline-popover-flecha {
    left: -7px;
    top: 50%;
    transform: translateY(-50%) rotate(-45deg);
}
@keyframes portal-timeline-pop-in {
    from { opacity: 0; transform: scale(.95); }
    to   { opacity: 1; transform: scale(1); }
}

/* ── Contenido del detalle (interno de modal/popover) ──────────── */
.portal-timeline-detalle-fecha {
    display: inline-block;
    font-size: .85rem;
    font-weight: 600;
    color: var(--timeline-color);
    text-transform: uppercase;
    letter-spacing: .03em;
    margin: 0 0 .4rem;
}
.portal-timeline-detalle-titulo {
    color: var(--c-text-strong, var(--c-text));
    font-size: 1.25rem;
    font-weight: 700;
    margin: 0 0 .75rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.portal-timeline-detalle-texto {
    color: var(--c-text);
    font-size: .98rem;
    line-height: 1.6;
    margin: 0 0 1rem;
    overflow-wrap: anywhere;
}
.portal-timeline-detalle-texto:last-child {
    margin-bottom: 0;
}
.portal-timeline-detalle-cta {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    background: var(--timeline-color);
    color: var(--c-text-light);
    padding: .55rem 1rem;
    border-radius: 8px;
    text-decoration: none;
    font-weight: 600;
    font-size: .92rem;
    transition: background .15s ease, transform .15s ease;
}
.portal-timeline-detalle-cta:hover,
.portal-timeline-detalle-cta:focus-visible {
    background: color-mix(in srgb, var(--timeline-color) 80%, black);
    color: var(--c-text-light);
    text-decoration: none;
    transform: translateY(-1px);
    outline: none;
}
/* Popover: el CTA achicado para no romper layout angosto. */
.portal-timeline-popover .portal-timeline-detalle-titulo {
    font-size: 1.1rem;
}
.portal-timeline-popover .portal-timeline-detalle-texto {
    font-size: .9rem;
    /* Tope para que el popover no se vuelva una pared de texto. Si
       el operador tiene una descripción muy larga conviene usar
       modal en lugar de flotante. */
    max-height: 200px;
    overflow: auto;
}

@media (max-width: 640px) {
    /* Popover en mobile: pasa a ocupar casi todo el ancho centrado
       en lugar de quedarse al costado del hito (que sería ilegible). */
    .portal-timeline-popover {
        max-width: calc(100vw - 2rem);
        left: 1rem !important;
        right: 1rem;
    }
    .portal-timeline-popover-flecha {
        display: none;
    }
}

/* Reduce-motion: anula las animaciones de entrada. */
@media (prefers-reduced-motion: reduce) {
    .portal-timeline-modal-backdrop,
    .portal-timeline-modal-dialog,
    .portal-timeline-popover {
        animation: none;
    }
}
