/* ═══════════════════════════════════════════════════════════════════
   Widget de accesibilidad — v1 (5 toggles)
   Cargado desde portal/base.html. Coexiste con el theme institucional
   del tenant (--c-primary, --c-bg, etc.) y los toma del :root.
   ═══════════════════════════════════════════════════════════════════ */

.a11y-widget {
    position: fixed;
    left: 1.25rem;
    bottom: 1.25rem;
    z-index: 9990;          /* arriba de la mayoría del contenido del
                               portal, pero abajo del lightbox (z=10000)
                               para no taparlo cuando un visitante lo
                               abre desde una galería. */
}

/* ── FAB (botón flotante) ──────────────────────────────────────── */

.a11y-fab {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    border: 0;
    background: var(--c-primary, #13304D);
    color: var(--c-text-light, #FFFFFF);
    cursor: pointer;
    box-shadow: 0 .55rem 1.2rem rgba(0, 0, 0, .22);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: transform .15s ease, box-shadow .15s ease,
                background .15s ease;
}
.a11y-fab:hover,
.a11y-fab:focus-visible {
    transform: translateY(-2px) scale(1.03);
    box-shadow: 0 .85rem 1.6rem rgba(0, 0, 0, .28);
    outline: none;
}
.a11y-fab-icono {
    width: 30px;
    height: 30px;
}

/* Cuando el FAB queda visualmente sobre un fondo del mismo color que él
   (--c-primary) — el footer institucional, o cualquier zona del
   page-builder con "Fondo: color de marca" — invertimos la pareja de
   colores: pasamos a fondo claro (--c-bg) con ícono primario. Sin esto,
   el botón "desaparece" contra un fondo del mismo tono. El nombre de la
   clase (`is-over-footer`) quedó del primer alcance (solo footer); el JS
   la aplica hoy contra cualquier fondo que matchee, no solo el footer. */
.a11y-widget.is-over-footer .a11y-fab {
    background: var(--c-bg, #FFFFFF);
    color: var(--c-primary, #13304D);
}

/* ── Panel ─────────────────────────────────────────────────────── */

.a11y-panel {
    position: absolute;
    bottom: 70px;
    left: 0;
    width: 340px;
    max-height: 75vh;
    background: var(--c-bg-card, #FFFFFF);
    color: var(--c-text, #1f2937);
    border-radius: 14px;
    box-shadow: 0 1.25rem 2.5rem rgba(0, 0, 0, .22);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: a11y-panel-aparecer .18s ease-out;
}
.a11y-panel[hidden] { display: none; }
@keyframes a11y-panel-aparecer {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

.a11y-panel-header {
    background: var(--c-primary, #13304D);
    color: var(--c-text-light, #FFFFFF);
    padding: 1rem 1.15rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
}
.a11y-panel-titulo {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    gap: .55rem;
}
.a11y-panel-titulo-icono {
    background: var(--c-text-light, #FFFFFF);
    color: var(--c-primary, #13304D);
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.a11y-panel-titulo-icono svg { width: 18px; height: 18px; }
.a11y-panel-acciones {
    display: flex;
    gap: .3rem;
}
.a11y-panel-accion {
    background: transparent;
    border: 0;
    color: var(--c-text-light, #FFFFFF);
    width: 32px;
    height: 32px;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background .12s ease;
}
.a11y-panel-accion:hover,
.a11y-panel-accion:focus-visible {
    background: rgba(255, 255, 255, .18);
    outline: none;
}
.a11y-panel-accion svg { width: 18px; height: 18px; }

.a11y-panel-body {
    padding: .85rem 1rem 1rem;
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

.a11y-grupo + .a11y-grupo { margin-top: 1rem; }
.a11y-grupo-titulo {
    margin: 0 0 .55rem;
    font-size: .82rem;
    font-weight: 600;
    color: var(--c-muted, #6b7280);
    text-transform: uppercase;
    letter-spacing: .03em;
}
.a11y-grupo-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .55rem;
}

.a11y-card {
    background: var(--c-bg-subtle, #f3f4f6);
    border: 1px solid transparent;
    border-radius: 10px;
    padding: .75rem .5rem;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    color: var(--c-text, #1f2937);
    text-align: center;
    transition: background .12s ease, border-color .12s ease,
                transform .08s ease;
}
.a11y-card:focus-visible {
    outline: 2px solid var(--c-primary, #13304D);
    outline-offset: 2px;
}
.a11y-card:active {
    transform: scale(.97);
}
.a11y-card.is-activo {
    background: var(--c-primary, #13304D);
    color: var(--c-text-light, #FFFFFF);
    border-color: var(--c-primary, #13304D);
}
.a11y-card-glyph {
    font-size: 1.4rem;
    line-height: 1;
    font-weight: 700;
}
.a11y-card-label {
    font-size: .82rem;
    line-height: 1.25;
    font-weight: 500;
}
.a11y-card-nivel {
    font-size: .72rem;
    opacity: .8;
    font-variant-numeric: tabular-nums;
}

.a11y-panel-footer {
    border-top: 1px solid var(--c-border-light, #e5e7eb);
    padding: .75rem 1rem;
    text-align: center;
    flex-shrink: 0;
}
.a11y-panel-link-decl {
    color: var(--c-primary, #13304D);
    font-size: .85rem;
    text-decoration: underline;
}

/* ═══════════════════════════════════════════════════════════════════
   TOGGLES — reglas que aplican al portal cuando hay una clase
   `a11y-*` en el <body>. Todo usa `:where()` para no inflar la
   especificidad y dejar que el operador siga pudiendo customizar
   bloques sin pelearse con !important.
   ═══════════════════════════════════════════════════════════════════ */

/* 1. Tamaño del texto — NO se maneja con clase. El JS setea
   `document.documentElement.style.fontSize = "110%"` (mismo patrón
   que el slider de gestión, ver `templates/setting.html` +
   `font-scale-overrides.css`). Todo lo que use `rem`/`em` en el
   portal escala automáticamente; lo que use `px` queda fijo. */

/* 2. Altura de línea — tres pasos para texto de lectura larga.
   Apuntamos al cuerpo de contenido (no a títulos ni navbar) para
   no romper el ritmo visual del chrome del sitio. */
body.a11y-lh-150 :where(.portal-main, .portal-texto, .portal-texto-cuerpo,
                       .portal-decl-a11y, .portal-card-descripcion,
                       .portal-acordeon-body-inner) { line-height: 1.5; }
body.a11y-lh-175 :where(.portal-main, .portal-texto, .portal-texto-cuerpo,
                       .portal-decl-a11y, .portal-card-descripcion,
                       .portal-acordeon-body-inner) { line-height: 1.75; }
body.a11y-lh-200 :where(.portal-main, .portal-texto, .portal-texto-cuerpo,
                       .portal-decl-a11y, .portal-card-descripcion,
                       .portal-acordeon-body-inner) { line-height: 2; }

/* 2.b Alineación de texto — 3 modos forzados (izquierda/centro/derecha),
   ciclados por el JS igual que Contraste/Cursor de lectura (ver
   `maxNivel`). Alcance: CONTENIDO DE LECTURA — el contenedor raíz de
   cada bloque de "listado/contenido" (texto libre, noticias, material
   descargable, repositorio, FAQ, timeline, tablas, autoridades...).
   `text-align` es una propiedad HEREDABLE: alcanza con setearla en el
   contenedor raíz de cada bloque para que título + descripción + ítems
   de adentro la hereden, sin tener que enumerar cada sub-clase
   (`-titulo`, `-descripcion`, `-copete`, `-resumen`...) una por una.

   A propósito quedan AFUERA los bloques de tipo hero/CTA/widget, donde
   centrar el texto es una decisión de DISEÑO, no de lectura, y forzar
   la alineación rompería la composición en vez de ayudar (WCAG 1.4.8
   habla de texto de LECTURA, no de una tarjeta de portada):

     - Los 4 heros (`slider_hero`, `hero_video`, `hero_noticia_dinamica`,
       `buscador_destacado`) y `banner_promocional` — imagen/video de
       fondo + texto superpuesto posicionado a propósito.
     - `grilla_accesos_directos` — son botones de navegación, no prosa.
     - `cinta_whatsapp`, `alerta_institucional` — avisos de una sola
       línea, más chrome que contenido.
     - `kpi` — tarjeta de estadística, centrada por diseño.
     - `embed_red_social` — es un embed de terceros, no tiene prosa propia.

   Si se agrega un bloque nuevo de listado/contenido al catálogo, sumar
   su clase contenedora acá. */
:is(body.a11y-align-izquierda, body.a11y-align-centro, body.a11y-align-derecha)
    :is(
        /* Bloques del page builder (listado/contenido) */
        .portal-texto, .portal-acordeon, .portal-antes-despues,
        .portal-api-consulta, .portal-cubo-noticias, .portal-banner-noticias,
        .portal-progreso, .portal-autoridades, .portal-calendario,
        .portal-cards, .portal-carrusel, .portal-citas, .portal-formulario,
        .portal-contacto, .portal-faq, .portal-galeria, .portal-grafico,
        .portal-noticias, .portal-ig-reels, .portal-timeline,
        .portal-documentos, .portal-listado-estados, .portal-mapa-geojson,
        .portal-material, .portal-panel-destacado, .portal-pasos,
        .portal-repositorio-bloque, .portal-repo-cascada, .portal-rss,
        .portal-editorial, .portal-tabla-comparativa, .portal-tabla,
        .portal-tabs, .portal-normativa, .portal-vivo, .portal-video,
        .portal-videos, .portal-visor-documento,
        /* Páginas standalone (no arman página vía bloques) */
        .portal-decl-a11y, .portal-noticia-detalle, .portal-noticias-pagina,
        .portal-repositorio-detalle, .portal-repositorio-pagina,
        .portal-formulario-gracias, .portal-contacto-gracias
    ) {
    text-align: var(--a11y-text-align);
}
body.a11y-align-izquierda { --a11y-text-align: left; }
body.a11y-align-centro    { --a11y-text-align: center; }
body.a11y-align-derecha   { --a11y-text-align: right; }

/* 3. Fuente legible — swap a Verdana (universal, espaciado generoso,
   sin serifas finas que confunden a lectores con dislexia). Forzamos
   el override en TODO descendiente con `*` + `inherit` porque muchos
   bloques setean su propia font-family. El widget propio queda en
   su font normal (no aplica el override porque vive fuera de .portal-main). */
body.a11y-readable-font :where(.portal-main, .portal-navbar, .portal-topbar,
                                .portal-decl-a11y, footer) {
    font-family: Verdana, Tahoma, 'Trebuchet MS', Arial, sans-serif !important;
    letter-spacing: 0.02em;
    word-spacing: 0.04em;
}
body.a11y-readable-font :where(.portal-main, .portal-navbar, .portal-topbar,
                                .portal-decl-a11y, footer) :where(*) {
    font-family: inherit !important;
}

/* 3.b Fuente para dislexia (MHF-HU042) — OpenDyslexic real, no una
   sans-serif del sistema como "Fuente legible". El @font-face NO vive
   acá (este CSS se sirve siempre) — lo inyecta el JS bajo demanda,
   solo la primera vez que este toggle se activa
   (`asegurarFuenteDislexia` en accesibilidad-widget.js), así el
   .woff2 nunca se descarga para quien no lo pidió.

   A propósito va DESPUÉS de "Fuente legible" en la hoja: si un
   visitante llegara a activar los dos toggles a la vez, con igual
   especificidad y ambos `!important`, gana la regla que aparece
   último — priorizamos el soporte real de dislexia por sobre el
   ajuste genérico. */
body.a11y-dyslexic-font :where(.portal-main, .portal-navbar, .portal-topbar,
                                .portal-decl-a11y, footer) {
    font-family: 'OpenDyslexic', Verdana, Tahoma, sans-serif !important;
    letter-spacing: 0.01em;
}
body.a11y-dyslexic-font :where(.portal-main, .portal-navbar, .portal-topbar,
                                .portal-decl-a11y, footer) :where(*) {
    font-family: inherit !important;
}

/* 4. Contraste — tres modos. Sobrescribimos las variables --c-* del
   tema solo cuando el modo está activo, así afecta a todo el stack
   (bloques, navbar, footer) sin tener que tocar cada CSS individual.

   Cada modo apunta a un usuario distinto:
     - "claro": refuerza WCAG dentro del esquema institucional claro.
     - "oscuro": modo nocturno con alto contraste interno.
     - "alto":  máximo contraste, blanco/negro puro + bordes fuertes.
*/
/* Los tres modos juegan con dos ejes:
     1) Lienzo del contenido (--c-bg / --c-bg-card / --c-bg-subtle):
        cambia entre claro y oscuro.
     2) Texto del cuerpo (--c-text y derivados):
        se invierte para mantener contraste con el lienzo.

   El chrome institucional (hero/navbar/topbar pintado con --c-primary
   y --c-accent) NO se toca: sigue usando --c-text-light para su texto,
   variable que NO cambia entre modos. Por eso ya no hace falta el
   override defensivo sobre navbar/hero que tenía la versión anterior. */

body.a11y-contrast-claro {
    --c-bg:           #FFFFFF;
    --c-bg-card:      #FFFFFF;
    --c-bg-subtle:    #F3F4F6;
    --c-text:         #111111;
    --c-text-on-light:#111111;
    --c-text-soft:    #1F2937;
    --c-text-strong:  #000000;
    /* Más oscuro que el gris "tibio" por defecto — la referencia contra
       la que se comparó (UserWay) usa mucho más contraste en su modo
       claro; #4B5563 pasaba WCAG AA pero se sentía como poca diferencia
       frente al modo normal. */
    --c-muted:        #374151;
    --c-border:       #4B5563;
    --c-border-light: #6B7280;
    /* --c-border-lighter no se tocaba acá (solo en el override de
       "glass" más abajo) — cualquier borde fuera de un wrapper glass
       que use esta variable se quedaba con el default de theme-config
       (#dddddd), casi invisible contra el fondo de este modo. */
    --c-border-lighter: #6B7280;
    /* En claro mantenemos texto-marca = primary (el patrón institucional
       normal). Solo subimos el contraste del cuerpo, no del branding. */
    --c-text-brand:   var(--c-primary);
}

body.a11y-contrast-oscuro {
    /* Lienzo en gris neutro casi negro — NO navy. Si usáramos un dark
       basado en primary, el navbar/hero (pintado con --c-primary) se
       funde con el fondo del cuerpo y se pierde la separación visual.
       Con un gris neutro muy oscuro el primary institucional sigue
       identificable como "chrome".

       Los tonos siguen la escala Material dark partiendo más profundo
       que el default (#121212) — bajamos a #0d0d0d para abrir más
       contraste con el primary del tenant (que típicamente está en
       el rango #15-25 de luminancia). La diferencia entre
       lienzo/card/subtle es ~6% de luminancia, lo justo para que los
       paneles se despeguen sin pelearse con el chrome. */
    --c-bg:           #0d0d0d;
    --c-bg-card:      #1c1c1c;
    --c-bg-subtle:    #262626;
    /* En oscuro, el "texto del cuerpo" pasa de oscuro a claro. Reusamos
       la variable --c-text-light que ya es la polaridad clara — así no
       inventamos un tercer color, mantenemos la pareja semántica. */
    --c-text:         var(--c-text-light);
    --c-text-on-light:var(--c-text-light);
    --c-text-soft:    #E5E7EB;
    --c-text-strong:  #FFFFFF;
    /* Más claro que el gris apagado por defecto (#B0B0B0) — mismo
       motivo que en "claro": la referencia (UserWay) usa un salto de
       contraste mucho más marcado. #B0B0B0 pasaba WCAG pero se leía
       "tibio" al lado de fondo casi negro. */
    --c-muted:        #D1D5DB;
    --c-border:       #525252;
    --c-border-light: #333333;
    --c-border-lighter: #333333;
    /* Texto de marca pasa a text-light (no accent) — la consigna del
       modo oscuro es priorizar contraste WCAG sobre identidad de marca.
       El accent queda reservado SOLO para hover en elementos
       clicleables, como única señal de "esto es interactivo" que
       sobrevive cuando todos los textos son del mismo color. */
    --c-text-brand:   var(--c-text-light);
    background: #0d0d0d;
    color: var(--c-text-light);
}
body.a11y-contrast-oscuro :where(.portal-main, .portal-body, footer) {
    background-color: var(--c-bg) !important;
    color: var(--c-text);
}

/* ── Modo oscuro: forzar todos los textos a text-light ────────────
   El usuario pidió que en dark mode todos los textos abandonen
   primary/text-brand y vayan a text-light, aceptando explícitamente
   que el diseño se vuelva "soso" a cambio de cumplir contraste. Esta
   regla barre el portal completo (main + footer + navbar) y aplica
   text-light a todos los nodos textuales típicos.

   No usamos `:where()` porque necesitamos especificidad para ganarle
   a las reglas de bloque que ya tienen color (ej. `.portal-card-titulo
   { color: var(--c-text-brand) }`). Con
   `body.a11y-contrast-oscuro :is(...) :is(...)` quedamos en (0,2,2) —
   beats todas las reglas de bloque que usan un solo ancestor. */
body.a11y-contrast-oscuro :is(.portal-main, .portal-navbar, footer) :is(
    h1, h2, h3, h4, h5, h6,
    p, a, li, td, th, span, div,
    button, label, small, strong, em, b, i,
    dt, dd, blockquote, summary, figcaption
) {
    color: var(--c-text-light);
}

/* Hover en elementos clicleables → accent. Es la única excepción al
   "todo blanco" y sirve de affordance: si al pasar el mouse el texto
   cambia, el visitante entiende que el elemento es interactivo. */
body.a11y-contrast-oscuro :is(.portal-main, .portal-navbar, footer)
    :is(a, button):hover,
body.a11y-contrast-oscuro :is(.portal-main, .portal-navbar, footer)
    :is(a, button):focus-visible {
    color: var(--c-accent);
}

/* Badges con fondo claro hardcoded (`.portal-material-badge--*`):
   tienen fondos pastel y texto oscuro. El sweep general les pisó el
   texto a blanco, que no contrasta con el fondo pastel. Acá los
   reinvertimos al patrón "fondo oscuro + texto color claro" típico
   de dark themes: mantiene la codificación semántica (rojo=PDF,
   azul=imagen, neutro=otros) y restaura contraste. La especificidad
   `body.<class> .portal-main .portal-material-badge--<x>` queda en
   (0,3,1), gana contra el sweep (0,2,2). */
body.a11y-contrast-oscuro .portal-main .portal-material-badge--pdf {
    background: rgba(220, 38, 38, 0.18);
    color: #fca5a5;
}
body.a11y-contrast-oscuro .portal-main .portal-material-badge--imagen {
    background: rgba(37, 99, 235, 0.20);
    color: #93c5fd;
}
body.a11y-contrast-oscuro .portal-main .portal-material-badge--otro {
    background: rgba(255, 255, 255, 0.10);
    color: var(--c-text-light);
}

/* Bloques con "fondo suave" (tinte claro de --c-primary/--c-accent,
   ej. `--fondo-primary-suave`): ese fondo NO se oscurece en ningún
   modo de contraste (solo "alto" lo hace, forzando primary a negro).
   El problema es que --c-text/--c-text-brand/etc. SÍ pasan a claro en
   oscuro, y el sweep de arriba fuerza lo mismo — el resultado es texto
   claro sobre un fondo que sigue siendo claro, ilegible. Se fija un
   texto oscuro fijo (no una variable --c-*, que cambiaría con el modo)
   porque el fondo real de estos bloques nunca cambia. */
body.a11y-contrast-oscuro .portal-main :is(
    .portal-bloque-wrapper--fondo-primary-suave,
    .portal-bloque-wrapper--fondo-accent-suave,
    .portal-texto--fondo-primary-suave,
    .portal-texto--fondo-accent-suave,
    .portal-card--fondo-primary-suave,
    .portal-card--fondo-accent-suave,
    .portal-listado-estados--fondo-primary-suave,
    .portal-listado-estados--fondo-accent-suave,
    .portal-panel-destacado--fondo-primary-suave,
    .portal-panel-destacado--fondo-accent-suave,
    .portal-citas.portal-fondo--primary-suave,
    .portal-citas.portal-fondo--accent-suave
) :is(
    h1, h2, h3, h4, h5, h6,
    p, a, li, td, th, span, div,
    button, label, small, strong, em, b, i,
    dt, dd, blockquote, summary, figcaption
) {
    color: #1F2937 !important;
}

body.a11y-contrast-alto {
    --c-bg:           #FFFFFF;
    --c-bg-card:      #FFFFFF;
    --c-bg-subtle:    #FFFFFF;
    --c-text:         #000000;
    --c-text-on-light:#000000;
    --c-text-soft:    #000000;
    --c-text-strong:  #000000;
    --c-muted:        #000000;
    --c-border:       #000000;
    --c-border-light: #000000;
    --c-border-lighter: #000000;
    --c-primary:      #000000;
    --c-primary-dark: #000000;
    --c-primary-light:#FFFFFF;
    --c-primary-lighter:#FFFFFF;
    --c-accent:       #000000;
    /* En alto contraste el primary se vuelve negro puro — entonces el
       texto que iba sobre primary necesita seguir siendo claro para
       contrastar. Forzamos --c-text-light a blanco puro para máximo
       contraste (vs el #f2f2f2 default). */
    --c-text-light:   #FFFFFF;
    /* Texto-marca también negro puro — máximo contraste sobre blanco. */
    --c-text-brand:   #000000;
    background: #FFFFFF !important;
    color: #000000;
}
body.a11y-contrast-alto :where(img, video) {
    /* Aseguramos contraste mínimo de imágenes — sin esto pueden quedar
       lavadas sobre fondo blanco si tenían opacity o mix-blend. */
    filter: contrast(1.2);
}

/* ── Links invisibles bajo contraste — WCAG 1.4.1 (no depender solo
   del color) ────────────────────────────────────────────────────────
   En "oscuro" y "alto", `--c-text-brand` queda pisado al MISMO valor
   que `--c-text` (ver arriba: en oscuro ambos son `--c-text-light`; en
   alto ambos son negro puro). Un link normal (`a { color:
   var(--c-text-brand) }`, estilosPortal.css) deja de distinguirse del
   texto de alrededor — sin la variable de color de marca, un link no
   tiene NINGÚN otro indicio visual: por defecto no lleva subrayado
   (solo al pasar el mouse, ver `a:hover` en estilosPortal.css).

   Esto es un problema NUEVO que introducen los modos de contraste (en
   modo normal, `--c-text-brand` = `--c-primary`, sí distinto de
   `--c-text`) — por eso se arregla acá y no cambiando el comportamiento
   general del sitio.

   Subrayado permanente y sutil (no el tratamiento fuerte del toggle
   aparte "Resaltar enlaces", que sigue disponible para quien lo
   necesite y gana por especificidad/`!important` si el visitante
   activa los dos a la vez). Sin forzar un color de subrayado: hereda
   el color ya resuelto del link (`currentColor` es el default de
   `text-decoration-color`), así que no hace falta otra variable.

   Acotado a `.portal-main` (mismo alcance que "Resaltar enlaces"): el
   chrome del sitio (navbar/footer) no usa `--c-text-brand` para sus
   propios links, así que no tiene este problema. Los links DENTRO del
   texto enriquecido (`.portal-texto-cuerpo a`) ya llevan subrayado
   propio siempre (estilosBloques.css) — esta regla no les suma nada,
   pero tampoco rompe nada por aplicarse igual ahí. */
:is(body.a11y-contrast-claro, body.a11y-contrast-oscuro, body.a11y-contrast-alto)
    :where(.portal-main a, .portal-main a:link, .portal-main a:visited) {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ── Footer invisible bajo "alto" — texto negro sobre fondo negro ──
   `.portal-footer-meta` y `.portal-footer-line` (estilosPortal.css) usan
   `color: var(--c-border)` — pensado como "gris sutil sobre el
   institucional `--c-primary`" del footer. En "alto", TANTO `--c-border`
   COMO `--c-primary` quedan forzados a negro puro (ver arriba): el
   texto desaparece contra su propio fondo. La variante de footer con
   `links_color: accent` (`.portal-footer--links-accent`) tiene el mismo
   problema con `--c-accent`, también forzado a negro.

   El resto del footer ya sigue el patrón correcto — título e
   institución usan `--c-text-light`, que SÍ se fuerza a blanco en
   "alto" (ver `.a11y-contrast-alto` arriba). Acá alineamos estas dos
   excepciones al mismo patrón, sólo bajo "alto": en claro/oscuro
   `--c-border` es gris claro/medio y SÍ contrasta contra el
   institucional, no hace falta tocarlas ahí.

   No hace falta un sweep de todo el footer (como el que "oscuro" tiene
   para navbar/footer, ver arriba): en alto el resto de sus elementos ya
   resuelve bien solos. */
body.a11y-contrast-alto footer :is(.portal-footer-meta, .portal-footer-line) {
    color: var(--c-text-light);
}
body.a11y-contrast-alto footer.portal-footer--links-accent
    :is(.portal-footer-link, .portal-footer-line-link) {
    color: var(--c-text-light);
}

/* ── Hover del navbar invisible bajo "alto" — mismo patrón de bug ──
   `.portal-nav-item:hover` usa `color: var(--c-primary-light)`
   (estilosPortal.css:247) — un tinte claro del institucional, para que
   el link se note un poco más suave al pasar el mouse. En "alto",
   `--c-primary-light` queda forzado a blanco puro (arriba: pensado
   para servir de TEXTO sobre un `--c-primary` que también se vuelve
   negro) — pero el navbar NO pinta con `--c-primary`, pinta con
   `--c-bg-card` (también blanco en "alto"). Resultado: al pasar el
   mouse por un item del menú, el texto se pone blanco sobre un navbar
   blanco — desaparece exactamente en el estado que más importa
   (hover, la confirmación visual de qué está por clickear).
   El menú mobile (overlay a pantalla completa sobre `--c-primary`) no
   tiene este problema: ahí el hover ya usa `--c-text-light` con
   opacidad, no `--c-primary-light` (ver la media query más abajo). */
body.a11y-contrast-alto .portal-nav-item:hover {
    color: #000000;
}

/* ── Contraste + fondos "glass" — conflicto conceptual real ────────
   El sistema de "fondo tipo vidrio" (config `fondo_transparente`,
   estilosPortal.css ~L970) resuelve el look reasignando --c-bg-card/
   --c-border* a rgba() translúcidos DIRECTAMENTE en el wrapper del
   bloque — eso gana por especificidad a cualquier valor que el modo
   de contraste ponga a nivel body, así que el contraste nunca llega.
   Y aunque llegara: una superficie translúcida + blur no puede
   garantizar contraste por diseño, porque se funde con lo que sea que
   haya detrás (imagen, degradé), que el modo no controla ni predice.

   Con cualquier modo de contraste activo, el glass se apaga: pasa a
   superficie opaca con los MISMOS tokens que ya usa el resto del modo
   (no un color nuevo) y sin blur — el contraste vuelve a ser
   garantizado, no "lo que haya detrás, un poco atenuado". */
/* OJO: no se puede escribir `--c-bg-card: var(--c-bg-subtle)` acá — el
   propio wrapper glass YA redefine --c-bg-subtle (y --c-bg-card, --c-bg,
   --c-border*) a rgba() translúcido en esta misma regla/elemento
   (estilosPortal.css ~L985); referenciar la variable la lee contaminada
   por esa misma redefinición, no el valor opaco del modo. Por eso van
   los valores literales de cada modo, duplicados a propósito (si se
   cambia un tono de modo arriba, hay que repetirlo acá).

   ── El texto también hay que pisarlo, y NO es el mismo problema ──
   Un bloque glass vive casi siempre dentro de una ZONA con fondo de
   imagen, que declara `.portal-zona--texto-claro` (estilosPortal.css
   ~L790): texto claro para leerse contra la foto oscura detrás. Esa
   regla existe en el ANCESTOR (la `<section>` de la zona), no en el
   wrapper — así que acá SÍ alcanza con redeclarar --c-text/--c-text-
   strong/etc. en el wrapper: al ser el ancestro más cercano, gana por
   herencia sin pelear especificidad con la zona (a diferencia de
   --c-bg-card arriba, que se declara en el wrapper mismo).

   El tema base YA resuelve este mismo cruce para tarjetas OPACAS
   normales — hay un reset (`.portal-zona--texto-claro :where(...)`,
   estilosPortal.css ~L828) que las devuelve a texto oscuro, pero
   EXCLUYE a propósito los wrappers glass (`:not(.portal-bloque-
   wrapper--sin-fondo *)`): en modo normal el glass debe seguir el tono
   de la zona (texto claro sobre vidrio translúcido oscuro — se ve
   bien). El problema es que bajo un modo de contraste el glass ya NO
   es translúcido (lo apagamos arriba, superficie opaca) — pero sigue
   excluido de ese reset, así que el texto claro de la zona le queda
   pisado encima de una tarjeta ahora blanca: texto claro sobre blanco,
   lavado (bug real, reportado 2026-08-18, capturas con "cards"/feed
   RSS/material descargable sobre fondo con foto).

   "oscuro" no necesita este mismo parche: el sweep de arriba
   (`body.a11y-contrast-oscuro :is(.portal-main,...) :is(h1,...,span,...)`)
   ya fuerza TODO el texto de `.portal-main` a `--c-text-light`, glass o
   no — es más bruto pero por eso mismo cubre este caso sin querer. */
body.a11y-contrast-claro :is(.portal-bloque-wrapper--sin-fondo, .portal-bloque-lateral--sin-fondo) {
    --c-bg-card:        #FFFFFF !important;
    --c-bg-subtle:      #F3F4F6 !important;
    --c-bg:             #FFFFFF !important;
    --c-border:         #4B5563 !important;
    --c-border-light:   #6B7280 !important;
    --c-border-lighter: #6B7280 !important;
    --c-text:           #111111 !important;
    --c-text-strong:    #000000 !important;
    --c-text-soft:      #1F2937 !important;
    --c-muted:          #374151 !important;
    --c-text-brand:     var(--c-primary) !important;
    color:              #111111 !important;
}
body.a11y-contrast-oscuro :is(.portal-bloque-wrapper--sin-fondo, .portal-bloque-lateral--sin-fondo) {
    --c-bg-card:        #1c1c1c !important;
    --c-bg-subtle:      #262626 !important;
    --c-bg:             #0d0d0d !important;
    --c-border:         #525252 !important;
    --c-border-light:   #333333 !important;
    --c-border-lighter: #333333 !important;
}
body.a11y-contrast-alto :is(.portal-bloque-wrapper--sin-fondo, .portal-bloque-lateral--sin-fondo) {
    --c-bg-card:        #FFFFFF !important;
    --c-bg-subtle:      #FFFFFF !important;
    --c-bg:             #FFFFFF !important;
    --c-border:         #000000 !important;
    --c-border-light:   #000000 !important;
    --c-border-lighter: #000000 !important;
    --c-text:           #000000 !important;
    --c-text-strong:    #000000 !important;
    --c-text-soft:      #000000 !important;
    --c-muted:          #000000 !important;
    --c-text-brand:     #000000 !important;
    color:              #000000 !important;
}
/* El blur en sí (aunque el fondo ya sea opaco, un blur de fondo sigue
   sin sumar nada útil bajo un modo de contraste) — se saca en TODO el
   subárbol, no solo en la lista puntual de "superficies card-like",
   para no depender de mantener esa lista sincronizada acá. */
:is(body.a11y-contrast-claro, body.a11y-contrast-oscuro, body.a11y-contrast-alto)
    :is(.portal-bloque-wrapper--sin-fondo, .portal-bloque-lateral--sin-fondo) * {
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
}

/* Botones/badge "glass" del hero y el banner: van DIRECTO sobre la
   imagen (no dentro de un wrapper con --c-bg-card), con colores fijos
   en rgba() pensados para funcionar "a ojo" sobre cualquier foto — cero
   garantía de contraste. Mismo criterio: superficie y texto del modo,
   sin blur. */
:is(body.a11y-contrast-claro, body.a11y-contrast-oscuro, body.a11y-contrast-alto)
    :is(.portal-hero-cta--glass, .portal-banner-cta--glass, .portal-hero-eyebrow) {
    background: var(--c-bg-card) !important;
    color: var(--c-text) !important;
    border-color: var(--c-border) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
}

/* 5. Escala de grises — filter en el wrapper principal. NO en el
   widget mismo, para que el visitante pueda seguir reconociendo el
   estado activo de los toggles por color. */
body.a11y-grayscale :where(.portal-main, .portal-navbar, .portal-topbar, footer) {
    filter: grayscale(1);
}

/* 5.b Ocultar imágenes (MHF-HU043) — acelera la carga percibida y
   reduce distracciones. Alcance: <img> reales (navbar/footer
   incluidos, sin excluir nada — "ocultar TODAS las imágenes" de la
   HU) + los `background-image` de CSS de los bloques que también
   pintan la foto así (noticias, hero de fotos) — un `<img>` oculto
   no alcanza ahí, no hay elemento al que aplicarle `display:none`.
   NO toca <svg> de íconos (UI/chrome, no "imágenes" en el sentido de
   la historia).

   Los contenedores listados abajo dejaban asomar, al ocultar su
   `<img>`, el fondo de "placeholder de carga" que cada uno ya tenía
   (degradé accent→primary en banners, `--c-primary` sólido y
   saturado en cards flip, `--c-bg-subtle` en otras) — cada uno
   pensado para el instante ANTES de que la foto cargue, no como
   estado final permanente, y por eso quedaban inconsistentes entre
   sí. Se unifican todos a un solo tono neutro derivado de
   `--c-primary` (mismo patrón `color-mix()` ya usado en
   theme-config.css para --c-primary-dark/light/lighter). */
body.a11y-hide-images {
    --a11y-img-fallback: color-mix(in srgb, var(--c-primary) 50%, #6b7280 50%);
}
body.a11y-hide-images img {
    display: none !important;
}
body.a11y-hide-images :where(
    .portal-noticia-thumb,
    .portal-hero--imagen-overlay,
    .portal-banner-media,
    .portal-card-flip-anverso,
    .portal-card--foto,
    .portal-card--overlay,
    .portal-panel-destacado-item,
    .portal-video-card-thumb,
    .portal-video-frame-thumb
) {
    background: var(--a11y-img-fallback) !important;
}
/* NO se toca `.portal-video-frame` (video_embebido/grilla en
   reproducción, o el <video> HTML5) — su fondo negro es letterboxing
   de reproductor de video real, no un placeholder de "sin imagen";
   usar el tono desaturado ahí se vería peor, no mejor.
   `.portal-video-frame-thumb` SÍ se unifica arriba: es la miniatura
   "click para cargar" del iframe (mismo rol que la de la grilla),
   no el reproductor en uso. El `poster` de <video> (imagen real,
   pero atributo HTML, no <img>) se resuelve por JS — ver
   `aplicarOcultarImagenesEnVideo` en accesibilidad-widget.js. */
/* Fondo de SECCIÓN con imagen (`--zona-bg-image`, ver estilosPortal.css
   ~línea 754) — mecanismo aparte de los bloques de arriba, el operador
   lo elige por zona (destacados/contenido/servicios) desde el editor.
   Compound selector a propósito: `.portal-zona--con-fondo` sola
   también cubre fondos de COLOR liso (elegidos aparte, sin imagen) —
   esos no se tocan, solo la variante que además tiene
   `.portal-zona--tiene-imagen`. */
body.a11y-hide-images .portal-zona--con-fondo.portal-zona--tiene-imagen {
    background-image: none !important;
    background-color: var(--a11y-img-fallback) !important;
}

/* Logo institucional del navbar: es NAVEGACIÓN (el único link a
   home), no "contenido" — ocultarlo sin más deja ese botón vacío e
   inutilizable (reportado por el usuario). El <img> real igual
   desaparece (cumple "ocultar TODAS las imágenes" al pie de la
   letra), pero se revela el nombre del sistema como texto, reusando
   el `title` que el link `.portal-logo` ya trae SIEMPRE —mismo dato
   que `.portal-logo-wordmark` muestra cuando no hay logo cargado,
   ver templates/portal/base.html~143. Sin declarar color: hereda
   `color: var(--c-text-brand)` que `.portal-logo` ya tiene. */
body.a11y-hide-images .portal-logo::after {
    content: attr(title);
    font-size: 1.1rem;
    font-weight: 800;
    letter-spacing: -0.02em;
}

/* 6. Pausar animaciones — útil para visitantes con vestibular
   sensitivity. Comprime todas las animations/transitions a casi-cero
   (0.001ms — no 0ms, así los listeners de `transitionend` igual
   disparan). Limita las animations infinitas a una sola iteración.
   El JS del widget además pausa los <video> y emite un evento que
   escucha el carrusel para detener su autoplay (setInterval, fuera
   del alcance de CSS). */
body.a11y-pause-animations,
body.a11y-pause-animations *,
body.a11y-pause-animations *::before,
body.a11y-pause-animations *::after {
    animation-duration: .001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
}

/* 7. Resaltar enlaces — subrayado fuerte en accent para todos los
   `<a>` del portal. No tocamos `color` ni `background` (eso rompía
   links sobre fondos de color como acordeón en navy, cinta de
   WhatsApp, botones que son <a>, etc.). Sólo el subrayado, con color
   propio (decoration-color) para que sea visible sobre cualquier
   fondo, sin requerir cambiar el texto. */
body.a11y-highlight-links :where(.portal-main a, .portal-main a:link, .portal-main a:visited) {
    text-decoration: underline solid 2px !important;
    text-decoration-color: var(--c-accent, #fbbf24) !important;
    text-underline-offset: 3px !important;
}

/* 6.b Bloques clicleables (accesos directos, cards, botones, banners,
   acordeón) tienen su propio diseño con fondos/colores propios. Para
   ellos el subrayado del texto interno tampoco sirve (no se entiende
   "todo el bloque es clickeable"). Usamos OUTLINE accent — comunica
   "esto es interactivo" sin disturbar el contenido. */
body.a11y-highlight-links .portal-main :is(
    .portal-acceso,
    .portal-card--clickable,
    .portal-material-card,
    .portal-card--overlay,
    .portal-banner,
    .portal-acordeon-header,
    .portal-cinta-whatsapp,
    button
) {
    outline: 3px solid var(--c-accent, #fbbf24) !important;
    outline-offset: 2px !important;
}

/* 8. Outline de foco — visible tanto en navegación por teclado como
   en click con mouse. Antes solo usaba `:focus-visible` que en la
   mayoría de browsers NO matchea con mouse click (heurística para
   "el foco lo necesitás solo si navegás por teclado"). Para que el
   toggle realmente haga algo cuando el visitante mueve el mouse,
   ampliamos a `:focus` también. */
body.a11y-focus-outline :focus,
body.a11y-focus-outline :focus-visible {
    outline: 3px solid var(--c-primary, #13304D) !important;
    outline-offset: 3px !important;
    border-radius: 3px;
}

/* 9. Cursor de lectura — 3 sub-modos, ciclado por el JS
   (`aplicarCursorAsistido`). A diferencia de todo lo de arriba, acá el
   posicionamiento en tiempo real lo hace JS (`mousemove`); el CSS solo
   define la forma/apariencia de cada modo y el overlay contenedor. */
.a11y-cursor-overlay {
    position: fixed;
    inset: 0;
    /* Arriba del FAB/panel (9990): con el cursor nativo oculto en todo el
       documento (ver 8.1), si el overlay quedara debajo, el cursor
       personalizado desaparecería justo al pasar sobre el propio widget
       de accesibilidad. pointer-events:none evita que tape los clics. */
    z-index: 9995;
    pointer-events: none;
}

/* 9.1 Cursor grande — se oculta el nativo (todo el documento, no solo
   el overlay) y se dibuja uno propio más grande y de alto contraste
   (relleno oscuro + borde claro, visible sobre cualquier fondo). */
body.a11y-cursor-grande,
body.a11y-cursor-grande * {
    cursor: none !important;
}
.a11y-cursor-grande-punta {
    position: absolute;
    top: 0;
    left: 0;
    width: 40px;
    height: 40px;
    /* Corrige el origen para que la "punta" (esquina superior
       izquierda de la forma, antes de rotar) quede exactamente en la
       posición real del mouse, no el centro del cuadrado de 40px. */
    margin: -4px 0 0 -4px;
    background: #111;
    border: 3px solid #fff;
    border-radius: 50% 50% 50% 0;
    transform: rotate(-45deg);
    box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
    will-change: transform;
}

/* 9.2 Máscara de lectura — dos paneles oscuros full-width que dejan
   una franja transparente centrada en la posición Y del mouse (~110px
   de alto), oscureciendo el resto de la página para que el visitante
   no pierda la línea que está leyendo. Los bordes en color marcan el
   límite de la franja legible, para distinguirla de la zona oscura. */
.a11y-cursor-mascara-panel {
    position: absolute;
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, .6);
}
.a11y-cursor-mascara-arriba {
    top: 0;
    border-bottom: 3px solid var(--c-accent, #fbbf24);
}
.a11y-cursor-mascara-abajo {
    bottom: 0;
    border-top: 3px solid var(--c-accent, #fbbf24);
}

/* 9.3 Guía de lectura — línea corta (no ocupa todo el ancho) con
   marcador triangular, que resalta la línea actual SIN oscurecer el
   resto (más liviana que la máscara). El JS mueve
   `.a11y-cursor-guia-linea` en los DOS ejes, centrada en el mouse pero
   acotada a no salirse de la pantalla, y desliza
   `.a11y-cursor-guia-triangulo` en X para seguir el mouse, dentro de
   los límites de la línea — si el mouse se va más allá de un extremo,
   el triángulo queda "pegado" a ese borde en vez de salirse. */
.a11y-cursor-guia-linea {
    position: absolute;
    top: 0;
    left: 0;
    width: min(700px, 90vw);
    height: 12px;
    border-radius: 6px;
    background: var(--c-accent, #fbbf24);
    border: 2px solid rgba(0, 0, 0, .55);
    box-shadow: 0 2px 8px rgba(0, 0, 0, .45);
    will-change: transform;
}
.a11y-cursor-guia-triangulo {
    position: absolute;
    top: -16px;
    left: 0;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 12px solid transparent;
    border-right: 12px solid transparent;
    /* Mismo color que la barra (--c-accent), no negro: así el triángulo
       se lee como parte de una sola pieza con la línea. El contorno
       oscuro (mismo tono que el borde de la línea) es lo que le da
       contraste contra fondos claros — no se puede usar `border` real
       en una forma hecha con border-triangle, por eso va como
       drop-shadow apilado en las 4 direcciones cardinales. */
    border-top: 12px solid var(--c-accent, #fbbf24);
    filter:
        drop-shadow(1.5px 0 0 #111)
        drop-shadow(-1.5px 0 0 #111)
        drop-shadow(0 1.5px 0 #111)
        drop-shadow(0 -1.5px 0 #111);
}

/* ═══════════════════════════════════════════════════════════════════
   Página pública de la declaración (/accesibilidad/).
   Hereda layout y tipografía del portal (`portal-texto`, `portal-container`)
   — sólo sumamos los headers institucionales y la grilla de metadata.
   ═══════════════════════════════════════════════════════════════════ */

.portal-decl-a11y {
    padding-block: 2.5rem 3rem;
}
.portal-decl-a11y-header {
    border-bottom: 2px solid var(--c-primary, #13304D);
    padding-bottom: 1rem;
    margin-bottom: 1.5rem;
}
.portal-decl-a11y-header h1 {
    color: var(--c-primary, #13304D);
    font-size: 2rem;
    font-weight: 700;
    margin: 0 0 .25rem;
}
.portal-decl-a11y-subtitulo {
    color: var(--c-muted, #6b7280);
    font-size: 1rem;
    margin: 0;
}
.portal-decl-a11y-cuerpo {
    margin-bottom: 2rem;
}
.portal-decl-a11y-meta {
    background: var(--c-bg-subtle, #f3f4f6);
    border-radius: 10px;
    padding: 1.25rem 1.5rem;
}
.portal-decl-a11y-meta-grid {
    display: grid;
    grid-template-columns: minmax(180px, max-content) 1fr;
    gap: .75rem 1.5rem;
    margin: 0;
}
.portal-decl-a11y-meta-grid dt {
    font-weight: 600;
    color: var(--c-text-strong, #111827);
}
.portal-decl-a11y-meta-grid dd {
    margin: 0;
    color: var(--c-text, #1f2937);
}
.portal-decl-a11y-tel {
    color: var(--c-muted, #6b7280);
    font-size: .9rem;
}
@media (max-width: 600px) {
    .portal-decl-a11y-meta-grid {
        grid-template-columns: 1fr;
        gap: .25rem 0;
    }
    .portal-decl-a11y-meta-grid dt {
        margin-top: .75rem;
    }
    .portal-decl-a11y-meta-grid dt:first-child {
        margin-top: 0;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   Responsive — en mobile el panel ocupa casi todo el ancho.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 480px) {
    .a11y-widget {
        left: .75rem;
        bottom: .75rem;
    }
    .a11y-panel {
        width: calc(100vw - 1.5rem);
        max-width: 360px;
    }
}
