/*
 * Estilos propios del sitio, mínimos e intencionados.
 * Sustituye a now-ui-kit.css + my-style.css + personalize.css (theme de 2017 sobre Bootstrap 4, retirado).
 * Solo contiene lo que Bootstrap 5 no cubre de forma nativa.
 *
 * Fase B, Tarea 6 (design U6): reescrito para usar tokens --ce-* en vez de
 * literales de color. Alcance de esta reescritura: colores (fondo, borde,
 * texto) y font-weight cuando hay un token exacto (700 -> --ce-font-weight-bold).
 * NO se ha tocado el espaciado/padding/margin/font-size/border-radius en
 * píxeles o rem: ninguno de esos valores concretos (10px, .95rem, .82rem,
 * 10px de radio...) tiene un token equivalente en la escala de Celosía
 * (--ce-space-*, --ce-font-size-*, --ce-size-radius-*), y forzar el valor
 * más cercano de la escala movería el layout de forma no verificada en el
 * checkpoint visual (riesgo de regresión, ver sdd/celosia-phase-b/design
 * §8 R1: "presupuestar un pase de seguimiento explícito, no reclamar
 * cobertura completa en un solo pase"). El antiguo bloque @media
 * (prefers-color-scheme: dark) de 14 literales hardcodeados se retiró en
 * la Tarea 7 (design U7) sin sustituto directo — ver la nota al final de
 * este fichero, justo donde vivía ese bloque.
 */

/* Red de seguridad de desbordamiento horizontal (sdd/directo-a-responsive, AD-4).
   NO sustituye a los arreglos de raiz: aterriza DESPUES de ellos, a proposito,
   para que .novedades-paginacion y .organigrama-contacto queden demostrados
   como arreglados y no simplemente tapados.
   A partir de aqui, la verificacion de desbordamiento se afirma a NIVEL DE
   ELEMENTO (scrollWidth <= clientWidth del componente sospechoso), nunca a
   nivel de documento: esta regla enmascara la senal de documento. */
html {
    overflow-x: hidden;
}
body {
    /* `hidden` como suelo; `clip` gana donde exista. Con <html> ya en hidden no
       hay propagacion al viewport, asi que un `hidden` en <body> lo convierte en
       contenedor de scroll (overflow-y visible pasa a auto) — el fallo clasico
       que desactiva position:sticky. `clip` recorta sin crear ese contenedor.
       Hoy el proyecto no tiene ningun sticky (auditado, ver design §4.4), pero
       este par deja el guard a prueba de sticky futuros por 1 linea. */
    overflow-x: hidden;
    overflow-x: clip;
}

/* Barra superior grande (teléfono/email/redes) que solo se ve en escritorio */
.navbar-lg {
    padding-top: 10px;
    padding-bottom: 10px;
    background-color: var(--ce-color-semantic-surface);
}

/* Cabecera de página interior: solo el título, sin fondo propio -- el fondo
   vive en .site-page-header, que envuelve este bloque junto con la miga de
   pan (ver ese bloque, más abajo, para el porqué del degradado de marca). */
.my-header {
    padding: 0;
    text-align: left;
}

.my-header > .container {
    width: 100%;
    padding-top: 40px;
    padding-bottom: 40px;
}

/* h1, no h2: es el único título de página de todo el sitio (revisión de
   encabezados -- antes era h2 y no había ningún h1 en ninguna página). */
.my-header > .container > h1 {
    margin: 0;
    font-weight: var(--ce-font-weight-bold);
    /* Texto en blanco: .my-header solo se pinta dentro de .site-page-header,
       cuyo fondo es el degradado fijo de marca (oscuro por construcción, ver
       ese bloque) -- el color de marca sobre color de marca no se lee. */
    color: var(--ce-color-neutral-0);
}

/* Secciones de la home: fondo a todo el ancho, alternado por widgets/index.php.
   El espaciado entre secciones vive aquí (padding, no margin) para que el fondo
   alterno cubra todo el hueco sin colapsar contra el margin de los widgets. */
.home-section { padding: 64px 0; }
/* .home-section-first envuelve solo hero-noticias (.ce-hero--marca): con
   padding-bottom heredado de .home-section, el fondo de marca se quedaba
   64px más alto que el propio .ce-hero, dejando una franja de color de
   marca colgando por debajo del hero antes de "Directo a" -- se anula
   por completo, no solo el padding-top. */
.home-section-first { padding: 0; }

/* Fondo "tinte" alterno de la home (Fase C, W6 correction): antes un
   style="" por petición con tint_color($config['color_primario']) inline
   (public/index.php). El valor está DERIVADO DEL PREAJUSTE DE MARCA, no de
   un dato arbitrario por fila de BD, así que su token vive en
   celosia_brand_css() junto a los diez pasos de la escala (lib/celosia_css.php)
   y se sirve por los mismos 7 ficheros preset-*.css pre-compilados que ya
   entrega inc_menu.php — no una regla nueva por petición. */
.home-section-tinted { background-color: var(--ce-color-brand-tint); }

/* El fondo de marca de hero-noticias YA NO vive aquí (ni en
   .home-section-first, que ya no lleva .home-section-tinted en el
   marcado -- public/index.php). Vivió aquí brevemente (dos commits
   atrás): la sección y .ce-hero--marca calculaban el mismo degradado
   sobre DOS anchos distintos (sección completa vs. .ce-hero dentro del
   .container, ~24px más estrecho por su gutter), y esa diferencia se
   veía como una costura justo donde empezaba la caja. Ahora el propio
   widget (.widget-hero-noticias-wrapper, más abajo) es el elemento a
   sangre completa Y es el mismo ancho que .ce-hero --sin .container de
   por medio--, así que solo hay un degradado real, no dos que intentan
   coincidir. */

/* Botón circular de solo icono (compartir, etc.) — Bootstrap 5 no trae esta variante */
.btn-icon.btn-round {
    border-radius: 50%;
    width: 2.375rem;
    height: 2.375rem;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Ficha de etapa educativa (estudiar/*, centro/agenda/; Fase D,
   sdd/celosia-componentes W4): dialecto de reemplazo del antiguo bloque
   .etapa-* (definido aquí mismo hasta W4.5, cuando el último adoptante
   —centro/agenda/index.php— migró y el bloque .etapa-* se retiró por
   completo; tasks #349 W4.1-4.5). El contenedor de tarjeta de resumen
   pasa a .ce-card sin modificador (celosia-componentes.css le basta). El
   acordeón de detalle co-clasea .ce-card con .ficha-detalle porque
   necesita comportamiento propio (summary, marcador, chevron, separador
   al abrir) fuera del contrato de .ce-card — mismo patrón que .ce-card +
   .agenda-card-acento en W5. El resto de clases
   (-intro/-subtitulo/-grid/-resumen-titulo/-resumen-texto/-imagen) es
   solo tipografía/layout sin equivalente en celosia-componentes.css.
   Adoptantes: estudiar/bachillerato (W4.1, pilote), estudiar/educacion-
   secundaria-obligatoria (W4.2), estudiar/educacion-permanente/{espa,
   btopa} (W4.3), estudiar/{formacion-profesional,curso-especializacion}
   (W4.4), centro/agenda (W4.5, solo -detalle/-detalle-icono/-detalle-
   chevron/-detalle-cuerpo: la tarjeta de evento ya usaba .ce-card desde
   W5). */
.ficha-intro { font-size: 1.05rem; color: var(--ce-color-semantic-text-secondary); max-width: 760px; margin-bottom: 32px; }
.ficha-subtitulo { font-size: var(--ce-font-size-lg); font-weight: var(--ce-font-weight-bold); margin: var(--ce-space-7) 0 var(--ce-space-4); }
.ficha-subtitulo:first-child { margin-top: 0; }

.ficha-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 18px;
    margin-bottom: 8px;
}
.ficha-resumen-titulo {
    font-weight: var(--ce-font-weight-bold);
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--ce-color-semantic-text-secondary);
    margin-bottom: 8px;
}
.ficha-resumen-texto { font-size: .92rem; color: var(--ce-color-semantic-text-primary); margin: 0; }

.ficha-detalle {
    margin-bottom: 12px;
}
.ficha-detalle summary {
    font-weight: var(--ce-font-weight-bold);
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 10px;
}
.ficha-detalle summary::-webkit-details-marker,
.ficha-detalle summary::marker { display: none; content: ''; }
.ficha-detalle-chevron {
    margin-left: auto;
    font-size: .8rem;
    color: var(--ce-color-semantic-text-secondary);
    transition: transform .2s ease;
}
.ficha-detalle[open] .ficha-detalle-chevron { transform: rotate(90deg); }
.ficha-detalle[open] summary {
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--ce-color-semantic-border-subtle);
}
.ficha-detalle-cuerpo { font-size: .95rem; color: var(--ce-color-semantic-text-primary); }
.ficha-detalle-cuerpo > *:last-child { margin-bottom: 0; }

.ficha-detalle-icono { color: var(--ce-color-semantic-text-secondary); width: 18px; text-align: center; flex-shrink: 0; }

/* Etiqueta de tipo de programa, encima de .ficha-intro (rediseño de contenido,
   adoptantes: los 6 index.php de detalle bajo estudiar/). Se probó primero como
   cabecera a sangre con foto + velo + título propio (mismo lenguaje que
   .estudiar-card--foto), pero duplicaba el título de la página -- .site-page-header
   (inc_menu.php) ya lo pinta en su propio <h2>. Retirada esa cabecera (petición
   expresa tras verla en producción) y también la miniatura de foto que había
   antes (.ficha-imagen, retirada por completo -- petición expresa): estos 6
   detalles ya no muestran ninguna imagen, solo la pastilla de tipo + el texto,
   con .ficha-grid a la anchura completa del contenido. */
.ficha-tipo-tag { margin-bottom: var(--ce-space-2); }

/* Datos rápidos (evolución de .ficha-grid/-resumen-titulo/-resumen-texto de
   arriba, mismas clases): añade un icono delante de cada etiqueta para que se
   lea como panel de datos de un vistazo, no como una lista de definiciones. */
.ficha-resumen-titulo { display: flex; align-items: center; gap: var(--ce-space-2); }
.ficha-resumen-icono { color: var(--ce-color-brand-600); font-size: .85rem; }

/* Secciones prioritarias (Requisitos de acceso / Salidas profesionales, las
   dos que de verdad deciden si a alguien le interesa el programa): se sacan
   del acordeón y se muestran siempre abiertas, justo debajo de los datos
   rápidos -- el resto de secciones scrapeadas (Plan de formación, Tus
   estudios en Europa...) se quedan en el acordeón de siempre, ahora bajo
   "Más detalles" en vez de "Toda la información" porque ya no lo es todo. */
.ficha-prioritaria { margin-bottom: var(--ce-space-5); }
.ficha-prioritaria-titulo {
    display: flex;
    align-items: center;
    gap: var(--ce-space-2);
    font-size: var(--ce-font-size-lg);
    font-weight: var(--ce-font-weight-bold);
    margin: 0 0 var(--ce-space-3);
}
.ficha-prioritaria-titulo i { color: var(--ce-color-brand-600); width: 20px; text-align: center; }

/* Cierre con llamada a la acción real: solo aparece si el destino existe de
   verdad (cita previa activa, mismo criterio Y que widgets/widget-custom.php
   y lib/buscador_sitio.php -- nunca se ofrece un servicio apagado), si no cae
   a un enlace de vuelta al listado general, nunca vacío. */
.ficha-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--ce-space-3);
    margin-top: var(--ce-space-7);
    padding: var(--ce-space-5);
    background-color: var(--ce-color-semantic-surface-sunken);
    border-radius: var(--ce-size-radius-container);
}
.ficha-cta p { margin: 0; font-weight: var(--ce-font-weight-bold); color: var(--ce-color-semantic-text-primary); }

/* ============================================================
   Cabecera del sitio + offcanvas de menú completo (antes bloque "B2"
   inline en inc_menu.php, <style> tras el offcanvas). Trasladado aquí
   verbatim, ya tokenizado, en la Tarea 6 (design §3e: "una vez que B2/B3
   no contienen PHP, se pueden mover tal cual a site.css" — recomendado,
   no obligatorio; se hace aquí para dejar inc_menu.php con un solo
   <style> inline, el del emisor de marca de la Tarea 4a).
   Las 7 llamadas a accessible_color($config['color_primario']) de este
   bloque devolvían siempre el mismo valor (mismo argumento en las 7),
   así que se sustituyen todas por el mismo token --ce-color-semantic-action
   — comportamiento idéntico al de antes, ahora sin PHP interpolado.
   ============================================================ */
.site-header { background: var(--ce-color-semantic-surface); border-bottom: 1px solid var(--ce-color-semantic-border-subtle); padding: 14px 0; }
.site-header-brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--ce-color-semantic-text-primary); }
.site-header-brand-title { font-size: 1.15rem; font-weight: 600; }
/* Crespón de luto: visible solo si $config['mostrar_crespon'] (checkbox en la intranet,
   Administración > Página web > Apariencia) está activo Y existe public/assets/img/crespon.svg
   -- el archivo en sí se sube aparte, directamente al servidor, no desde ese formulario. */
.site-header-crespon { height: 32px; width: auto; }
/* Único @media (prefers-color-scheme: dark) de este fichero, a propósito (ver la nota del
   encabezado: el resto del sitio reasigna tokens --ce-*, no hace falta un bloque dedicado).
   Este caso es distinto: el color del crespón está horneado en los píxeles del SVG (relleno
   negro/gris), no es un token de Celosía -- no hay nada que reasignar. Sobre el fondo oscuro
   de --ce-color-semantic-surface (neutral-800) un lazo casi negro se pierde por completo; se
   invierte a un gris claro, único ajuste posible sin editar el propio SVG por duplicado. */
@media (prefers-color-scheme: dark) {
    .site-header-crespon { filter: invert(1); }
}
/* Centros con denominación larga (p.ej. "I.E.S. Politécnico Jesús Marín / I.E.S. Poeta
   García Gutierrez") no caben en una línea junto al logo y el botón de menú en móvil con
   el tamaño de escritorio -- se reduce logo y texto solo por debajo de 768px. */
@media (max-width: 767px) {
    .site-header-brand { gap: 8px; }
    .site-header-brand img { height: 32px; }
    .site-header-brand-title { font-size: .85rem; line-height: 1.2; }
}
.site-header-nav { list-style: none; gap: 26px; margin: 0; padding: 0; }
.site-header-nav-link {
    color: var(--ce-color-semantic-text-primary);
    text-decoration: none;
    font-size: .95rem;
    font-weight: var(--ce-font-weight-medium);
    padding-bottom: 4px;
    border-bottom: 2px solid transparent;
    transition: color var(--ce-duration-fast) ease, border-color var(--ce-duration-fast) ease;
}
.site-header-nav-link:hover {
    color: var(--ce-color-semantic-action-hover);
    border-bottom-color: var(--ce-color-semantic-action-hover);
}
.site-header-nav-link.active {
    color: var(--ce-color-semantic-action);
    border-bottom-color: var(--ce-color-semantic-action);
}
.site-header-nav-link.active:hover {
    color: var(--ce-color-semantic-action-hover);
    border-bottom-color: var(--ce-color-semantic-action-hover);
}
.site-header-toggle {
    border: 0;
    background: none;
    color: var(--ce-color-semantic-action);
    font-size: 1.3rem;
    margin-left: 24px;
    padding: 4px 6px;
    line-height: 1;
}
/* .offcanvas.site-offcanvas (dos clases), no .site-offcanvas a secas: el propio elemento
   también lleva "offcanvas offcanvas-end" de Bootstrap, cuya regla .offcanvas.offcanvas-end
   (0,2,0) le ganaba siempre a una regla de una sola clase (0,1,0) -- este ancho llevaba
   escrito así desde el principio sin haber tenido efecto nunca (confirmado con
   getComputedStyle contra el sitio real: siempre se quedaba en los 400px de fábrica de
   Bootstrap). Misma razón para todas las reglas .site-offcanvas de ancho de este fichero. */
.offcanvas.site-offcanvas {
    /* 100% del ancho y alto reales del viewport, en cualquier tamaño de pantalla (antes solo
       a partir de 768px, un cajón de 320px por debajo) -- left:0 + width:auto (con el
       right:0 de fábrica de .offcanvas-end), no width:100vw: 100vw incluye el hueco de la
       barra de scroll del documento, así que con scroll vertical visible desbordaba un
       puñado de píxeles fuera del viewport real (confirmado con getComputedStyle: left
       llegaba a valer -20px). left/right sin width fijo se ajusta siempre al ancho
       disponible, con o sin scrollbar. */
    left: 0;
    width: auto;
    max-width: none;
    height: 100vh;
    height: 100dvh;
    /* A petición expresa: se abre de golpe, no con el desplazamiento de derecha a
       izquierda de fábrica de Bootstrap. Igual de válido que el propio soporte de
       prefers-reduced-motion de Bootstrap (misma técnica: transition-duration en 0
       hace que su JS no espere ningún "transitionend" que nunca llegaría). */
    transition: none;
}
/* La cabecera del offcanvas ya no lleva su propio título/botón genéricos: reutiliza
   .site-header-brand/.site-header-toggle tal cual (mismas clases que la cabecera real), así
   que no necesita reglas propias de tamaño de fuente aquí -- las hereda de site-header. */
/* .offcanvas-header y .offcanvas-body traen de fábrica su propio padding horizontal
   (1rem, Bootstrap) ADEMÁS del que ya pone el .container que hay dentro de cada uno --
   el resultado en móvil era un doble margen (16px de fábrica + 12px del .container, y en
   las filas del menú +16px más de .site-menu-cabecera) que no coincidía con el margen real
   del resto del sitio (solo los 12px del .container). En escritorio ya se anulaba (ver
   @media 768px más abajo); en móvil faltaba la misma corrección. Solo horizontal: el
   vertical (16px arriba/abajo) se queda como está, es el .container quien debe marcar el
   margen lateral, en cualquier tamaño de pantalla. */
.site-offcanvas .offcanvas-header,
.site-offcanvas .offcanvas-body {
    padding-left: 0;
    padding-right: 0;
}

/* ============================================================
   Menú principal del offcanvas (.site-menu): maestro-detalle a dos columnas
   en tablet/desktop, acordeón vertical de una sola columna en móvil --
   mismo patrón que el menú de telefonica.com/es/ (explorado a petición
   expresa). Un solo árbol de marcado (inc_menu.php): en móvil
   .site-menu-panel aparece en flujo normal bajo su categoría (Bootstrap
   Collapse de fábrica); a partir de 768px se saca de flujo y se posiciona a
   la derecha de la lista, siempre anclada arriba, sin importar qué fila se
   haya abierto -- igual que en la referencia. Sin JS propio: el
   abrir/cerrar (uno solo a la vez, vía data-bs-parent) lo da el Collapse de
   Bootstrap ya cargado; .accordion-button reutiliza el chevron reskinnado
   y las variables --bs-accordion-active-* de celosia-bootstrap.css, así
   que la fila expandida ya se resalta sin declarar un color nuevo aquí. */
/* Sin borde por fila, en ningún tamaño de pantalla: el aire entre categorías ya basta para
   separarlas (mismo criterio que a partir de 768px, ver más abajo). .accordion-item trae de
   fábrica un borde izquierdo/derecho propio (var(--bs-accordion-border-*)) pensado para que
   cada item se vea como una caja apilada -- tampoco lo queremos aquí. */
.site-menu-item,
.accordion-item.site-menu-item { border: 0; }
.site-menu-cabecera {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    /* El resaltado de hover/activo es el fondo de ESTA caja, no el del enlace: el padding
       vive aquí (no en .site-menu-link) para que el texto respire dentro del resaltado en
       vez de quedar pegado a su borde. */
    padding: 4px 16px;
    border-radius: var(--ce-size-radius-container);
    transition: background-color var(--ce-duration-fast) ease;
}
.site-menu-link {
    display: block;
    /* flex-grow:0 (no 1): si creciera para ocupar el hueco hasta el botón +/x, el
       border-bottom de .active se vería igual de ancho, no ajustado al texto. Sin grow, el
       propio .site-menu-cabecera (justify-content:space-between) sigue empujando el botón al
       otro extremo de la fila igual que antes. */
    flex: 0 1 auto;
    padding: 12px 0 10px;
    color: var(--ce-color-semantic-text-primary);
    text-decoration: none;
    font-size: 1rem;
    font-weight: var(--ce-font-weight-medium);
    /* Mismo recurso que .site-header-nav-link (menú superior): borde inferior transparente
       reservado siempre, para que no cambie de tamaño al activarse -- solo cambia de color. */
    border-bottom: 2px solid transparent;
    transition: color var(--ce-duration-fast) ease, border-color var(--ce-duration-fast) ease;
}
.site-menu-link:hover { color: var(--ce-color-semantic-action-hover); }
/* Página actual: mismo subrayado que ya usa .site-header-nav-link.active en el menú
   superior -- si solo cambiara el color, se confundía con el propio :hover. */
.site-menu-link.active {
    color: var(--ce-color-semantic-action);
    border-bottom-color: var(--ce-color-semantic-action);
}
/* Botón-icono, no una fila entera como el .accordion-button de fábrica: el
   texto navegable vive en .site-menu-link, aparte -- este control solo
   abre/cierra el detalle. Misma especificidad (0,2,0) que las reglas de
   Bootstrap que pisa (.accordion-button, .accordion-button:not(.collapsed)),
   así que gana por orden de cascada (site.css carga después), sin
   necesidad de !important. */
.site-menu-toggle.accordion-button {
    flex: 0 0 auto;
    width: auto;
    /* Sin padding a derecha: el de .site-menu-cabecera ya deja el hueco hasta el borde del
       resaltado. A la izquierda, el hueco hasta la línea separadora del enlace. */
    padding: 8px 0 8px 16px;
    background-color: transparent;
    box-shadow: none;
    /* Separador entre el enlace y el botón: sin él, el límite entre "voy a esta página" y
       "despliego el submenú" es solo un poco de aire, fácil de fallar al pulsar. */
    border-left: 1px solid var(--ce-color-semantic-border-subtle);
}
.site-menu-toggle.accordion-button:not(.collapsed) { background-color: transparent; }
/* Icono propio: "+" que gira 45° y se convierte en "×" al desplegar, con el color de marca
   -- sustituye por completo al chevron de fábrica de .accordion-button (Tarea 3 de
   celosia-bootstrap.css), que aquí no se usa. */
.site-menu-toggle.accordion-button::after { display: none; content: none; }
.site-menu-icon {
    position: relative;
    display: inline-block;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    transition: transform var(--ce-duration-normal) ease;
}
.site-menu-icon::before,
.site-menu-icon::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    background-color: var(--ce-color-semantic-action);
    transition: background-color var(--ce-duration-fast) ease;
}
.site-menu-icon::before { width: 100%; height: 2px; }
.site-menu-icon::after { width: 2px; height: 100%; }
.site-menu-toggle.accordion-button:not(.collapsed) .site-menu-icon { transform: rotate(45deg); }
/* La categoría desplegada se marca de forma permanente (no solo al pasar el ratón, que ya
   lo hace :hover más abajo): si no, en cuanto el ratón se aparta de la fila no queda ninguna
   pista de cuál de las categorías está abierta ahora mismo, más allá del propio icono. */
.site-menu-cabecera:has(.site-menu-toggle:not(.collapsed)) {
    background-color: var(--bs-accordion-active-bg);
}
.site-menu-cabecera:has(.site-menu-toggle:not(.collapsed)) .site-menu-link {
    color: var(--bs-accordion-active-color);
}
.site-menu-panel .accordion-body { padding: 0 0 12px; }
.site-menu-panel-titulo {
    display: none;
    margin: 0 0 16px;
    font-size: 1.4rem;
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-text-primary);
}
.site-menu-sublista li { border-bottom: 1px solid var(--ce-color-semantic-border-subtle); }
.site-menu-sublista a {
    display: block;
    padding: 10px 0 10px 16px;
    color: var(--ce-color-semantic-text-primary);
    text-decoration: none;
    transition: color var(--ce-duration-fast) ease;
}
.site-menu-sublista a:hover { color: var(--ce-color-semantic-action-hover); }
/* Mismo icono y tamaño que .widget-directo-a-externo (widgets/directo-a.php): un enlace
   externo se marca igual en todo el sitio, no solo en el menú. */
.site-menu-externo { font-size: .72rem; margin-left: 2px; }

@media (min-width: 768px) {
    /* El ancho/alto reales del offcanvas ya son 100% a cualquier tamaño (regla base de
       arriba); a partir de aquí, además, el CONTENIDO no se estira a lo ancho: usa el mismo
       .container (y por tanto el mismo max-width responsivo de Bootstrap) que el resto del
       sitio, para que el logotipo de la cabecera del menú quede exactamente alineado con el
       de la cabecera real, y las dos columnas no floten sueltas por una pantalla ancha. */
    .site-offcanvas .offcanvas-header { padding: 14px 0; }
    .site-offcanvas .offcanvas-body {
        padding: 32px 0 0;
        height: 100%;
        /* El scroll ya no lo lleva este contenedor entero: cada columna gestiona el suyo
           (la izquierda no lo necesita, la derecha si hace falta, ver .site-menu-panel). */
        overflow: hidden;
    }
    .site-menu-container { position: relative; height: 100%; padding-bottom: 48px; }

    .site-menu {
        width: 400px;
        flex: 0 0 auto;
        /* Divisor entre columnas: borde de la propia lista, no del
           offcanvas-body entero, para que arranque y termine justo con la
           altura de la lista. */
        border-right: 1px solid var(--ce-color-semantic-border-subtle);
        padding-right: 56px;
    }
    .site-menu-cabecera { padding: 8px 20px; }
    .site-menu-cabecera:hover { background-color: var(--ce-color-semantic-surface-sunken); }
    .site-menu-link {
        padding: 14px 0;
        font-size: var(--ce-font-size-md);
        font-weight: var(--ce-font-weight-bold);
    }

    .site-menu-panel {
        position: absolute;
        top: 0;
        bottom: 0;
        /* 456px = los 400px de .site-menu más sus 56px de padding-right (el divisor vive en
           ese borde) -- coincide a propósito con esos dos valores, si cambian ahí hay que
           cambiar este número también. Ancho relativo al propio .site-menu-container (no a
           100vw): así no hereda el mismo bug de scrollbar que el punto de arriba. */
        left: 456px;
        width: min(480px, calc(100% - 456px));
        overflow-y: auto;
        /* Sin transición de altura aquí: Bootstrap la anima vía JS
           (.collapsing) pensando en un panel EN FLUJO que empuja contenido
           hacia abajo: posicionado en columna aparte, ese crecimiento
           progresivo solo se vería como un parpadeo sin ningún beneficio
           visual real -- mejor que aparezca entero de golpe. */
        transition: none;
    }
    .site-menu-panel-titulo {
        display: block;
        margin: 0 0 32px;
        font-size: 2rem;
        font-weight: var(--ce-font-weight-regular);
    }
    .site-menu-sublista a { padding: 14px 16px; border-radius: var(--ce-size-radius-container); }
    .site-menu-sublista a:hover { background-color: var(--ce-color-semantic-surface-sunken); }
}
.site-offcanvas-sublista { margin: 0 0 8px; padding: 0 0 4px 16px; }
.site-offcanvas-sublista li { margin-bottom: 6px; }
.site-offcanvas-sublista a {
    color: var(--ce-color-semantic-action);
    text-decoration: none;
    font-size: .92rem;
}
.site-offcanvas-sublista a:hover { text-decoration: underline; }

/* ============================================================
   Miga de pan (antes bloque "B3" inline en inc_menu.php). Trasladado
   aquí verbatim, ya tokenizado, misma razón que el bloque anterior.
   ============================================================ */
/* Franja de marca que envuelve la miga de pan + el título de página interior
   (inc_menu.php, .site-page-header). Mismo patrón que .ce-hero--marca /
   .site-footer: fondo FIJO de marca (tokens --ce-color-brand-*, literales,
   no semánticos -- no cambia con claro/oscuro, es la misma franja siempre),
   así que el texto de dentro pasa a blanco/tinte de marca claro en vez de
   los colores semánticos que usaban miga y título cuando vivían sobre fondo
   neutro. */
.site-page-header {
    background: linear-gradient(135deg, var(--ce-color-brand-600), var(--ce-color-brand-900));
    /* Mismo motivo que .ce-hero--marca más abajo: el anillo de foco sencillo
       necesita blanco, no el azul de acción, sobre este fondo fijo.
       --ce-color-focus-ring (no --ce-color-semantic-action, del que también
       depende el fondo de .ce-btn) para no pintar de blanco los botones. */
    --ce-color-focus-ring: var(--ce-color-neutral-0);
}
.site-breadcrumb { padding: 14px 0; }
.site-breadcrumb .container { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.site-breadcrumb-puntos { display: inline-flex; gap: 4px; margin-right: 4px; }
.site-breadcrumb-puntos span {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--ce-color-neutral-0);
    display: inline-block;
}
.site-breadcrumb-item {
    font-size: .78rem;
    font-weight: var(--ce-font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .02em;
    text-decoration: none;
    color: var(--ce-color-neutral-0);
}
.site-breadcrumb-item:hover { text-decoration: underline; }
.site-breadcrumb-actual { color: var(--ce-color-brand-100); font-weight: 600; }
.site-breadcrumb-sep { font-size: .6rem; color: var(--ce-color-brand-100); }
/* Separador entre miga y título SOLO cuando ambos están presentes: usa un
   hermano adyacente (miga inmediatamente seguida de .my-header), no :first-child
   en .my-header -- así una página sin miga (home, si algún día tuviera
   .site-page-header) no arrastra un borde superior huérfano. */
.site-breadcrumb + .my-header { border-top: 1px solid rgba(255,255,255,.16); }

/* Tarea 7 (design U7): el bloque de 14 literales hardcodeados que vivía
   aquí (fondo #2a2a2a, tarjetas #333/#444, texto #ccc/#ddd/#aaa...) se
   retira sin sustituto directo. No hace falta: TODAS las reglas de arriba
   en este fichero ya usan var(--ce-color-semantic-*) (Tarea 6), y esos
   mismos tokens se reasignan a valores seguros para oscuro en
   public/assets/css/celosia.css dentro de su propio
   @media (prefers-color-scheme: dark) — cargado antes que este fichero
   en el <head> (design §0), así que el efecto es automático: cada regla
   de arriba "hereda" su versión oscura sin declarar nada aquí.
   Un efecto secundario intencionado de ese reapuntado: .ce-notice (y, en
   su día, el ya retirado .etapa-resumen-card) usa
   --ce-color-semantic-surface-sunken, que en oscuro es MÁS OSCURA que la
   superficie normal (neutral-900 < neutral-800) — al revés que el bloque
   literal anterior, donde la tarjeta (#333) era más CLARA que el fondo de
   página (#2a2a2a). Es el comportamiento nuevo y correcto (design §4b,
   "surface-sunken... inverted: sunken is *darker*"), no una regresión:
   verificado en vivo con Playwright/Edge, ver
   sdd/celosia-phase-b/apply-progress. */

/* Fase C, Tarea 5 (design §5.1, W4): bloques <style> puramente estáticos
   (sin interpolación PHP) extraídos verbatim, sin cambio alguno, de los
   ficheros donde vivían. Se mueven tal cual: los literales de color que
   contienen (#555, #e6e6e6, #1a1a1a...) NO se tokenizan aquí porque eso
   sería un cambio visual y esta unidad es zero-diff por contrato; su
   tokenización queda pendiente como seguimiento (ver commit de esta
   unidad). Prerrequisito de la Fase 10 (endurecimiento de CSP,
   style-src-elem 'self'): un <style> sin extraer bloquea la política. */

/* === widgets/documentos-departamento.php (antes <style nonce> inline) === */
.widget-doc-departamento-cabecera { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 24px; }
.widget-doc-departamento-titulo { font-size: var(--ce-font-size-xl); font-weight: var(--ce-font-weight-regular); margin: 0; }
.widget-doc-departamento-drive {
    display: flex;
    align-items: center;
    gap: 12px;
    background: #f7f9fb;
    border: 1px solid #e6e6e6;
    border-radius: 10px;
    padding: 16px;
    text-decoration: none;
    color: inherit;
    transition: box-shadow .15s ease, transform .15s ease;
}
.widget-doc-departamento-drive:hover { box-shadow: 0 4px 14px rgba(0,0,0,.08); transform: translateY(-2px); color: inherit; }
.widget-doc-departamento-drive .widget-doc-departamento-icono { flex: 0 0 auto; font-size: 1.4rem; width: 28px; text-align: center; }
.widget-doc-departamento-drive .widget-doc-departamento-nombre { flex: 1 1 auto; font-size: .95rem; font-weight: 600; }

/* === widgets/agenda-eventos.php (antes <style> inline) === */
/* Lo que Bootstrap no cubre: el ancho fijo en px del acceso (no hay utility para un flex-basis
en px), la rejilla auto-fill/minmax de las tarjetas de evento (el grid de Bootstrap es por nº de
columnas, no por ancho mínimo responsivo), el borde de color por evento (dinámico, va en el style
inline) y los fondos/colores propios de cada bloque. El resto (flex, paddings, gaps, rounded-pill,
mt-auto, flex-fill, fw-normal) son utilities de Bootstrap en el propio HTML -- rounded-3/shadow-sm/
bg-white se retiraron del marcado (sdd/celosia-componentes): la superficie de la tarjeta de evento
ahora es .ce-card (fondo/borde/radio dark-mode-aware, sin sombra por D1), y el estado vacío
.ce-notice (mismo motivo). Antes eran valores fijos (#fff vía bg-white, #fafafa, #ddd, #888, #666,
#1a1a1a, #bbb) que se quedaban en modo claro aunque el resto del sitio ya estuviera en oscuro. */
.widget-agenda-eventos-wrapper { margin: 0 auto; }
/* font-weight/margin vivían como utilidades de Bootstrap (fw-normal mt-0 mb-4) en el propio
   marcado -- movidos aquí, con tokens de Celosía, para que esta regla sea la única fuente de
   verdad de su tipografía, igual que el resto de títulos de widget. */
.widget-agenda-eventos-titulo { font-size: var(--ce-font-size-xl); font-weight: var(--ce-font-weight-regular); margin: 0 0 var(--ce-space-5); }

.widget-agenda-eventos-acceso {
    position: relative;
    /* Recorta el icono de fondo sobredimensionado a las esquinas
       redondeadas de la propia caja -- si no, sus puntas cuadradas
       asomarían por fuera del rounded-3. */
    overflow: hidden;
    flex: 0 0 220px;
    text-decoration: none;
    background: var(--ae-acceso-tinte);
}
@media (max-width: 767px) {
    .widget-agenda-eventos-acceso { flex: 0 0 auto; }
}
.widget-agenda-eventos-acceso:hover { text-decoration: none; opacity: .92; }
/* El icono de categoría pasa a ser una marca de agua de fondo (petición
   expresa): grande, girado y muy tenue, con el texto encima -- sustituye
   al icono pequeño de cabecera que llevaba antes. */
.widget-agenda-eventos-acceso-icono-fondo {
    position: absolute;
    right: -14px;
    bottom: -20px;
    z-index: 0;
    font-size: 6rem;
    line-height: 1;
    color: var(--ae-acceso-icono);
    opacity: .16;
    transform: rotate(-14deg);
    pointer-events: none;
}
.widget-agenda-eventos-acceso-titulo,
.widget-agenda-eventos-acceso-cta {
    position: relative;
    z-index: 1;
}
.widget-agenda-eventos-acceso-titulo { font-weight: 700; font-size: .98rem; color: var(--ce-color-semantic-text-primary); }
.widget-agenda-eventos-acceso-cta { font-size: .75rem; font-weight: 700; letter-spacing: .03em; color: var(--ce-color-semantic-text-primary); }
.widget-agenda-eventos-acceso-cta i { font-size: .7rem; margin-left: 4px; }

.widget-agenda-eventos-fila-eventos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 1rem;
    min-width: 0;
}
/* border-left-* como longhand aparte (no un `border` nuevo): dado que
   .ce-card YA declara un `border` de 1px en las 4 caras, un shorthand
   nuevo aquí se lo llevaría por delante. Con solo estos tres longhand se
   sobrescribe el lado izquierdo (grosor/color de categoría) y el resto de
   .ce-card (color semántico, radio, fondo) queda intacto -- mismo
   mecanismo que ya vencía a bg-white/border/shadow-sm de Bootstrap antes
   de este cambio, solo que ahora contra .ce-card en vez de utilities. */
.widget-agenda-eventos-card {
    border-left-width: 4px;
    border-left-style: solid;
    border-left-color: var(--ae-card-color);
    text-decoration: none;
}
.widget-agenda-eventos-card-fecha { font-size: .74rem; color: var(--ce-color-semantic-text-secondary); text-transform: uppercase; letter-spacing: .03em; font-weight: 600; }
.widget-agenda-eventos-card-nombre { font-weight: 700; font-size: .92rem; color: var(--ce-color-semantic-text-primary); line-height: 1.35; overflow-wrap: anywhere; }
.widget-agenda-eventos-card-lugar { font-size: .8rem; color: var(--ce-color-semantic-text-secondary); overflow-wrap: anywhere; }
.widget-agenda-eventos-card-lugar i { margin-right: 4px; }
.widget-agenda-eventos-card-cat { display: inline-block; padding: 2px 10px; font-size: .7rem; font-weight: 600; white-space: nowrap; background: var(--ae-card-tinte); color: var(--ae-card-texto); }

/* Mismo lenguaje visual que la caja de acceso de al lado (acento de color
   de categoría + icono de fondo grande y girado) en vez de un .ce-notice
   genérico igual para las tres filas -- así una fila vacía sigue leyendo
   "esto es Trámites" y no un aviso neutro sin relación con su categoría.
   border-left-* longhand, mismo motivo que en .widget-agenda-eventos-card:
   no pisa el resto del border de .ce-notice. */
.widget-agenda-eventos-fila-vacio {
    position: relative;
    overflow: hidden;
    font-size: .88rem;
    border-left-width: 4px;
    border-left-style: solid;
    border-left-color: var(--ae-vacio-color);
}
.widget-agenda-eventos-fila-vacio-icono-fondo {
    position: absolute;
    right: -10px;
    bottom: -16px;
    z-index: 0;
    font-size: 5rem;
    line-height: 1;
    color: var(--ae-vacio-icono);
    opacity: .1;
    transform: rotate(-14deg);
    pointer-events: none;
}
.widget-agenda-eventos-fila-vacio .ce-notice__icono,
.widget-agenda-eventos-fila-vacio .ce-notice__contenido {
    position: relative;
    z-index: 1;
}
.widget-agenda-eventos-fila-vacio .ce-notice__icono { color: var(--ce-color-semantic-text-secondary); font-size: 1.1rem; }

/* === public/centro/agenda/index.php ===
   Título simplificado de "Agenda de eventos" a "Agenda" y retirada la frase de intro
   ("Eventos del curso... hasta el mes actual") -- petición expresa, los filtros ya dejan
   claro qué se está viendo. .agenda-intro se retira por completo (dead code).
   De paso, ajustes menores en los estilos: .agenda-filtro/-mes-contador/-vacio usaban
   colores literales (#ddd/#fff/#555/#888/#bbb/#fafafa/#777), la misma clase de bug de modo
   oscuro ya cerrada en el resto del sitio esta ronda -- el resto de este fichero
   (.agenda-card-*) ya estaba en tokens. */
.agenda-filtros { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 36px; }
.agenda-filtro {
    border: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
    background: var(--ce-color-semantic-surface);
    border-radius: 999px;
    padding: 6px 16px;
    font-size: .85rem;
    font-weight: 600;
    color: var(--ce-color-semantic-text-secondary);
    cursor: pointer;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.agenda-filtro:hover { border-color: var(--agenda-filtro-color, var(--ce-color-semantic-border)); }
/* Fallback a los tokens de marca, no a un gris neutro fijo: es el estado activo por defecto
   ("Todos", sin --agenda-filtro-color/-texto propios porque no representa una categoría con
   color real) -- que se vea como el resto de estados "activo" del sitio (acción de marca),
   no como un gris suelto sin relación con la paleta. */
.agenda-filtro.is-activo {
    background: var(--agenda-filtro-color, var(--ce-color-semantic-action));
    border-color: var(--agenda-filtro-color, var(--ce-color-semantic-action));
    color: var(--agenda-filtro-texto, var(--ce-color-semantic-text-on-brand));
}

.agenda-mes-contador {
    margin-left: 4px;
    font-size: .78rem;
    font-weight: 600;
    color: var(--ce-color-semantic-text-secondary);
    white-space: nowrap;
}
.agenda-mes-contador.is-vacio { opacity: .65; }

/* .agenda-card RETIRADO (Fase D, sdd/celosia-componentes, W5): background/border/
   radio ahora los da .ce-card (celosia-componentes.css). El acento de color por
   categoría (borde izquierdo de 4px) no forma parte del contrato de .ce-card
   (superficie plana sin modificadores de color), así que sobrevive aquí como
   regla propia, con nombre distinto para no revivir el dialecto retirado. */
.agenda-card-acento { border-left-width: 4px; border-left-style: solid; border-left-color: var(--agenda-card-color); }
.agenda-card-fecha { font-size: .76rem; color: var(--ce-color-semantic-text-secondary); text-transform: uppercase; letter-spacing: .03em; font-weight: 600; }
.agenda-card-cat {
    display: inline-block;
    border-radius: 999px;
    padding: 3px 12px;
    font-size: .72rem;
    font-weight: 600;
    white-space: nowrap;
    background: var(--agenda-cat-tinte);
    color: var(--agenda-cat-texto);
}
.agenda-card-nombre { font-weight: 700; font-size: 1rem; color: var(--ce-color-semantic-text-primary); line-height: 1.35; overflow-wrap: anywhere; }
.agenda-card-descripcion { font-size: .88rem; color: var(--ce-color-semantic-text-secondary); line-height: 1.5; margin-bottom: 10px; overflow-wrap: anywhere; }
.agenda-card-descripcion a,
.agenda-card-detalles a { color: var(--ce-color-semantic-link); text-decoration: underline; }

.agenda-card-detalles { display: flex; flex-direction: column; gap: 6px; font-size: .82rem; color: var(--ce-color-semantic-text-secondary); overflow-wrap: anywhere; }
.agenda-card-detalles i { width: 16px; margin-right: 4px; color: var(--ce-color-semantic-text-secondary); }

.agenda-vacio {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: var(--ce-color-semantic-surface-sunken);
    border: 1px dashed var(--ce-color-semantic-border-subtle);
    border-radius: 10px;
    padding: 48px 24px;
}
.agenda-vacio-icono { font-size: 2rem; color: var(--ce-color-semantic-text-secondary); opacity: .65; margin-bottom: 14px; }
.agenda-vacio-texto { color: var(--ce-color-semantic-text-secondary); font-size: .95rem; }
.agenda-vacio-mes { padding: 28px 24px; }

/* Fase C, Fase 6 (design W5): conversión de atributos style="" estáticos a
   clases. Cada bloque siguiente sustituye a un atributo style inline puntual,
   sin cambio visual (mismo valor exacto). Un fichero por commit (design R7). */

/* inc_pie.php: logos de cofinanciación europea/autonómica, ahora en todas las
   páginas (antes solo en home). Vienen a color sobre fondo transparente,
   pensados para un fondo claro -- desde que el pie es oscuro a propósito
   (patrón unir.net, ver === inc_pie.php === más abajo) se leerían mal tal
   cual (p.ej. texto oscuro casi invisible sobre el fondo casi negro).
   invert()+contrast() los deja en blanco/gris claro; grayscale entra ANTES
   para que el azul saturado de la bandera de la UE no salga con un tono
   raro al invertirlo. */
.footer-logo-financiacion { max-height: 60px; filter: grayscale(1) invert(1) contrast(1.6); opacity: .85; }

/* public/instituto/calendario/index.php: leyenda de colores del calendario escolar */
.calendario-leyenda-autonomica { color: #8ECC5B; }
.calendario-leyenda-provincial { color: #FEFB49; }
.calendario-leyenda-local { color: #95E4FF; }
.calendario-leyenda-otras { color: #FD9BB1; }

/* plugins/calendar.class.php (antes style="" inline por día — Fase C, W6) */
.calendario-dia-enlace { text-decoration: none; }
/* Compuesto (2 clases) a propósito: la tabla de Bootstrap pinta el fondo de celda vía
   `.table>:not(caption)>*>*{background-color:var(--bs-table-bg)}` (especificidad 0,0,1,1), que
   gana a una única clase (0,0,1,0) — el `style=""` inline que sustituye esta regla nunca tenía
   ese problema porque un inline siempre gana. `.table .calendario-dia-evento` (0,0,2,0) gana de
   forma incondicional, sin depender del orden de las hojas de estilo (R7, mismo criterio que la
   Fase 6). */
.table .calendario-dia-evento { background-color: var(--calendario-dia-color); }


/* public/instituto/libros-texto/index.php: tabla de libros de texto por nivel */
.libros-texto-tabla { font-size: 0.75rem; }

/* === public/errors/error404.php ===
   Sin caja ni centrado (petición expresa, "como en la Junta"): icono a la izquierda, texto en
   columna aparte, todo alineado a la izquierda -- el título ya lo da el propio
   .site-page-header de la página (inc_menu.php, $pagina['titulo']), así que aquí no se repite
   como un <h1> más. error500.php/error503.php NO reutilizan estas clases (no pueden cargar
   site.css como todo lo demás en su cabecera): comparten el mismo concepto visual
   (icono + mensaje + disculpa), con su propio CSS embebido, no estas reglas. Contenido
   inspirado en la página de error de la Junta de Andalucía (icono de búsqueda + sugerencias
   accionables + disculpa), adaptado a lo que este sitio realmente ofrece. */
.error-pagina {
    display: flex;
    align-items: flex-start;
    gap: var(--ce-space-6);
}
.error-pagina-icono { flex: 0 0 auto; font-size: 3.5rem; color: var(--ce-color-semantic-text-secondary); opacity: .35; }
.error-pagina-contenido { flex: 1 1 auto; min-width: 0; }
.error-pagina-texto { color: var(--ce-color-semantic-text-secondary); max-width: 60ch; }
.error-pagina-lista {
    color: var(--ce-color-semantic-text-secondary);
    max-width: 60ch;
    margin: 0 0 var(--ce-space-4);
    padding-left: 1.2em;
}
.error-pagina .ce-btn { margin-top: var(--ce-space-4); }
@media (max-width: 575px) {
    .error-pagina { flex-direction: column; }
    .error-pagina-icono { font-size: 2.5rem; }
}

/* public/documentos/index.php (File Thingie, vendorizado): mensaje de error
   cuando no se puede abrir el directorio. Verificado por lectura de código,
   no por captura visual: la propia validación de la librería (ft_check_dir)
   redirige antes de llegar a esta rama en cualquier ?dir= inválido probado
   (302, nunca 200 con este mensaje) — parece una rama de fallback muerta
   en la práctica, no un estado alcanzable de forma fiable en local. */
.documentos-mensaje-vacio { margin: 40px 0; }

/* Fase C, Fase 9 (design, resto de W3): bloques <style> "dinámico-mixtos"
   (CSS mayormente estático con un puñado de interpolaciones del color de
   marca) convertidos a CSS externo con var(--ce-color-brand-*). Cada
   interpolación PHP se verificó contra el token que emite (comprobado con
   PHP CLI, no supuesto): accessible_color($config['color_primario']) es
   un no-op confirmado para las 7 paletas reales (tests/ColorFunctionsTest.php
   + apply-progress Fase 8) — el resultado ES el primario ES el paso 600,
   así que se sustituye por var(--ce-color-brand-600) sin ningún cambio de
   valor. Un fichero por commit (R7). */

/* === inc_pie.php (antes <style nonce> inline) === */
/* Patrón de referencia: unir.net (footer oscuro, marca + columnas de
   enlaces del propio menú, fila de logos, barra inferior con redes +
   copyright). Fondo SIEMPRE oscuro, a propósito -- no es un `.home-section`
   más que deba adaptarse a claro/oscuro: es la franja de cierre fija del
   sitio, igual que .ce-hero--marca es un fondo de marca fijo. Por eso usa
   tokens literales (--ce-color-neutral-900, --ce-color-brand-200/600), NO
   los semánticos -text-primary/-surface (esos SÍ cambian con el tema, y
   aquí el fondo no cambia nunca). */
.site-footer {
    margin-top: 20px;
    background: var(--ce-color-neutral-900);
    color: var(--ce-color-neutral-0);
    /* Mismo motivo que .ce-hero--marca: blanco, no el azul de acción, sobre
       este fondo oscuro fijo. --ce-color-focus-ring (no
       --ce-color-semantic-action, del que también depende el fondo de
       .ce-btn) para no pintar de blanco los botones. */
    --ce-color-focus-ring: var(--ce-color-neutral-0);
}
.site-footer a { text-decoration: none; }

.site-footer-top { padding: 48px 0 32px; }
/* 33.333% (1/3), no 1.4fr: repeat(auto-fit, ...) no puede convivir con otro
   track EN fr en la misma lista -- el cálculo de cuántas repeticiones caben
   se vuelve circular (¿cuánto ocupa el fr antes de saber cuántas
   repeticiones hay?) y el navegador lo resuelve colapsando todo a una sola
   columna de ancho completo (comprobado en vivo: gridTemplateColumns
   computaba a un único track). Un porcentaje sí es compatible con auto-fit
   (es un tamaño definido, no flexible, igual que un px) -- da el 1/3 pedido
   al logo sin fijar cuántas columnas de menú habrá, que las reparte a
   partes iguales el 2/3 restante. */
.site-footer-grid {
    display: grid;
    grid-template-columns: minmax(0, 33.333%) repeat(auto-fit, minmax(140px, 1fr));
    gap: 32px;
}
@media (max-width: 767px) {
    .site-footer-grid { grid-template-columns: 1fr; gap: 28px; }
}
.site-footer-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--ce-color-neutral-0);
}
.site-footer-brand-title { font-weight: 700; font-size: 1.05rem; line-height: 1.25; }
.site-footer-contacto {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 18px 0 0;
    padding: 0;
    font-size: .88rem;
    color: var(--ce-color-brand-200);
}
.site-footer-contacto li { display: flex; align-items: flex-start; gap: 10px; }
.site-footer-contacto i { flex: 0 0 auto; width: 16px; margin-top: .2em; text-align: center; }
.site-footer-contacto a { color: var(--ce-color-brand-200); }
.site-footer-contacto a:hover { color: var(--ce-color-neutral-0); text-decoration: underline; }
/* fa-phone (FA5 solid) trae el auricular "al revés" respecto al icono de teléfono clásico --
   se voltea en horizontal solo aquí (organigrama-contacto usa fa-phone-alt, un glyph distinto,
   sin este problema). */
.site-footer-contacto-icono-tel { transform: scaleX(-1); }
.site-footer-col-titulo {
    margin: 0 0 var(--ce-space-4);
    font-size: var(--ce-font-size-sm);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-neutral-0);
}
.site-footer-col-lista {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
    padding: 0;
}
.site-footer-col-lista a {
    color: var(--ce-color-brand-200);
    font-size: .88rem;
}
.site-footer-col-lista a:hover { color: var(--ce-color-neutral-0); text-decoration: underline; }

.site-footer-logos {
    padding: 32px 0;
    border-top: 1px solid rgba(255,255,255,.14);
}
.site-footer-logos-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 32px;
}
@media (max-width: 575.98px) {
    .site-footer-logos-row { flex-direction: column; gap: 24px; }
}
.site-footer-bottom {
    padding: 24px 0;
    border-top: 1px solid rgba(255,255,255,.14);
    font-size: .82rem;
}
.site-footer-copyright { color: var(--ce-color-brand-100); }
.site-footer-social-list {
    list-style: none;
    display: flex;
    gap: 10px;
    margin: 0;
    padding: 0;
}
.site-footer-social-list a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: var(--ce-size-border-width) solid rgba(255,255,255,.3);
    color: var(--ce-color-neutral-0);
    transition: background-color var(--ce-duration-fast), border-color var(--ce-duration-fast);
}
.site-footer-social-list a:hover {
    background: var(--ce-color-brand-600);
    border-color: var(--ce-color-brand-600);
}

/* === public/centro/organigrama/index.php ===
   Renombrada de "Organigrama" a "Equipo Directivo" (petición expresa: la página nunca mostró
   un organigrama real -- jerarquía, departamentos -- solo esto, el equipo en tarjetas; ver
   comentario en el propio index.php). Rediseño de contenido de paso: avatar circular (antes
   cuadrado con esquinas redondeadas, dialecto genérico de tarjeta) y el cargo pasa a .ce-tag
   en vez de texto plano gris, mismo tratamiento que ya usa el resto del sitio para etiquetar
   "tipo" de algo (categoría de noticia, tipo de ciclo...).

   También se cierran aquí varios literales de color que se quedaban fijos en modo claro
   (#555/#eee/#1a1a1a/#666/#888, la misma clase de bug ya cerrada en .novedades-card y
   .estudiar-* este mismo ciclo) -- el resto (flex, paddings, márgenes) son utilities de
   Bootstrap en el propio HTML. */
.organigrama-intro { font-size: 1.05rem; color: var(--ce-color-semantic-text-secondary); margin-bottom: 40px; max-width: 760px; }

.organigrama-avatar {
    width: 100%;
    max-width: 140px;
    aspect-ratio: 1 / 1;
    border-radius: 50%;
    background: var(--ce-color-brand-tint);
    color: var(--ce-color-brand-600);
    font-size: 2.6rem;
}
.organigrama-avatar-foto { overflow: hidden; background: var(--ce-color-semantic-surface-sunken); }
.organigrama-avatar-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }

.organigrama-nombre { font-weight: 700; font-size: 1.05rem; color: var(--ce-color-semantic-text-primary); margin-bottom: var(--ce-space-2); }
/* .organigrama-cargo ya no lleva tipografía/color propios: .ce-tag los trae completos
   (fondo/borde/radio/texto, dark-mode-safe) -- solo el centrado dentro de la tarjeta
   .text-center, que .ce-tag (inline-block) ya respeta sin ayuda. */
.organigrama-contacto { font-size: .82rem; }
.organigrama-contacto a { color: var(--ce-color-semantic-text-secondary); text-decoration: none; overflow-wrap: break-word; }
.organigrama-contacto a:hover { color: var(--ce-color-brand-600); }
.organigrama-contacto i { width: 14px; margin-right: 4px; }

/* === public/centro/index.php ===
   Rediseño de contenido. MVV pasa a .ce-card (borde/radio consistentes con el
   resto del sitio, mismo fondo brand-tint que ya tenía -- token dark-mode-
   safe, redirigido en celosia-bootstrap.css) con el icono en badge en vez de
   suelto en línea.

   Los accesos (.centro-enlace) adoptan el mismo lenguaje que la caja de
   acceso de widgets/agenda-eventos.php (.widget-agenda-eventos-acceso, ver
   más abajo): color de acento propio por enlace + icono grande a modo de
   marca de agua girada de fondo, en vez de su antiguo icono+texto centrado
   sin más -- petición expresa, para que Centro y su propio widget de agenda
   compartan identidad visual. El color de "Agenda" reutiliza a propósito el
   mismo naranja que la fila "Agenda del Centro" de ese widget (mismo
   destino); el resto son colores propios, sin taxonomía real detrás (a
   diferencia de calendario_categorias.color) -- misma naturaleza de
   decisión que elegir qué icono le corresponde a cada uno.

   De paso se cierra un bug de contraste real, ya detectado y documentado
   como pendiente (Fase D, sdd/celosia-componentes W5): el color de enlace
   POR DEFECTO de Bootstrap en este sitio no sigue en la práctica a
   --bs-link-color (correcto en oscuro), sino a --bs-link-color-rgb, que
   celosia-bootstrap.css deja fijo sin redirección de modo oscuro --
   .centro-enlace heredaba ese azul roto (~2.56:1 sobre su fondo en oscuro,
   no llega a 3:1 WCAG 1.4.11), igual que su icono (--ce-color-brand-600 fijo,
   mismo problema). Se corrige fijando el color explícitamente al alias
   semántico de texto (mismo criterio que .widget-agenda-eventos-acceso-
   titulo/-cta) en vez de heredar el comportamiento por defecto roto de
   Bootstrap. */
.centro-seccion-titulo {
    font-size: 1.3rem;
    font-weight: var(--ce-font-weight-bold);
    margin-bottom: var(--ce-space-5);
}
.centro-mvv-card {
    background: var(--ce-color-brand-tint);
    display: flex;
    flex-direction: column;
    gap: var(--ce-space-3);
}
.centro-mvv-titulo { font-size: 1.15rem; font-weight: var(--ce-font-weight-bold); color: var(--ce-color-brand-600); }

/* Lo que Bootstrap no cubre: el icono de viñeta de la lista de valores (no hay utility para
contenido ::before). */
.centro-mvv-lista li::before {
    content: "\f00c";
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    color: var(--ce-color-brand-600);
    margin-right: 8px;
    font-size: .8em;
}

.centro-enlace {
    position: relative;
    overflow: hidden;
    text-decoration: none;
    background: var(--ce-enlace-tinte);
    border-radius: var(--ce-size-radius-container);
}
.centro-enlace:hover { text-decoration: none; opacity: .92; }
/* Icono de categoría como marca de agua de fondo: grande, girado y muy tenue, con el
   texto encima -- mismo mecanismo que .widget-agenda-eventos-acceso-icono-fondo. */
.centro-enlace-icono-fondo {
    position: absolute;
    right: -14px;
    bottom: -20px;
    z-index: 0;
    font-size: 6rem;
    line-height: 1;
    color: var(--ce-enlace-icono);
    opacity: .16;
    transform: rotate(-14deg);
    pointer-events: none;
}
/* Variante para favicons reales (public/servicios/index.php): un <img> no responde a
   font-size, así que necesita su propio ancho/alto -- el resto (posición, opacity, giro)
   ya lo hereda de .centro-enlace-icono-fondo sin cambios. */
.centro-enlace-icono-fondo-img { width: 96px; height: 96px; object-fit: contain; }
.centro-enlace-titulo,
.centro-enlace-cta {
    position: relative;
    z-index: 1;
}
.centro-enlace-titulo { font-weight: var(--ce-font-weight-bold); font-size: .98rem; color: var(--ce-color-semantic-text-primary); }
.centro-enlace-cta { font-size: .75rem; font-weight: var(--ce-font-weight-bold); letter-spacing: .03em; color: var(--ce-color-semantic-text-primary); }
.centro-enlace-cta i { font-size: .7rem; margin-left: 4px; transition: transform var(--ce-duration-fast); }
.centro-enlace:hover .centro-enlace-cta i { transform: translateX(3px); }

/* === public/centro/plan-de-centro/index.php ===
   Esta página no había pasado por ninguna fase de migración a Celosía: usaba utilities crudas
   de Bootstrap (border rounded shadow-sm) en vez de .ce-card -- ese shadow-sm es justo lo que
   D1 prohíbe como afordancia en el resto del sitio. Aplicados los tokens manteniendo la MISMA
   estructura de siempre (petición expresa: no rediseñar el contenido, solo aplicar estilos) --
   incluida la caja dentro de caja en cada nivel de anidamiento (nivel 0 > R > R3, etc.), a
   propósito: es lo que deja claro de un vistazo qué documento cuelga de qué apartado. Un
   primer intento la había retirado por error (una .ce-card dentro de otra parecía sobrar);
   corregido tras comprobar el diseño real en producción. */
/* --ce-space-7 (3rem), no -5: petición expresa de más aire entre cajas padre -- son la
   unidad de nivel más alto (nivel 0), necesitan respirar más que las cajas anidadas dentro
   de ellas (.plan-centro-subseccion, más abajo, se queda en -4). */
.plan-centro-seccion { margin-bottom: var(--ce-space-7); }
.plan-centro-seccion:last-child { margin-bottom: 0; }
.plan-centro-titulo { font-size: var(--ce-font-size-lg); font-weight: var(--ce-font-weight-bold); margin-bottom: var(--ce-space-2); }
.plan-centro-descripcion { color: var(--ce-color-semantic-text-secondary); margin: 0; }

/* Subsecciones anidadas (nivel>0): .ce-card propia, igual que la sección de nivel 0 -- cada
   nivel de anidamiento se ve con su propio borde. */
.plan-centro-subseccion { margin-bottom: var(--ce-space-4); }
.plan-centro-subseccion:last-child { margin-bottom: 0; }
.plan-centro-subseccion-titulo { font-size: var(--ce-font-size-sm); font-weight: var(--ce-font-weight-bold); margin-bottom: var(--ce-space-2); }

.plan-centro-doc { display: flex; align-items: center; gap: var(--ce-space-2); margin-bottom: var(--ce-space-2); }
.plan-centro-doc:last-child { margin-bottom: 0; }
.plan-centro-doc i { color: var(--ce-color-brand-600); flex-shrink: 0; }
.plan-centro-doc a { color: var(--ce-color-semantic-link); text-decoration: none; overflow-wrap: break-word; }
.plan-centro-doc a:hover { text-decoration: underline; }

/* === public/documentos/index.php (File Thingie, solo CSS) === */
/* La librería vendored es de solo lectura en el sitio público (UPLOAD,
   CREATE, FILEACTIONS y ADVANCEDACTIONS están en FALSE en config.php), así
   que el rediseño no toca su lógica PHP de filesystem: solo envuelve la
   tabla que ya genera ft_make_body() en .ce-card y le da estilo a sus filas. */
.documentos-breadcrumb {
    background-color: var(--ce-color-semantic-surface-sunken);
    border-radius: var(--ce-size-radius-container);
    padding: var(--ce-space-3) var(--ce-space-4);
    margin-bottom: var(--ce-space-6);
}
.documentos-breadcrumb .breadcrumb {
    background: none;
    margin: 0;
    padding: 0;
    font-size: var(--ce-font-size-sm);
}
.documentos-breadcrumb .breadcrumb-item a { color: var(--ce-color-semantic-link); text-decoration: none; }
.documentos-breadcrumb .breadcrumb-item a:hover { text-decoration: underline; }
.documentos-breadcrumb .breadcrumb-item.active { color: var(--ce-color-semantic-text-secondary); }
.documentos-breadcrumb .breadcrumb-item + .breadcrumb-item::before { color: var(--ce-color-semantic-border); }

.documentos-card { padding: 0; overflow: hidden; }
.documentos-tabla { margin: 0; }
.documentos-tabla > tbody > tr { border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle); }
.documentos-tabla > tbody > tr:last-child { border-bottom: none; }
.documentos-tabla > tbody > tr:hover { background-color: var(--ce-color-semantic-surface-sunken); }
.documentos-tabla > tbody > tr.seperator { border-top: var(--ce-size-border-width-strong) solid var(--ce-color-semantic-border-subtle); }
.documentos-tabla td { border: none; padding: var(--ce-space-3) var(--ce-space-4); vertical-align: middle; }
.documentos-tabla td.name a { color: var(--ce-color-semantic-text-primary); text-decoration: none; font-weight: var(--ce-font-weight-medium); }
.documentos-tabla td.name a:hover { color: var(--ce-color-semantic-link); text-decoration: underline; }
.documentos-tabla td.name i { width: 1.4em; text-align: center; margin-right: var(--ce-space-2); }
/* css/ft.css (vendored, propio de File Thingie) fija el color del nombre a
   un literal #2c3e50 con selector por ID (#filelist tr.dir/.file > td.name a)
   -- (1,3,1) de especificidad, por encima de la regla de arriba, y en oscuro
   deja el texto casi invisible sobre .ce-card (≈1.1:1 sobre neutral-800).
   Se replica el mismo selector con !important en vez de tocar el CSS
   vendorizado: mismo patrón que los .text-* de Bootstrap justo debajo. */
#filelist tr.dir > td.name a,
#filelist tr.file > td.name a {
    color: var(--ce-color-semantic-text-primary) !important;
}
.documentos-tabla .float-right { color: var(--ce-color-semantic-text-secondary); font-size: var(--ce-font-size-sm); font-variant-numeric: tabular-nums; }
/* Iconos por tipo de fichero (mimetypes en ft_make_body()): las utilities
   .text-danger/-info/-success/-warning de Bootstrap NO están retinadas a
   Celosía (celosia-bootstrap.css solo redirige --bs-primary), así que en
   oscuro mostrarían el rojo/azul/verde por defecto de Bootstrap, sin
   verificar contraste. Se sobreescriben aquí a los tokens color.state, que
   ya tienen su propio valor oscuro calculado (celosia.css, 4.5:1+). */
.documentos-tabla .text-danger  { color: var(--ce-color-state-danger)  !important; }
.documentos-tabla .text-info    { color: var(--ce-color-state-info)    !important; }
.documentos-tabla .text-success { color: var(--ce-color-state-success) !important; }
.documentos-tabla .text-warning { color: var(--ce-color-state-warning) !important; }
.documentos-tabla .text-primary { color: var(--ce-color-semantic-link) !important; }
.documentos-tabla tfoot .documentos-resumen {
    border-top: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
    color: var(--ce-color-semantic-text-secondary);
    font-size: var(--ce-font-size-xs);
}

/* === public/servicios/index.php (Sorpréndeme) ===
   "Accesos directos" reutiliza .centro-enlace TAL CUAL (public/centro/index.php, arriba en
   este mismo fichero): mismo lenguaje de acceso rápido en toda la web. El color es cíclico
   por posición, no por título -- son enlaces libres editables desde la intranet (menu_enlaces),
   sin ninguna taxonomía real de la que deducir un color con sentido (mismo criterio que el
   color por área en areas/index.php). Páginas de interés / Blogs del profesorado pasan de una
   lista de texto plano a filas dentro de una .ce-card, mismo patrón que .documentos-tabla /
   .widget-doc-departamento-grid (icono + nombre a la izquierda, indicador de enlace externo a
   la derecha, fila con hover). */
.servicios-seccion-titulo {
    font-size: 1.3rem;
    font-weight: var(--ce-font-weight-bold);
    margin-bottom: var(--ce-space-5);
}

.servicios-lista {
    display: grid;
    grid-template-columns: 1fr;
    padding: 0;
    overflow: hidden;
}
.servicios-lista-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: var(--ce-space-3) var(--ce-space-4);
    text-decoration: none;
    color: var(--ce-color-semantic-text-primary);
    border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
}
/* Dos columnas a partir de "md" (768px, mismo corte que el col-md-6 anterior): por debajo,
   una sola columna -- varios títulos reales ("Consejería de Desarrollo Educativo y Formación
   Profesional") no caben cómodos a mitad de ancho en pantallas estrechas. El borde entre
   columnas va en la misma media query: en una sola columna no hay "columna derecha" de la que
   separarse. */
@media (min-width: 768px) {
    .servicios-lista { grid-template-columns: repeat(2, 1fr); }
    .servicios-lista-item:nth-child(odd) { border-right: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle); }
}
.servicios-lista-item:hover { background-color: var(--ce-color-semantic-surface-sunken); color: var(--ce-color-semantic-text-primary); }
.servicios-lista-item-icono { flex: 0 0 auto; width: 20px; text-align: center; color: var(--ce-color-semantic-text-secondary); font-size: .85rem; }
.servicios-lista-item-nombre { flex: 1 1 auto; min-width: 0; font-size: .92rem; font-weight: var(--ce-font-weight-medium); }
.servicios-lista-item-externo { flex: 0 0 auto; font-size: .75rem; color: var(--ce-color-semantic-text-secondary); opacity: .6; }

/* === public/areas/index.php ===
   Las tarjetas de departamento reutilizan .centro-enlace/-icono-fondo/-titulo/-cta
   TAL CUAL (definidas más arriba, junto a public/centro/index.php): mismo
   lenguaje visual de "acceso rápido" -- color de acento + icono en marca de
   agua -- a petición expresa, para no duplicar la regla en dos sitios. Aquí
   el color ya no es una elección editorial por enlace (como en Centro): se
   asigna por ÁREA de coordinación, cíclicamente sobre una paleta fija (ver
   $paletaColoresArea en el PHP), porque cada centro nombra sus áreas de
   forma distinta y no hay ninguna taxonomía de la que deducir un color con
   sentido -- iterar es la única estrategia que generaliza a cualquier centro. */
.dep-area { margin-bottom: 48px; }
.dep-area:last-child { margin-bottom: 0; }
.dep-area-titulo {
    font-size: var(--ce-font-size-lg);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-brand-600);
    margin-bottom: var(--ce-space-4);
}

/* === public/novedades/index.php (antes <style> inline, sin nonce) === */
/* accessible_color($config['color_primario']) -> var(--ce-color-brand-600) (no-op
confirmado). $config['color_primario'] usado directo como fondo -> mismo token (ES
el paso 600). contrasting_text_color($config['color_primario']) siempre devuelve
#ffffff para las 7 paletas reales (comprobado con PHP CLI) -- se usa
var(--ce-color-neutral-0), NO var(--ce-color-semantic-text-on-brand): ese alias
semántico se REAPUNTA en modo oscuro (a neutral-900, ver celosia.css), y el cálculo
PHP original era ciego al esquema de color (siempre el mismo valor en claro y en
oscuro) -- usar el alias introduciría un delta real en oscuro que no existía antes.
neutral-0 es #FFFFFF en las dos polaridades (no se reasigna en el bloque @media),
así que es la sustitución fiel. El sufijo alfa "40" (línea del focus ring de
paginación) se compone con la tripleta -rgb y el alfa decimal exacto (0x40/255 =
0.25098039215686274) para no perder precisión frente a el hex de 8 dígitos original. */
/* ============================================================
   Detalle de una noticia (public/novedades/index.php, vista con ?id=): antes una única
   columna centrada (.row.justify-content-md-center > .col-md-9) con todo apilado --
   compartir, etiquetas y relacionadas al final del cuerpo. Ahora alineado a la izquierda,
   con esas tres cosas en una columna aparte a la derecha (a petición expresa). */
.novedades-detalle { padding: 40px 0 60px; }
.novedades-detalle-meta {
    color: var(--ce-color-semantic-text-secondary);
    font-size: var(--ce-font-size-sm);
    margin: 0 0 var(--ce-space-5);
}
.novedades-detalle-meta a { color: inherit; }
.novedades-detalle-meta a:hover { color: var(--ce-color-brand-600); }
.novedades-detalle-imagen { margin-bottom: var(--ce-space-5); border-radius: var(--ce-size-radius-container); overflow: hidden; }
.novedades-detalle-imagen img { display: block; width: 100%; }
.novedades-detalle-extracto {
    font-size: var(--ce-font-size-lg);
    color: var(--ce-color-semantic-text-secondary);
    line-height: 1.5;
    margin: 0 0 var(--ce-space-5);
}
.novedades-detalle-texto { line-height: 1.7; }
.novedades-detalle-texto img { max-width: 100%; height: auto; border-radius: var(--ce-size-radius-container); }

.novedades-detalle-sidebar { display: flex; flex-direction: column; gap: var(--ce-space-6); }
@media (min-width: 992px) {
    /* Fija en pantalla mientras se hace scroll del cuerpo del artículo (que puede ser
       mucho más largo que la propia barra lateral) -- sin esto, en cuanto el cuerpo supera
       la altura de la barra, "Compartir"/"Etiquetas"/"Más en X" desaparecen de la vista
       enseguida y dejan de estar disponibles la mayor parte de la lectura. */
    .novedades-detalle-sidebar { position: sticky; top: var(--ce-space-6); align-self: flex-start; }
}
.novedades-detalle-sidebar-bloque { padding-top: var(--ce-space-5); border-top: 1px solid var(--ce-color-semantic-border-subtle); }
.novedades-detalle-sidebar-bloque:first-child { padding-top: 0; border-top: 0; }
.novedades-detalle-sidebar-label {
    display: block;
    font-size: var(--ce-font-size-xs);
    font-weight: var(--ce-font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--ce-color-semantic-text-secondary);
    margin: 0 0 var(--ce-space-3);
}
.novedades-detalle-tags { display: flex; flex-wrap: wrap; gap: var(--ce-space-2); }

/* Documentos adjuntos: al final del cuerpo del artículo (no en el sidebar -- a petición
   expresa, estilo "recursos descargables" tipo Telefónica: fila completa por documento,
   icono + nombre + formato/tamaño a la izquierda, botón de descarga a la derecha). */
.novedades-detalle-documentos { margin-top: var(--ce-space-8); padding-top: var(--ce-space-6); border-top: 1px solid var(--ce-color-semantic-border-subtle); }
.novedades-detalle-documentos-item + .novedades-detalle-documentos-item { margin-top: var(--ce-space-3); }
.novedades-detalle-documentos-item a {
    display: flex;
    align-items: center;
    gap: var(--ce-space-4);
    padding: var(--ce-space-4);
    background: var(--ce-color-brand-50);
    border-radius: var(--ce-size-radius-container);
    text-decoration: none;
    transition: background-color .15s ease, box-shadow .15s ease;
}
.novedades-detalle-documentos-item a:hover {
    background: var(--ce-color-semantic-action);
    box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}
.novedades-detalle-documentos-icono {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--ce-size-radius-container);
    background: var(--ce-color-semantic-surface);
    font-size: var(--ce-font-size-lg);
    color: var(--ce-color-semantic-action);
}
.novedades-detalle-documentos-info { flex: 1 1 auto; min-width: 0; }
.novedades-detalle-documentos-nombre { display: block; font-weight: var(--ce-font-weight-bold); color: var(--ce-color-semantic-text-primary); transition: color .15s ease; }
.novedades-detalle-documentos-meta { display: block; font-size: var(--ce-font-size-xs); color: var(--ce-color-semantic-text-secondary); margin-top: 2px; transition: color .15s ease; }
.novedades-detalle-documentos-item a:hover .novedades-detalle-documentos-nombre,
.novedades-detalle-documentos-item a:hover .novedades-detalle-documentos-meta { color: #fff; }
.novedades-detalle-documentos-descarga {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--ce-color-semantic-surface);
    color: var(--ce-color-semantic-action);
}
.novedades-detalle-documentos-item a:hover .novedades-detalle-documentos-descarga { background: #fff; color: var(--ce-color-semantic-action); }

.novedades-detalle-relacionadas { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--ce-space-4); }
.novedades-detalle-relacionadas a { display: flex; gap: var(--ce-space-3); text-decoration: none; }
.novedades-detalle-relacionadas img { width: 72px; height: 72px; object-fit: cover; border-radius: var(--ce-size-radius-container); flex-shrink: 0; }
.novedades-detalle-relacionadas-titulo {
    display: block;
    font-size: var(--ce-font-size-sm);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-text-primary);
    line-height: 1.3;
}
.novedades-detalle-relacionadas a:hover .novedades-detalle-relacionadas-titulo { color: var(--ce-color-brand-600); }
.novedades-detalle-relacionadas-fecha { display: block; font-size: var(--ce-font-size-xs); color: var(--ce-color-semantic-text-secondary); margin-top: var(--ce-space-1); }

.novedades-wrapper { padding: 40px 0 60px; }

.novedades-buscador {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 28px;
}
.novedades-buscador-input {
    flex: 1 1 320px;
    position: relative;
}
.novedades-buscador-input i {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    color: #999;
    font-size: .9rem;
}
/* --ce-size-radius-container (8px, "tarjeta, aviso, campo" -- celosia.css)
   en vez del 999px suelto de antes: decisión explícita del propietario, tras
   notar que un campo casi en píldora quedaba raro junto al anillo de foco
   sencillo (que usa --ce-size-radius-sm, mucho más discreto). Mismo token en
   TODOS los campos de formulario "a mano" del sitio (fuera de los
   .form-control de Bootstrap, que ya usan --bs-border-radius = --ce-size-
   radius-md de forma consistente y no formaban parte de la queja). */
.novedades-buscador-input input {
    width: 100%;
    border: 1px solid #ddd;
    border-radius: var(--ce-size-radius-container);
    padding: 10px 16px 10px 38px;
    font-size: .95rem;
}
.novedades-buscador-input input:focus {
    border-color: var(--ce-color-brand-600);
}
.novedades-buscador-cat {
    flex: 0 1 220px;
    border: 1px solid #ddd;
    border-radius: var(--ce-size-radius-container);
    padding: 10px 16px;
    font-size: .95rem;
    background: #fff;
}
.novedades-buscador-btn {
    border: none;
    border-radius: var(--ce-size-radius-container);
    padding: 10px 22px;
    font-weight: 600;
    font-size: .95rem;
    background: var(--ce-color-brand-600);
    color: var(--ce-color-neutral-0);
}
.novedades-buscador-limpiar {
    color: #888;
    font-size: .9rem;
    text-decoration: none;
    white-space: nowrap;
}
.novedades-buscador-limpiar:hover { text-decoration: underline; }

.novedades-tag-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: #f4f4f4;
    border-radius: 8px;
    padding: 10px 16px;
    margin-bottom: 24px;
    font-size: .9rem;
}
.novedades-tag-banner a { color: var(--ce-color-brand-600); text-decoration: none; }
.novedades-tag-banner a:hover { text-decoration: underline; }

.novedades-tags-destacados {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 14px;
    margin: 8px 0 32px;
    padding-bottom: 24px;
    border-bottom: 1px solid #eee;
}
.novedades-tags-destacados-label {
    font-size: .78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #888;
    flex: 0 0 auto;
}
.novedades-tags-destacados-lista { display: flex; flex-wrap: wrap; gap: 8px; }
.novedades-tags-destacados-lista a {
    display: inline-block;
    background: #f4f4f4;
    border: 1px solid #e2e2e2;
    border-radius: 999px;
    padding: 4px 14px;
    font-size: .8rem;
    font-weight: 600;
    color: var(--ce-color-brand-600);
    text-decoration: none;
}
.novedades-tags-destacados-lista a:hover { text-decoration: underline; }

/* Páginas del sitio que se aproximan a la búsqueda (no noticias) -- misma
   estructura visual que .novedades-tags-destacados de arriba, pero con
   tokens Celosía (dark-mode-aware) en vez de los hex fijos de ese bloque
   más antiguo: no se toca ese bloque para no ampliar el alcance de esta
   tarea, pero sí se evita repetir el mismo hex sin token en código nuevo. */
.novedades-paginas-relacionadas {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 14px;
    margin: 8px 0 32px;
    padding-bottom: 24px;
    border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
}
.novedades-paginas-relacionadas-label {
    font-size: .78rem;
    font-weight: var(--ce-font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--ce-color-semantic-text-secondary);
    flex: 0 0 auto;
}
.novedades-paginas-relacionadas-lista { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.novedades-paginas-relacionadas-lista a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background-color: var(--ce-color-semantic-surface-sunken);
    border: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
    border-radius: var(--ce-size-radius-full);
    padding: 4px 14px;
    font-size: .8rem;
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-link);
    text-decoration: none;
}
.novedades-paginas-relacionadas-lista a i { font-size: .7em; }
.novedades-paginas-relacionadas-lista a:hover { text-decoration: underline; }

.novedades-total { text-align: right; color: #888; font-size: .85rem; margin-bottom: 10px; }

/* Estado vacío del buscador/listado (spec celosia-empty-state-exception):
   pierde el color de severidad de `alert-warning` -- "no hay resultados"
   no es un aviso real, ver decisión del propietario. Tratamiento neutro
   consistente con .agenda-vacio / .widget-doc-departamento-vacio /
   .widget-ultimas-novedades-vacio; queda fuera de .ce-notice a propósito
   (mismo criterio que esos tres). */
.novedades-vacio {
    background: #fafafa;
    border: 1px dashed #ddd;
    border-radius: 10px;
    padding: 24px;
    text-align: center;
    color: #777;
    font-size: .95rem;
}

.novedades-grid { display: grid; gap: 24px; margin-bottom: 24px; }
.novedades-grid-2 { grid-template-columns: repeat(2, 1fr); }
.novedades-grid-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 767px) {
    .novedades-grid-2, .novedades-grid-3 { grid-template-columns: 1fr; }
}

/* .novedades-card retirado (sdd/celosia-componentes W3, cierre): la
   superficie de tarjeta (fondo/borde/radio/sombra) ahora la da .ce-card/
   .ce-card--flush (celosia-componentes.css). El layout de columna que
   aportaba (display:flex; flex-direction:column, para que
   .novedades-card-body crezca hasta llenar la altura de fila del grid)
   se expresa en el marcado con las utilidades de Bootstrap d-flex/
   flex-column, ver novedades/index.php:novedades_render_card(). */
.novedades-card-img-wrap { display: block; position: relative; background: #eee; }
.novedades-card-img { width: 100%; height: 170px; object-fit: cover; display: block; }
.novedades-card-lg .novedades-card-img { height: 260px; }
.novedades-card-img-placeholder { background: linear-gradient(135deg, #ddd, #eee); }
/* Migrado a .ce-btn/.ce-btn--icon (sdd/celosia-componentes W7): el círculo
   con box-shadow y background/color fijos (dialecto vendored) se retira --
   ahora vienen de .ce-btn (--ce-color-semantic-action/-text-on-brand, ya
   con redirección dark-mode, cosa que el brand-600/neutral-0 directos de
   aquí NUNCA tuvieron) y .ce-btn--icon (borde visible, radio cuadrado 4px,
   sin sombra, D1). Solo queda aquí el POSICIONAMIENTO y el tamaño de
   instancia: min-height/min-width se sobrescriben porque es un icono
   puramente decorativo (aria-hidden) dentro de la tarjeta -- la tarjeta
   entera es clicable vía .stretched-link en el título, no un control
   independiente -- no debe forzarse al touch-target de 44px de .ce-btn. */
.novedades-card-flecha {
    position: absolute;
    right: 12px;
    bottom: 12px;
    width: 36px;
    height: 36px;
    min-height: 36px;
    min-width: 36px;
    padding: 0;
    font-size: .85rem;
    transition: transform .2s ease;
}
/* Se dispara con el :hover de toda la tarjeta, no solo de la imagen (antes
   .novedades-card-img-wrap era el propio <a>; ahora el enlace real es
   .stretched-link en el título y cubre la tarjeta entera vía su ::after,
   así que el hover que importa es el de .ce-card). */
.ce-card:hover .novedades-card-flecha { transform: scale(1.1); }
.novedades-card-body { padding: 16px 18px; flex: 1 1 auto; }
.novedades-card-lg .novedades-card-body { padding: 20px 22px; }
.novedades-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: .76rem; margin-bottom: 8px; }
/* Mantiene el .ce-tag de categoría clicable por encima del ::after del
   stretched-link del título (z-index:1, celosia-bootstrap.css/Bootstrap) --
   acotado a este selector concreto (no ".ce-card .ce-tag" genérico) para no
   alcanzar a otras tarjetas con .ce-tag que no usan stretched-link, como
   widgets/oferta-educativa.php, ver comentario en celosia-componentes.css. */
.novedades-card-meta .ce-tag { position: relative; z-index: 2; }
/* .novedades-card-fecha ya NO es compartida con .novedades-destacada
   (sdd/celosia-componentes W9): destacada usa ahora .ce-hero__fecha,
   tokenizada, consolidada con widgets/hero-noticias.php — ver el bloque
   .ce-hero__* más abajo. Este selector y su sobreescritura .ce-card
   descendiente solo alcanzan ya a novedades_render_card(). */
.novedades-card-fecha { color: #888; text-transform: uppercase; letter-spacing: .03em; font-weight: 600; }
.ce-card .novedades-card-fecha { color: var(--ce-color-semantic-text-secondary); }
/* .novedades-card-cat retirado (sdd/celosia-componentes W3): la pastilla
   de categoría del cluster de novedades usa .ce-tag desde
   celosia-componentes.css, ver novedades/index.php. */
.novedades-card-titulo { font-size: 1rem; font-weight: 700; line-height: 1.3; margin: 0; }
.novedades-card-lg .novedades-card-titulo { font-size: 1.2rem; }
/* .novedades-card-titulo y -resumen son exclusivas de .novedades-card
   (destacada usa sus propias -titulo/-resumen), así que sí se retintan
   directamente a tokens semánticos (sdd/celosia-componentes W3, cierre):
   el literal #1a1a1a/#555 solo era seguro mientras el fondo de la tarjeta
   era #fff fijo; con .ce-card el fondo ahora responde a modo oscuro y el
   texto debe hacerlo también para seguir siendo legible. */
.novedades-card-titulo a { color: var(--ce-color-semantic-text-primary); text-decoration: none; }
.novedades-card-titulo a:hover { color: var(--ce-color-brand-600); }
.novedades-card-resumen { font-size: .88rem; color: var(--ce-color-semantic-text-secondary); margin: 8px 0 0; }

.novedades-paginacion { margin-top: 36px; }
.novedades-paginacion .pagination { gap: 6px; flex-wrap: wrap; }
.novedades-paginacion .page-link {
    border: none;
    border-radius: 999px !important;
    min-width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #333;
    font-size: .9rem;
    background: #f4f4f4;
}
.novedades-paginacion .page-link:hover {
    background: #e9e9e9;
    color: #111;
}
.novedades-paginacion .page-item.active .page-link {
    background: var(--ce-color-brand-600);
    color: var(--ce-color-neutral-0);
    font-weight: 700;
}
.novedades-paginacion .page-item.disabled .page-link {
    background: transparent;
    color: #ccc;
}
.novedades-paginacion .page-link:focus {
    box-shadow: 0 0 0 .2rem rgba(var(--ce-color-brand-600-rgb), 0.25098039215686274);
}

.novedades-destacadas-wrap { display: flex; flex-direction: column; gap: 24px; margin-bottom: 32px; }
/* .novedades-destacada, -texto, -titulo, -resumen, -flecha, -img-wrap,
   -img (exactas) retiradas — consolidadas en .ce-hero / .ce-hero__*
   junto con widgets/hero-noticias.php (sdd/celosia-componentes W9). */

/* === widgets/directo-a.php (antes <style> inline, sin nonce) === */
.widget-directo-a-wrapper { margin: 0 auto; }
.widget-directo-a-titulo {
    font-size: var(--ce-font-size-xl);
    font-weight: var(--ce-font-weight-regular);
    margin: 0 0 var(--ce-space-5);
}
.widget-directo-a-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
}
.widget-directo-a-cols-1 { grid-template-columns: 1fr; }
.widget-directo-a-cols-2 { grid-template-columns: repeat(2, 1fr); }
.widget-directo-a-cols-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 991px) {
    .widget-directo-a-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 575px) {
    .widget-directo-a-grid { grid-template-columns: 1fr; }
}
/* .widget-directo-a-caja retirado (Fase D, sdd/celosia-componentes, W2): la
   superficie de tarjeta ahora la da .ce-card (celosia-componentes.css) en
   el propio <details>; .widget-directo-a-item solo se ocupa del estado
   abierto/cerrado del acordeón (ver [open] más abajo), no del aspecto. */
.widget-directo-a-cabecera {
    display: flex;
    align-items: center;
    gap: 10px;
    list-style: none;
}
.widget-directo-a-cabecera::-webkit-details-marker,
.widget-directo-a-cabecera::marker { display: none; content: ''; }
.widget-directo-a-icono {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 36px;
    height: 36px;
    font-size: 1rem;
    color: var(--ce-color-semantic-action);
    background: color-mix(in srgb, var(--ce-color-semantic-action) 14%, transparent);
    border-radius: var(--ce-size-radius-full);
}
.widget-directo-a-item-titulo { font-weight: 700; font-size: var(--ce-font-size-lg); }
.widget-directo-a-chevron {
    margin-left: auto;
    font-size: .8rem;
    color: #999;
    transition: transform .2s ease;
}
.widget-directo-a-item[open] .widget-directo-a-chevron { transform: rotate(90deg); }
.widget-directo-a-item[open] .widget-directo-a-cabecera {
    padding-bottom: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid #eee;
}
@media (max-width: 575px) {
    .widget-directo-a-cabecera { cursor: pointer; }
}
@media (min-width: 576px) {
    .widget-directo-a-cabecera { cursor: default; pointer-events: none; }
    .widget-directo-a-chevron { display: none; }
}
.widget-directo-a-enlaces { list-style: none; margin: 0; padding: 0; }
.widget-directo-a-enlaces li { margin-bottom: 8px; }
.widget-directo-a-enlaces li:last-child { margin-bottom: 0; }
.widget-directo-a-enlaces a {
    color: var(--ce-color-brand-600);
    text-decoration: none;
    font-size: .92rem;
    font-weight: var(--ce-font-weight-medium);
    transition: color .15s ease;
}
.widget-directo-a-enlaces a:hover {
    color: var(--ce-color-semantic-action-hover);
    text-decoration: underline;
}
.widget-directo-a-externo { font-size: .72rem; margin-left: 2px; }

/* === widgets/hero-noticias.php + public/novedades/index.php
   (novedades_render_destacada) — sdd/celosia-componentes W9 ===
   .widget-hero-noticias-* (antes <style> inline, sin nonce) y
   .novedades-destacada-* han sido dos dialectos independientes con el
   mismo marcado duplicado desde antes de esta fase. Se consolidan aquí
   en un único bloque .ce-hero__* (el contenedor .ce-hero — grid,
   min-height, sin radio, ancho de contenido ~75% — vive en
   celosia-componentes.css, ver ese fichero para el porqué del reparto).
   Retirados por completo: ambas clases -wrapper/-card exactas (ver
   comentarios de retirada más abajo, en el hueco que dejaron). El
   antiguo contrasting_text_color($config['color_primario']) ->
   --ce-color-neutral-0 fijo del CTA circular se sustituye por
   --ce-color-semantic-action + --ce-color-semantic-text-on-brand (ver
   celosia-componentes.css): el fondo ahora es dark-mode-aware (brand-300
   en oscuro, un tono CLARO) y un texto blanco fijo dejaría de tener
   contraste sobre él — mismo tipo de trampa que W3/W5 encontraron en
   .novedades-card/.agenda-card. */
/* Bootstrap fija 0.6s para el fundido cruzado de .carousel-fade -- con
   textos de longitud distinta entre diapositivas (título/resumen no
   tienen el mismo nº de líneas), esa duración deja ver un solape de
   texto viejo+nuevo ilegible durante buena parte de la transición. Se
   acorta a --ce-duration-fast (120ms, ya usado en el resto del sitio
   para afordancias rápidas) -- mismo efecto, pero el solape pasa
   demasiado rápido para leerse, en vez de eliminar el fundido cruzado. */
#hero-noticias-carousel.carousel-fade .carousel-item {
    transition-duration: var(--ce-duration-fast);
}

.ce-hero__contenido {
    position: relative;
    /* padding-right pasa de 90px (mucho más que el resto de lados, reserva
       de la migración original) a igualar el resto -- en una columna de
       ~465px eso se comía casi un cuarto del ancho de texto sin motivo: la
       flecha (46px, bottom:24px right:24px) solo necesita hueco vertical
       abajo, no un margen lateral en toda la altura de la columna. Ese
       hueco se reserva con padding-bottom, mismo patrón que ya usa la
       versión móvil (24px 24px 70px) más abajo. */
    /* padding-left a 0 -- a petición expresa: .widget-hero-noticias-container
       ya es un .container real (misma clase que usa "Directo a" y el
       resto del sitio), así que su propio gutter ya separa el texto del
       borde; un padding-left adicional aquí lo desalineaba del resto del
       contenido de la página, que arranca justo en el borde del
       .container, no 28px más adentro. */
    padding: 28px 28px 78px 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
}
@media (max-width: 767px) {
    .ce-hero__contenido { padding: 24px 24px 70px 0; order: 2; }
}
/* Carrusel de home (widgets/hero-noticias.php), a petición expresa: en móvil la imagen
   pasa a sangre completa y el texto recupera un margen simétrico -- tres ajustes
   deliberadamente NO aplicados al genérico .ce-hero__contenido/.ce-hero__imagen-wrap de
   arriba, que también sirve a la sección "Destacadas" de novedades/index.php (sin
   carrusel, sin este pedido). */
@media (max-width: 767px) {
    /* :has(), no la clase a secas: .widget-hero-noticias-container también envuelve la
       tarjeta de buscador del estado "sin destacadas" (misma clase, ver
       widgets/hero-noticias.php rama else) -- esa sí debe seguir centrada dentro de su
       propio .container, con su gutter normal. */
    .widget-hero-noticias-container:has(#hero-noticias-carousel) {
        padding-left: 0;
        padding-right: 0;
    }
    /* El padding que perdió el .container de arriba se recupera aquí, EN AMBOS
       lados por igual (antes solo llevaba el derecho -- el izquierdo lo daba gratis
       el propio gutter del .container, así que el resultado no era simétrico: 0 a la
       izquierda del .ce-hero__contenido pero +24px de gutter, frente a 24px+12px de
       gutter a la derecha). También +16px de aire entre el resumen y la flecha: antes
       el padding-bottom (70px) coincidía en px exactos con el alto de la flecha más su
       offset (46px + 24px = 70px), así que el hueco real era 0, no un margen visible. */
    #hero-noticias-carousel .ce-hero__contenido {
        padding-left: calc(var(--bs-gutter-x, 1.5rem) * .5);
        padding-right: calc(var(--bs-gutter-x, 1.5rem) * .5);
        padding-bottom: 86px;
    }
}

.ce-hero__meta { display: flex; align-items: center; gap: 10px; font-size: .78rem; }
.ce-hero__fecha { color: var(--ce-color-semantic-text-secondary); text-transform: uppercase; letter-spacing: .03em; font-weight: 600; }

/* hyphens + line-clamp: una noticia con título/extracto largos no debe
   estirar la caja del hero -- se corta a un nº de líneas fijo y las
   palabras largas parten con guión en vez de desbordar. */
.ce-hero__titulo {
    font-size: var(--ce-font-size-2xl);
    font-weight: var(--ce-font-weight-bold);
    line-height: 1.2;
    margin: var(--ce-space-3) 0;
    /* .ce-hero__contenido es flex column: sin esto el min-width:auto por
       defecto de un flex item deja que la palabra más larga empuje el
       título fuera de la caja, inutilizando overflow-wrap/hyphens. */
    min-width: 0;
    overflow-wrap: break-word;
    -webkit-hyphens: auto;
    hyphens: auto;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}
.ce-hero__titulo a { color: var(--ce-color-semantic-text-primary); text-decoration: none; }
.ce-hero__titulo a:hover { color: var(--ce-color-brand-600); }
.ce-hero__resumen {
    font-size: .95rem;
    color: var(--ce-color-semantic-text-secondary);
    margin: 0;
    min-width: 0;
    overflow-wrap: break-word;
    -webkit-hyphens: auto;
    hyphens: auto;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

/* Afordancia = cambio de background-color únicamente (D1,
   celosia-componentes.css). El vendored llevaba box-shadow +
   transform:scale en ambos dialectos — ninguno de los dos se traslada. */
.ce-hero__flecha {
    position: absolute;
    right: 24px;
    bottom: 24px;
    width: 46px;
    height: 46px;
    /* Cuadrado-redondeado (4px, var(--ce-size-radius-md)), no circular --
       a petición expresa, para igualar a .ce-btn--icon (el botón-icono ya
       establecido en el resto del sitio, incluido el CTA de
       widgets/widget-custom.php dentro de su propio .ce-hero -- ver el
       comentario junto a .ce-btn--icon en celosia-componentes.css). Este
       era el único CTA de icono que seguía siendo literalmente circular. */
    border-radius: var(--ce-size-radius-md);
    background-color: var(--ce-color-semantic-action);
    color: var(--ce-color-semantic-text-on-brand);
    /* Es un <a>: sin esto hereda el subrayado global de enlace (celosia.css,
       a{text-decoration:...}), que se ve como una rayita bajo el icono. */
    text-decoration: none;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color var(--ce-duration-fast);
}
.ce-hero__flecha:hover { background-color: var(--ce-color-semantic-action-hover); }

.ce-hero__imagen-wrap { display: block; height: 100%; }
@media (max-width: 767px) {
    .ce-hero__imagen-wrap { order: 1; }
}
.ce-hero__imagen { width: 100%; height: 100%; min-height: 260px; object-fit: cover; display: block; }
.ce-hero__imagen-marcador { background: linear-gradient(135deg, var(--ce-color-semantic-surface-sunken), var(--ce-color-semantic-surface)); }

/* .ce-hero--marca: fondo de marca (referencia visual UNIR), modificador
   EXCLUSIVO de widgets/hero-noticias.php -- las otras dos instancias de
   .ce-hero (novedades-destacada, banner de cita previa) se quedan con el
   fondo neutro de la base, decisión tomada por separado para cada una.
   --ce-color-brand-* son pasos de escala fijos, no tokens semánticos: el
   mismo literal en claro/oscuro, no necesitan redirección -- lo que hay
   que verificar es contraste, no inversión.

   El fondo NO va aquí ni en .ce-hero__contenido: va en .home-section-first
   (public/index.php), la <section> que envuelve el widget entero, a sangre
   completa de verdad -- .ce-hero__contenido es solo ~31% de ese ancho
   (grid 5fr/7fr), así que un fondo puesto ahí (o peor, un degradado) nunca
   llega a ocupar "todo el ancho" que se pidió; y .home-section-tinted
   (compartida con cita-previa/oferta-educativa) sigue con su tinte claro
   sin cambios -- ver esa regla más abajo para .home-section-first.

   SÍ necesita SU PROPIO fondo opaco (no transparente) pese a que
   .home-section-first ya pinta el mismo color por debajo: con
   .carousel-fade, Bootstrap no hace un cross-dissolve real -- la
   diapositiva ENTRANTE sube a z-index:1 y se supone que TAPA a la
   SALIENTE (que se queda a opacity:1 debajo hasta el último instante,
   ver transition:opacity 0s .6s de Bootstrap). Si la entrante no tiene
   fondo propio opaco, no tapa nada, y el texto de la diapositiva de
   abajo se lee a través del de la de arriba durante toda la transición.
   Mismo degradado que .home-section-first, recalculado sobre el ancho
   de .ce-hero (no el de la sección, ~24px más estrecho por el gutter
   del .container en escritorio -- diferencia imperceptible, y en móvil
   son el mismo ancho exacto). */
.ce-hero--marca {
    background: linear-gradient(135deg, var(--ce-color-brand-600), var(--ce-color-brand-900));
    background-attachment: fixed;
    /* El anillo de foco sencillo (celosia.css) usa --ce-color-focus-ring,
       que aquí sería un azul de marca casi invisible sobre este mismo fondo
       de marca -- se reasigna a blanco SOLO dentro de este contexto fijo,
       sin tocar la regla :focus-visible en sí (ya la alimenta vía cascada).
       Aparte de --ce-color-semantic-action a propósito: ese token también
       alimenta el fondo de .ce-btn (celosia-componentes.css) -- reasignarlo
       aquí pintaba de blanco tanto el fondo como el icono de los botones
       dentro de este contexto (bug real, ver commit que lo corrige). */
    --ce-color-focus-ring: var(--ce-color-neutral-0);
}
.ce-hero--marca .ce-hero__fecha { color: var(--ce-color-brand-100); }
/* El <h2> no fija color propio (solo ".ce-hero__titulo a", ver comentario
   ~L1679); pero los "..." del line-clamp los pinta el propio <h2>, no el
   <a> -- sin esto heredaban el texto oscuro por defecto en vez del blanco
   de marca. */
.ce-hero--marca .ce-hero__titulo { color: var(--ce-color-neutral-0); }
.ce-hero--marca .ce-hero__titulo a { color: var(--ce-color-neutral-0); }
.ce-hero--marca .ce-hero__titulo a:hover { color: var(--ce-color-brand-100); }
.ce-hero--marca .ce-hero__resumen { color: var(--ce-color-brand-100); }
/* .ce-tag por defecto es una píldora rellena (fondo surface-sunken),
   pensada para superficie neutra -- a petición expresa, aquí se vuelve
   contorno: fondo transparente, solo borde y texto en blanco. */
.ce-hero--marca .ce-tag {
    background-color: transparent;
    border-color: var(--ce-color-neutral-0);
    color: var(--ce-color-neutral-0);
    /* Empuja el tag al extremo derecho de .ce-hero__meta (flex), dejando
       la fecha anclada a la izquierda. */
    margin-left: auto;
}
.ce-hero--marca .ce-hero__flecha { background-color: var(--ce-color-neutral-0); color: var(--ce-color-brand-700); }
.ce-hero--marca .ce-hero__flecha:hover { background-color: var(--ce-color-brand-50); }

/* Tablet (768-991px, el "lg" de Bootstrap -- no hay un breakpoint propio
   de "tablet" en el proyecto, este es el corte habitual del propio
   Bootstrap para ese rango) a petición expresa: mismo layout apilado que
   móvil, no las 2 columnas de escritorio. SOLO para esta instancia
   (.ce-hero--marca) -- .ce-hero base (celosia-componentes.css) sigue
   cambiando a 2 columnas en 768px para novedades-destacada y el banner
   de cita previa, sin tocar. */
@media (min-width: 768px) and (max-width: 991px) {
    .ce-hero--marca {
        /* minmax(0, 1fr): mismo motivo que el mobile de .ce-hero base
           (celosia-componentes.css) -- 1fr suelto no tiene suelo 0. */
        grid-template-columns: minmax(0, 1fr);
        min-height: var(--ce-size-hero-min);
    }
    .ce-hero--marca .ce-hero__contenido {
        padding: 24px 24px 70px 0;
        order: 2;
    }
    .ce-hero--marca .ce-hero__imagen-wrap {
        order: 1;
    }
}

/* Indicadores del carrusel — widgets/hero-noticias.php únicamente,
   novedades-destacada no es un carrusel. Integrados DENTRO del hero
   (position:absolute, el valor por defecto real de .carousel-indicators
   en Bootstrap -- #hero-noticias-carousel ya es position:relative por
   su propia clase .carousel), alineados con el borde izquierdo de
   .ce-hero__contenido (mismo padding, 28px/24px) en vez de centrados a
   todo el ancho del carrusel -- así quedan sobre el color de marca,
   como en unir.net, en vez de en una fila aparte debajo. Esto también
   quita el hueco que dejaban fuera del hero (petición: que la altura
   del carrusel y de la sección coincidan "a base"). */
.ce-hero__indicadores {
    position: absolute;
    left: 28px;
    right: auto;
    bottom: 24px;
    z-index: 2;
    margin: 0;
    list-style: none;
}
@media (max-width: 767px) {
    .ce-hero__indicadores { left: 24px; bottom: 24px; }
}
.ce-hero__indicadores [data-bs-target] {
    background-color: color-mix(in srgb, var(--ce-color-neutral-0) 45%, transparent);
    width: 8px;
    height: 8px;
    /* Bootstrap declara este selector con border-top/bottom:10px solid
       transparent (para el estilo "raya" por defecto) -- en content-box eso
       suma 20px de alto reales por debajo del 8px especificado, deformando
       el punto en una píldora vertical. Se resetea explícitamente. */
    border: 0;
    border-radius: var(--ce-size-radius-full);
    opacity: 1;
    transition: background-color var(--ce-duration-fast), box-shadow var(--ce-duration-fast);
}
/* Activo = anillo (borde vía box-shadow, no crece el tamaño de caja) en vez
   de solo cambiar de color -- más legible como paginación, referencia UNIR.
   Blanco sólido + halo translúcido, no el par superficie/marca de antes:
   ahora se apoyan sobre .home-section-first (fondo de marca), no sobre una
   superficie neutra -- ese par se leería mal aquí. */
.ce-hero__indicadores .active {
    background-color: var(--ce-color-neutral-0);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ce-color-neutral-0) 40%, transparent);
}

/* --neutral: mismos indicadores, sobre .ce-hero SIN --marca (carousel de
   "Destacadas" en public/novedades/index.php) -- ahí el fondo es
   var(--ce-color-semantic-surface), no el degradado oscuro de marca fijo de
   arriba, así que los puntos blancos del bloque anterior se leerían casi
   invisibles (sobre todo en modo claro). Tokens semánticos (dark-mode-aware)
   en vez de los literales --neutral-0 fijos que sí tienen sentido cuando el
   fondo nunca cambia. */
.ce-hero__indicadores--neutral [data-bs-target] {
    background-color: color-mix(in srgb, var(--ce-color-semantic-text-secondary) 45%, transparent);
}
.ce-hero__indicadores--neutral .active {
    background-color: var(--ce-color-semantic-action);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ce-color-semantic-action) 25%, transparent);
}

/* .widget-hero-noticias-wrapper: a sangre completa (ya NO un .container
   de Bootstrap), pinta el fondo de marca a petición expresa -- en vez de
   depender de la <section> que lo envuelve en public/index.php (que es
   un wrapper neutro, sin clase -tinted). .widget-hero-noticias-container
   (nuevo, DENTRO de este wrapper) SÍ es un .container real: ahí vive
   .ce-hero, para que su contenido (texto e imagen) quede alineado con el
   mismo margen que usa el resto del sitio (cabecera, "Directo a"...) en
   vez de estirarse también él a sangre completa -- eso fue justo el bug
   de la iteración anterior, corregido a petición expresa (ver captura de
   pantalla): el fondo debía llegar a los bordes, el CONTENIDO no.

   Con .ce-hero de nuevo más estrecho que el wrapper, .ce-hero--marca
   sigue necesitando su propio fondo opaco para el enmascarado de
   carousel-fade (ver ese comentario, celosia-componentes.css). Dos
   cajas de anchos distintos con el MISMO degradado normalmente no
   coinciden en el borde donde una empieza dentro de la otra (el
   problema original) -- pero un degradado VERTICAL (180deg, no 135deg)
   depende solo de la posición Y y de la ALTURA de la caja, nunca de su
   ancho, y ambas cajas miden exactamente la misma altura (quedan a la
   misma línea de arriba y de abajo). Cambiar de diagonal a vertical
   hace que las dos cajas produzcan el mismo color en cada altura Y por
   construcción geométrica, sin necesitar que sus anchos coincidan. */
.widget-hero-noticias-wrapper {
    background: linear-gradient(135deg, var(--ce-color-brand-600), var(--ce-color-brand-900));
    background-attachment: fixed;
}

/* .widget-hero-noticias-card, .widget-hero-noticias-texto, -meta,
   -fecha, -cat, -titulo, -extracto, -flecha, -img-wrap, -img,
   -img-placeholder, -indicators (exactas) retiradas — consolidadas
   arriba en .ce-hero / .ce-hero__* (sdd/celosia-componentes W9). */

/* Sin destacadas (ver widgets/hero-noticias.php, rama else): foto a sangre
   completa + tarjeta traslúcida con el buscador, calcado del patrón de
   portada de juntadeandalucia.es (foto grande, tarjeta "¿En qué podemos
   ayudarte?" apoyada abajo con un buscador pegado al texto) -- CTA directo
   al buscador real de novedades/ (?q=, ver public/novedades/index.php:
   209/442), no una copia de sus estilos/colores institucionales, solo del
   patrón de composición. */
.widget-hero-noticias-wrapper--vacio {
    display: flex;
    align-items: center;
    min-height: var(--ce-size-hero-min);
    /* Mismo velo de marca que .widget-custom-banner/--secretaria,
       para que las tres fotos del sitio lean como una misma familia. */
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--ce-color-brand-600) 85%, transparent),
            color-mix(in srgb, var(--ce-color-brand-900) 85%, transparent)
        ),
        url('../img/hero-noticias-vacio-fondo.jpg') center / cover no-repeat;
}
@media (min-width: 768px) {
    .widget-hero-noticias-wrapper--vacio { min-height: var(--ce-size-hero-min-lg); }
}
/* Ancho vía clase real de Bootstrap (.col-md-8, en el propio marcado de
   hero-noticias.php dentro de .row.justify-content-center), no un width
   suelto aquí -- el proyecto sigue usando el grid de 12 columnas en todas
   partes, así que la caja se comporta como cualquier otra columna del
   sitio en vez de tener su propia regla de ancho aparte. */
.widget-hero-noticias-vacio {
    margin: 32px 0;
    padding: 22px 26px;
    text-align: left;
    /* Traslúcida (no opaca): dentro del patrón de referencia la foto se
       sigue viendo por detrás de la tarjeta. Neutral-900 fijo, NO el
       token semántico -text-primary: la tarjeta es blanca a propósito
       tanto en claro como en oscuro (como el fondo fijo de .ce-hero--
       marca), así que el texto tiene que ser oscuro también siempre --
       en modo oscuro -text-primary se vuelve casi blanco y desaparece
       sobre esta tarjeta. */
    background: color-mix(in srgb, var(--ce-color-neutral-0) 88%, transparent);
    border-radius: var(--ce-size-radius-md);
    color: var(--ce-color-neutral-900);
}
.widget-hero-noticias-vacio-titulo {
    margin: 0 0 var(--ce-space-4);
    font-size: var(--ce-font-size-lg);
    font-weight: var(--ce-font-weight-bold);
    line-height: 1.25;
}
.widget-hero-noticias-buscador { display: flex; }
.widget-hero-noticias-buscador input {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--ce-space-2) var(--ce-space-4);
    background: var(--ce-color-neutral-0);
    color: var(--ce-color-neutral-900);
    border: var(--ce-size-border-width) solid var(--ce-color-brand-600);
    border-right: 0;
    border-radius: var(--ce-size-radius-container) 0 0 var(--ce-size-radius-container);
    font: inherit;
    font-size: .88rem;
}
.widget-hero-noticias-buscador button {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ce-size-touch-comfortable);
    border: 0;
    border-radius: 0 var(--ce-size-radius-container) var(--ce-size-radius-container) 0;
    background: var(--ce-color-brand-600);
    color: var(--ce-color-neutral-0);
    font-size: 1rem;
    cursor: pointer;
    transition: background-color var(--ce-duration-fast);
}
.widget-hero-noticias-buscador button:hover { background: var(--ce-color-brand-700); }
.widget-hero-noticias-buscador button:disabled { opacity: .6; cursor: not-allowed; }
@media (max-width: 575px) {
    .widget-hero-noticias-vacio { margin: 24px 0; padding: 20px 24px; }
}

/* === widgets/ultimas-novedades.php (antes <style> inline, sin nonce) === */
.widget-ultimas-novedades-wrapper { margin: 0 auto; }
.widget-ultimas-novedades-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 24px;
    flex-wrap: wrap;
    gap: 8px;
}
.widget-ultimas-novedades-titulo { font-size: var(--ce-font-size-xl); font-weight: var(--ce-font-weight-regular); margin: 0; }
/* Estado vacío opcional ($_un_mostrar_vacio): mismo tratamiento neutro que .agenda-vacio /
   .widget-doc-departamento-vacio (spec celosia-empty-state-exception) -- fuera de .ce-notice
   a propósito, "todavía no hay novedades" no es un aviso real. */
.widget-ultimas-novedades-vacio {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: var(--ce-color-semantic-surface-sunken);
    border: 1px dashed var(--ce-color-semantic-border-subtle);
    border-radius: 10px;
    padding: 36px 24px;
}
.widget-ultimas-novedades-vacio-icono { font-size: 2rem; color: var(--ce-color-semantic-text-secondary); opacity: .65; margin-bottom: 14px; }
.widget-ultimas-novedades-vacio-texto { color: var(--ce-color-semantic-text-secondary); font-size: .95rem; }
.widget-ultimas-novedades-vertodas {
    color: var(--ce-color-brand-600);
    font-weight: 600;
    font-size: .95rem;
    text-decoration: none;
    white-space: nowrap;
}
.widget-ultimas-novedades-vertodas i { margin-left: 4px; font-size: .85em; }
.widget-ultimas-novedades-vertodas:hover { text-decoration: underline; }

.widget-ultimas-novedades-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    align-items: stretch;
}
@media (max-width: 767px) {
    .widget-ultimas-novedades-grid { grid-template-columns: 1fr; }
}

/* .ce-card/.ce-card--flush (celosia-componentes.css) dan fondo/borde/radio ya
   dark-mode-aware -- antes eran valores fijos (#fff, #e6e6e6, box-shadow),
   invisibles en modo oscuro (ver captura: estas dos cards se quedaban blancas
   mientras el resto del sitio ya estaba en oscuro). Sin box-shadow (D1): la
   sombra de 0 2px 10px no se traslada.
   -destacada usa --flush (imagen es el primer hijo, contrato limpio); en
   -secundaria la imagen va DESPUÉS del texto en el marcado (a propósito, ver
   el comentario del order:-1 más abajo), así que no encaja con el contrato
   ":first-child" de --flush -- se aplica solo .ce-card y se replica el
   padding:0/overflow:hidden a mano. */
.widget-ultimas-novedades-destacada { padding-bottom: 20px; }
.widget-ultimas-novedades-secundaria { padding: 0; overflow: hidden; }

.widget-ultimas-novedades-img-wrap { display: block; position: relative; background: var(--ce-color-semantic-surface-sunken); }
.widget-ultimas-novedades-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.widget-ultimas-novedades-img-wrap-lg .widget-ultimas-novedades-img { height: 340px; }
/* El radio de las esquinas de la imagen ya no se declara aquí: lo recorta el
   overflow:hidden + border-radius de la propia .ce-card que la envuelve
   (mismo mecanismo que .ce-card--flush > :first-child). */
.widget-ultimas-novedades-img-placeholder { background: var(--ce-color-semantic-surface-sunken); min-height: 100px; }

/* Migrado a .ce-btn/.ce-btn--icon (sdd/celosia-componentes W7) -- ver el
   comentario gemelo en .novedades-card-flecha arriba (mismo dialecto, mismo
   fix). Solo POSICIONAMIENTO + tamaño de instancia quedan aquí. */
.widget-ultimas-novedades-flecha {
    position: absolute;
    right: 14px;
    bottom: 14px;
    width: 42px;
    height: 42px;
    min-height: 42px;
    min-width: 42px;
    padding: 0;
    transition: transform .2s ease;
}
.widget-ultimas-novedades-flecha-sm { width: 32px; height: 32px; min-height: 32px; min-width: 32px; right: 10px; bottom: 10px; font-size: .85rem; }
/* Se dispara con el :hover de toda la tarjeta -- ver el comentario gemelo
   en .ce-card:hover .novedades-card-flecha arriba (mismo dialecto, mismo
   motivo: el enlace real es .stretched-link en el título, no la imagen). */
.ce-card:hover .widget-ultimas-novedades-flecha { transform: scale(1.1); }

.widget-ultimas-novedades-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 18px 20px 0;
    font-size: .78rem;
}
.widget-ultimas-novedades-secundaria .widget-ultimas-novedades-meta { padding: 16px 18px 0; }
/* Mismo motivo que .novedades-card-meta .ce-tag arriba: mantener el .ce-tag
   de categoría clicable por encima del ::after del stretched-link del
   título, acotado a este selector para no alcanzar a otras tarjetas. */
.widget-ultimas-novedades-meta .ce-tag { position: relative; z-index: 2; }
.widget-ultimas-novedades-fecha { color: var(--ce-color-semantic-text-secondary); text-transform: uppercase; letter-spacing: .03em; font-weight: 600; }
/* .widget-ultimas-novedades-cat retirado (sdd/celosia-componentes W3): la
   pastilla de categoría usa .ce-tag desde celosia-componentes.css, ver
   widgets/ultimas-novedades.php. */

.widget-ultimas-novedades-card-titulo { font-size: var(--ce-font-size-lg); font-weight: var(--ce-font-weight-bold); line-height: 1.3; margin: var(--ce-space-3) var(--ce-space-5) 0; }
.widget-ultimas-novedades-card-titulo-sm { font-size: var(--ce-font-size-md); margin: var(--ce-space-2) var(--ce-space-5) 0; }
.widget-ultimas-novedades-card-titulo a { color: var(--ce-color-semantic-text-primary); text-decoration: none; }
.widget-ultimas-novedades-card-titulo a:hover { color: var(--ce-color-brand-600); }

.widget-ultimas-novedades-secundarias {
    display: flex;
    flex-direction: column;
    gap: 24px;
}
.widget-ultimas-novedades-secundaria {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    flex: 1 1 0;
    min-height: 0;
}
.widget-ultimas-novedades-img-wrap-sm { flex: 0 0 42%; }
.widget-ultimas-novedades-img-wrap-sm .widget-ultimas-novedades-img { min-height: 100%; }
/* Selector de clase, no ">div" genérico: el envoltorio de imagen (antes <a>,
   ahora <div> desde que usa .stretched-link en el título en vez de ser él
   mismo el enlace) es OTRO div hijo directo de .widget-ultimas-novedades-secundaria.
   Un ">div" a secas enganchaba también a la imagen y le pisaba su propio
   flex:0 0 42% con este flex:1 1 auto (misma especificidad, pero ">div" con
   combinador de tipo puntúa más que una sola clase) -- regresión real
   detectada en producción tras ese cambio. */
.widget-ultimas-novedades-texto { flex: 1 1 auto; display: flex; flex-direction: column; padding-bottom: 16px; min-width: 0; }
@media (max-width: 575px) {
    .widget-ultimas-novedades-secundaria { flex-direction: column; }
    /* En el marcado, el texto va antes que la imagen (así en fila la imagen
       queda a la derecha en escritorio). Al apilarse en columna hereda ese
       orden -- texto arriba, imagen abajo -- que no coincide con la
       destacada (imagen arriba, texto abajo). order:-1 iguala el orden
       visual sin tocar el marcado ni el orden de tabulación. El radio ya no
       se fuerza a 0 aquí: lo decide el overflow:hidden de la .ce-card que
       envuelve, igual que en desktop -- ahora con la imagen arriba (order:
       -1) redondea las esquinas de arriba, a juego con la destacada. */
    .widget-ultimas-novedades-img-wrap-sm { flex: 0 0 auto; order: -1; }
    /* Misma altura que la destacada (ver -img-wrap-lg abajo): con la
       petición expresa de que ambas cajas se vean iguales en móvil, la
       diferencia de tamaño entre 340px (destacada) y 160px (secundaria) ya
       no tiene sentido. */
    .widget-ultimas-novedades-img-wrap-lg .widget-ultimas-novedades-img,
    .widget-ultimas-novedades-img-wrap-sm .widget-ultimas-novedades-img { height: 160px; }
}
/* En 2 columnas (>=768px), .widget-ultimas-novedades-secundarias hereda una
   altura definida vía align-items:stretch del grid, y flex:1 1 0 (arriba)
   reparte esa altura a partes iguales entre las 2 noticias secundarias. En
   1 columna esa altura deja de ser definida (el grid pasa a 2 filas
   independientes, cada una auto), así que flex:1 1 0 + min-height:0
   colapsan cada noticia a su flex-basis (0) en vez de a su contenido real.
   Debe declararse DESPUÉS de la regla base (misma especificidad, gana el
   orden de aparición) para que realmente la sustituya. */
@media (max-width: 767px) {
    .widget-ultimas-novedades-secundaria { flex: 0 0 auto; min-height: auto; }
}

/* === widgets/oferta-educativa.php (antes <style> inline, sin nonce) === */
/* El sufijo alfa "1f" del badge (0x1f/255 = 0.12156862745098039) se compone con la
tripleta -rgb, mismo criterio que public/novedades/index.php. */
.widget-oferta-educativa-wrapper { margin: 0 auto; }
.widget-oferta-educativa-titulo {
    font-size: var(--ce-font-size-xl);
    font-weight: var(--ce-font-weight-regular);
    margin: 0 0 var(--ce-space-5);
}

.widget-oferta-educativa-scroller {
    position: relative;
    margin: 0 -4px;
}
/* El velo que insinúa "hay más tarjetas" se desvanece hacia el color real
   de la sección (--ce-color-brand-tint, el fondo de .home-section-tinted
   que envuelve este widget en public/index.php), no hacia blanco fijo --
   antes quedaba como un manchón blanco propio en modo oscuro, encima de un
   fondo que ya no es blanco. */
.widget-oferta-educativa-scroller::before,
.widget-oferta-educativa-scroller::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 14px;
    /* Más estrecho (72px -> 40px) y sin llegar a opaco (color-mix al 55%
       en vez del brand-tint sólido): con las cards de foto, un bloque de
       color liso a todo alto tapando ~72px de la última tarjeta leía como
       un tapón, no como una insinuación de que hay más contenido. */
    width: 40px;
    pointer-events: none;
    z-index: 2;
    transition: opacity .25s ease;
}
.widget-oferta-educativa-scroller::before {
    left: 0;
    background: linear-gradient(to left, transparent, color-mix(in srgb, var(--ce-color-brand-tint) 55%, transparent));
    opacity: 1;
}
.widget-oferta-educativa-scroller::after {
    right: 0;
    background: linear-gradient(to right, transparent, color-mix(in srgb, var(--ce-color-brand-tint) 55%, transparent));
    opacity: 1;
}
.widget-oferta-educativa-scroller.is-start::before { opacity: 0; }
.widget-oferta-educativa-scroller.is-end::after { opacity: 0; }

/* Fila fija debajo del carrusel, alineada a la derecha (petición expresa) --
   antes los dos botones flotaban sobre los bordes izquierdo/derecho de las
   cards (position:absolute), tapando parcialmente la primera/última tarjeta.
   .ce-btn/.ce-btn--icon (sdd/celosia-componentes W7) siguen dando el
   círculo/cuadrado dark-mode-aware sin sombra (D1); aquí solo queda
   POSICIONAMIENTO de la fila + tamaño de instancia (40px, menor que el
   touch-target por defecto de .ce-btn). En los extremos del scroll se
   deshabilitan (:disabled, ya estilado en .ce-btn) en vez de ocultarse --
   más accesible que desaparecer un control interactivo. Visible también en
   móvil (petición expresa): antes se ocultaba por debajo de 575px dando
   por hecho que el swipe táctil bastaba, pero ya no hace falta esa
   suposición -- la fila se oculta sola cuando de verdad no hay nada que
   desplazar (.widget-oferta-educativa-wrapper--sin-scroll, más abajo). */
.widget-oferta-educativa-nav-row {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 12px;
}
.widget-oferta-educativa-nav {
    width: 40px;
    height: 40px;
    min-height: 40px;
    min-width: 40px;
    padding: 0;
    font-size: .85rem;
}
/* Con tan pocas tarjetas que crecen (flex-grow, ver .widget-oferta-
   educativa-card) hasta llenar la fila sin desbordar, no hay nada que
   desplazar -- la fila de botones se ocultaba a medias (los dos
   deshabilitados a la vez, ver JS updateEdges()) en vez de directamente no
   aparecer. Clase puesta por JS, misma comprobación de scrollWidth que ya
   se usaba para deshabilitarlos. */
.widget-oferta-educativa-wrapper--sin-scroll .widget-oferta-educativa-nav-row {
    display: none;
}

.widget-oferta-educativa-grid {
    display: flex;
    gap: 18px;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scroll-behavior: smooth;
    padding: 4px 4px 18px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    cursor: grab;
    user-select: none;
}
.widget-oferta-educativa-grid.is-dragging { cursor: grabbing; scroll-behavior: auto; }
.widget-oferta-educativa-grid::-webkit-scrollbar { display: none; }

.widget-oferta-educativa-card {
    position: relative;
    display: flex;
    flex-direction: column;
    /* grow:1, shrink:0, basis:250px -- no hace falta fijar a mano un
       "número mínimo de cards que caben sin desbordar" (varía con el ancho
       real del contenedor, no es una constante). Con pocas tarjetas, sobra
       espacio y crecen para repartírselo (hasta max-width); en cuanto no
       caben más a 250px cada una, shrink:0 impide que se compriman -- en
       vez de eso desbordan y activan el scroll (ver JS, updateEdges()). */
    flex: 1 0 250px;
    max-width: 340px;
    /* Alto fijo, no auto: con tarjetas de foto y de icono mezcladas en la
       misma fila (según si la ficha en JSON trae imagen o no -- ver
       widgets/oferta-educativa.php), sin esto cada una tomaría una altura
       distinta según su propio contenido y la fila quedaría escalonada. */
    height: 300px;
    scroll-snap-align: start;
    /* Fondo/borde/radio ya vienen de .ce-card (celosia-componentes.css),
       dark-mode-aware -- antes eran valores fijos (#fff/#e6e6e6) más
       box-shadow, que D1 prohíbe como afordancia y que además se quedaba
       fija en modo oscuro (card blanca sobre fondo oscuro). Solo el
       padding se ajusta aquí (escala de espaciado, no el 1rem por defecto
       de .ce-card: esta tarjeta necesita más aire). */
    padding: var(--ce-space-5) var(--ce-space-4);
    text-decoration: none;
}
.widget-oferta-educativa-card:hover .widget-oferta-educativa-cta i {
    transform: translateX(3px);
}

/* Variante con foto real (ficha en JSON con imagen -- data/estudiar/etapas o
   .../fp-{basica,medio,superior}/*.json, mismo dato que ya usan las páginas
   de detalle de estudiar/). A sangre completa (.ce-card--flush no encaja:
   aquí conviven una <img> Y un botón de flecha absolutamente posicionado
   como hermanos, no "un solo primer hijo"), con velo inferior para leer el
   texto encima de cualquier foto. El velo usa negro/blanco fijos (no
   tokens): es contraste contra la FOTO, no contra la superficie del sitio,
   así que no debe invertirse con el modo claro/oscuro -- mismo principio
   que el degradado de marca fijo de .ce-hero--marca. */
.widget-oferta-educativa-card--foto {
    padding: 0;
    overflow: hidden;
}
.widget-oferta-educativa-foto {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .35s ease;
}
.widget-oferta-educativa-card--foto:hover .widget-oferta-educativa-foto {
    transform: scale(1.06);
}
.widget-oferta-educativa-velo {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: var(--ce-space-4);
    padding-right: 56px;
    background: linear-gradient(to top, rgba(0,0,0,.82) 0%, rgba(0,0,0,.35) 45%, transparent 75%);
}
/* Fuera del velo, en posición fija (no apilada encima del nombre): dentro
   del velo, su altura dependía de cuántas líneas ocupara el nombre debajo
   -- con nombres de distinta longitud, la etiqueta de cada tarjeta quedaba
   a una altura distinta (petición expresa: que todas queden alineadas). El
   propio velo empieza muy claro por arriba, así que aquí sí hace falta un
   fondo (no solo contorno como en .ce-hero--marca, pensado para un
   degradado de marca fijo, no para una foto cualquiera). */
.widget-oferta-educativa-tag-foto {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 1;
    background-color: rgba(0,0,0,.45);
    border-color: var(--ce-color-neutral-0);
    color: var(--ce-color-neutral-0);
}
.widget-oferta-educativa-nombre-foto {
    color: var(--ce-color-neutral-0);
    font-size: 1.15rem;
    /* 800 quedaba demasiado grueso encima de la foto (petición expresa) --
       700 sigue leyendo como título destacado sin la sensación de negrita
       "de cartel". */
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -.01em;
}
.widget-oferta-educativa-card--foto .widget-oferta-educativa-flecha {
    position: absolute;
    right: 14px;
    bottom: 14px;
    width: 36px;
    height: 36px;
    min-height: 36px;
    min-width: 36px;
    padding: 0;
    font-size: .8rem;
}

.widget-oferta-educativa-icono-badge {
    width: 52px;
    height: 52px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    background: rgba(var(--ce-color-brand-600-rgb), 0.12156862745098039);
    color: var(--ce-color-brand-600);
    margin-bottom: 20px;
    transition: transform .25s ease;
}
/* .widget-oferta-educativa-tipo retirado (sdd/celosia-componentes W3):
   era texto plano sin estilo, ahora usa .ce-tag desde
   celosia-componentes.css, ver widgets/oferta-educativa.php. Se añade
   margin-bottom aquí para conservar la separación visual frente al nombre
   (.ce-tag por sí solo no lleva margen: lo definen sus adoptadores). */
.widget-oferta-educativa-card .ce-tag { margin-bottom: 8px; }
.widget-oferta-educativa-nombre {
    display: block;
    font-size: 1.15rem;
    font-weight: 800;
    color: var(--ce-color-semantic-text-primary);
    line-height: 1.28;
    letter-spacing: -.01em;
    margin-bottom: 22px;
}
.widget-oferta-educativa-cta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: auto;
    font-size: .85rem;
    font-weight: 700;
    color: var(--ce-color-brand-600);
}
.widget-oferta-educativa-cta i { font-size: .78rem; transition: transform .25s ease; }

/* === widgets/documentos-departamento.php, segundo bloque <style nonce> (el primero
ya se extrajo en la Fase 5, W4, bloque estático sin PHP). .widget-doc-departamento-cabecera
y .widget-doc-departamento-titulo ya están declaradas arriba (idénticas -- el propio
fichero fuente ya duplicaba ese selector entre sus dos bloques <style>), no se repiten. */
.widget-doc-departamento-abrir {
    font-size: .85rem;
    font-weight: 600;
    color: #666;
    text-decoration: none;
    white-space: nowrap;
}
.widget-doc-departamento-abrir:hover { color: var(--ce-color-brand-600); }
.widget-doc-departamento-abrir i { margin-right: 4px; }

.widget-doc-departamento-migas {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: .82rem;
    color: #888;
    margin-bottom: 16px;
}
.widget-doc-departamento-migas a { color: #666; text-decoration: none; font-weight: 600; }
.widget-doc-departamento-migas a:hover { color: var(--ce-color-brand-600); }
.widget-doc-departamento-migas i { font-size: .7rem; color: #ccc; }
.widget-doc-departamento-migas span { font-weight: 600; }

.widget-doc-departamento-vacio {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: var(--ce-color-semantic-surface-sunken);
    border: 1px dashed var(--ce-color-semantic-border-subtle);
    border-radius: 10px;
    padding: 36px 24px;
    margin: 0;
}
.widget-doc-departamento-vacio-icono { font-size: 2rem; color: var(--ce-color-semantic-text-secondary); opacity: .65; margin-bottom: 14px; }
.widget-doc-departamento-vacio-texto { color: var(--ce-color-semantic-text-secondary); font-size: .95rem; }

/* Lista de filas a ancho completo, no grid de tarjetas pequeñas: con varias columnas
   (auto-fill, minmax(190px,1fr), versión anterior) los nombres de fichero reales -- casi
   siempre más largos que 190px -- se recortaban con ellipsis y quedaban ilegibles. Mismo
   lenguaje que .documentos-tabla (public/documentos/index.php, File Thingie): una fila por
   documento, icono + nombre a la izquierda, tamaño a la derecha. */
.widget-doc-departamento-grid {
    display: flex;
    flex-direction: column;
    background-color: var(--ce-color-semantic-surface);
    border: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
    border-radius: var(--ce-size-radius-container);
    overflow: hidden;
}
.widget-doc-departamento-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: var(--ce-space-3) var(--ce-space-4);
    text-decoration: none;
    color: inherit;
    border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
}
.widget-doc-departamento-item:last-child { border-bottom: none; }
.widget-doc-departamento-item:hover {
    background-color: var(--ce-color-semantic-surface-sunken);
    color: inherit;
}
.widget-doc-departamento-icono {
    flex: 0 0 auto;
    font-size: 1.2rem;
    width: 24px;
    text-align: center;
}
.widget-doc-departamento-nombre {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .92rem;
    font-weight: var(--ce-font-weight-medium);
    overflow-wrap: break-word;
}
.widget-doc-departamento-tamano {
    flex: 0 0 auto;
    font-size: var(--ce-font-size-xs);
    color: var(--ce-color-semantic-text-secondary);
    font-variant-numeric: tabular-nums;
}
/* Iconos por tipo de fichero (widgetDocDepartamentoIcono()): mismo problema que en
   .documentos-tabla -- las utilities .text-* de Bootstrap no están retintadas a Celosía
   (celosia-bootstrap.css solo redirige --bs-primary), así que en oscuro saldría el color
   crudo de Bootstrap sin verificar. Se sobreescriben a los tokens color.state. */
.widget-doc-departamento-item .text-danger    { color: var(--ce-color-state-danger)  !important; }
.widget-doc-departamento-item .text-info      { color: var(--ce-color-state-info)    !important; }
.widget-doc-departamento-item .text-success   { color: var(--ce-color-state-success) !important; }
.widget-doc-departamento-item .text-warning   { color: var(--ce-color-state-warning) !important; }
.widget-doc-departamento-item .text-primary   { color: var(--ce-color-semantic-link) !important; }
.widget-doc-departamento-item .text-secondary { color: var(--ce-color-semantic-text-secondary) !important; }

.widget-doc-departamento-vermas {
    display: inline-block;
    margin-top: 16px;
    font-size: .85rem;
    font-weight: 600;
    color: var(--ce-color-brand-600);
    text-decoration: none;
}

/* === widgets/widget-custom.php (antes <style> inline, sin nonce) === */
/* .widget-custom-banner ya NO define fondo/radio/padding/layout propios
   (sdd/celosia-componentes W6): ese chroma lo da ahora .ce-hero
   (celosia-componentes.css) — es el caso "hero SIN imagen" (un solo hijo
   directo, .ce-hero__contenido, sin .ce-hero__imagen-wrap; ver ese fichero
   para el porqué del override de grid-template-columns de abajo). El radio
   de 14px del dialecto anterior no se traslada: .ce-hero no lleva
   border-radius (spec #347 celosia-hero). El fondo fijo var(--ce-color-
   brand-900) tampoco se traslada: .ce-hero usa var(--ce-color-semantic-
   surface) (dark-mode-aware) para las CINCO instancias de hero del sitio
   por igual — es justo la trampa de contraste que W3/W5/W9 ya encontraron
   con fondos fijos + texto fijo, evitada aquí retintando -titulo/-resumen a
   .ce-hero__titulo/.ce-hero__resumen (ya verificados en W9, ver ese
   bloque) en vez de conservar el #fff / rgba(255,255,255,.85) que
   solo eran seguros contra el brand-900 fijo anterior. El botón circular
   pasa a .ce-btn/.ce-btn--icon (celosia-componentes.css): mismo par de
   tokens action/text-on-brand ya verificado por verify_presets.py, sin
   sombra ni scale-on-hover (D1) -- el vendored llevaba ambos, ninguno se
   traslada. */
.widget-custom-wrapper { margin: 0 auto; }

/* Embed opcional (widget_custom_config, ver bootstrap.php/widgets/widget-custom.php)
   -- arriba/abajo: fila propia a ancho completo, apilada con la fila de accesos
   (mismo orden que el DOM, sin position/order). izquierda/derecha: dos columnas,
   con --prop-1-2/--prop-1-3 repartiendo el ancho -- el embed se queda con esa
   fracción, los accesos con el resto. Mismo gap (20px) que .widget-custom-grid
   para que ambas filas/columnas respiren igual. */
.widget-custom-wrapper--pos-arriba,
.widget-custom-wrapper--pos-abajo {
    display: flex;
    flex-direction: column;
    gap: 20px;
}
.widget-custom-wrapper--pos-izquierda,
.widget-custom-wrapper--pos-derecha {
    display: grid;
    gap: 20px;
    align-items: stretch;
}
.widget-custom-wrapper--pos-izquierda.widget-custom-wrapper--prop-1-2,
.widget-custom-wrapper--pos-derecha.widget-custom-wrapper--prop-1-2 {
    grid-template-columns: 1fr 1fr;
}
/* izquierda: embed (1fr) + accesos (2fr) = el embed ocupa 1/3. derecha: mismo
   reparto, orden invertido -- el DOM ya coloca el embed antes/después según la
   posición, así que solo cambia qué lado de la plantilla es 1fr y cuál 2fr. */
.widget-custom-wrapper--pos-izquierda.widget-custom-wrapper--prop-1-3 {
    grid-template-columns: 1fr 2fr;
}
.widget-custom-wrapper--pos-derecha.widget-custom-wrapper--prop-1-3 {
    grid-template-columns: 2fr 1fr;
}
@media (max-width: 767px) {
    /* Los selectores --prop-1-2/--prop-1-3 de arriba combinan DOS clases (0,2,0): más
       específicos que este --pos-izquierda/--pos-derecha a secas (0,1,0), así que sin
       repetir la combinación aquí seguían ganando ellos pase lo que pase con el viewport
       -- el embed se quedaba en su columna estrecha en vez de apilarse en móvil. */
    .widget-custom-wrapper--pos-izquierda,
    .widget-custom-wrapper--pos-derecha,
    .widget-custom-wrapper--pos-izquierda.widget-custom-wrapper--prop-1-2,
    .widget-custom-wrapper--pos-derecha.widget-custom-wrapper--prop-1-2,
    .widget-custom-wrapper--pos-izquierda.widget-custom-wrapper--prop-1-3,
    .widget-custom-wrapper--pos-derecha.widget-custom-wrapper--prop-1-3 {
        grid-template-columns: 1fr;
    }
}

.widget-custom-embed {
    /* min-width:0 -- imprescindible como hijo de grid: sin esto, un iframe no se
       encoge por debajo de su ancho intrínseco y desborda la columna 1/3. */
    min-width: 0;
}
.widget-custom-embed iframe {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--ce-size-radius-container);
}

/* Hasta tres cajas lado a lado (cita previa + secretaría virtual + consulta
   de listas), mismo patrón de grid+modificador de columnas que
   .widget-directo-a-grid/-cols-N. Cada caja se gasta por su cuenta (ver
   widgets/widget-custom.php); PHP cuenta cuántas van a pintarse de verdad y
   añade -grid-1/-2/-3 para que las que falten no dejen hueco vacío. */
.widget-custom-grid {
    display: grid;
    gap: 20px;
}
.widget-custom-grid-1 { grid-template-columns: 1fr; }
.widget-custom-grid-2 { grid-template-columns: 1fr 1fr; }
.widget-custom-grid-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 767px) {
    .widget-custom-grid { grid-template-columns: 1fr; }
}

.widget-custom-banner {
    position: relative;
    grid-template-columns: 1fr;
    text-decoration: none;
    /* Sin esto hereda el min-height de .ce-hero (400/460px, pensado para
       el hero de noticias) -- de sobra para una fila de CTA de una línea,
       deja un hueco enorme debajo del contenido. Se ajusta al contenido
       real (padding + texto/botón). */
    min-height: 0;
    /* Radio LOCAL a esta instancia, no en .ce-hero (que sigue sin
       border-radius por spec #347 celosia-hero, sin tocar aquí). Antes de
       que estas cajas pudieran compartir fila con .widget-custom-embed
       (iframe, ver esa regla) no importaba -- ahora sí, y un borde recto
       junto a uno redondeado en la misma fila/columna se lee como un
       descuido. Mismo token que el iframe para que combinen exactamente. */
    border-radius: var(--ce-size-radius-container);
    overflow: hidden;
    /* Mismo motivo que .ce-hero--marca: blanco, no el azul de acción, para
       el anillo de foco sobre este fondo de marca fijo (el propio banner ES
       el <a> foco able -- overflow:hidden no recorta el outline del propio
       elemento, solo a sus hijos, así que sigue siendo visible).
       --ce-color-focus-ring (no --ce-color-semantic-action, del que también
       depende el fondo de .ce-btn -- reasignarlo aquí dejaba los botones de
       flecha del banner en blanco sobre blanco, invisibles). */
    --ce-color-focus-ring: var(--ce-color-neutral-0);
    /* Foto (charlesdeluvio, Unsplash) + velo diagonal en los mismos dos
       tonos de marca que .ce-hero--marca (hero-noticias), para que ambas
       secciones lean como del mismo sitio. El velo va casi opaco (85%):
       sin esto el texto/icono en blanco de abajo pierde contraste sobre
       zonas claras de la foto. Se repite igual en --secretaria (mismo
       velo, otra foto) -- ver ese bloque para el porqué de no compartirlo
       vía variable. */
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--ce-color-brand-600) 85%, transparent),
            color-mix(in srgb, var(--ce-color-brand-900) 85%, transparent)
        ),
        url('../img/cita-previa-fondo.jpg') center / cover no-repeat;
}

/* Secretaría Virtual: mismo velo de marca que la caja de cita previa (para
   leer como pareja), pero con su propia foto (sergey-zolkin, Unsplash) --
   repetir la de cita previa en las dos cajas leería como un fallo visual,
   no como una decisión. Selector compuesto (mayor especificidad que
   .widget-custom-banner sola) para ganar sin depender del orden de
   declaración ni de !important. */
.widget-custom-banner.widget-custom-banner--secretaria {
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--ce-color-brand-600) 85%, transparent),
            color-mix(in srgb, var(--ce-color-brand-900) 85%, transparent)
        ),
        url('../img/secretaria-virtual-fondo.jpg') center / cover no-repeat;
}

/* Consulta de listas: mismo degradado de marca, sin foto -- a diferencia de
   las otras dos, esta caja no enlaza a ningún sitio (es un <div>, no un
   <a>, con un formulario dentro), así que una foto de fondo detrás de un
   input/resultado real estorbaría más que ayudaría. */
.widget-custom-banner.widget-custom-banner--listas {
    background: linear-gradient(135deg, var(--ce-color-brand-600), var(--ce-color-brand-900));
}

/* .ce-hero__contenido consolidado (W9) es de columna, pensado para un
   artículo con título/fecha/resumen apilados; este banner es un CTA de
   una sola línea de texto + botón de icono en fila, así que se
   sobrescribe aquí (mayor especificidad de selector, sin !important) solo
   para esta instancia -- no se toca la regla compartida. */
.widget-custom-banner .ce-hero__contenido {
    position: relative;
    z-index: 1;
    padding: 36px 40px;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
}
/* .ce-hero__titulo no fija color propio (solo lo hace ".ce-hero__titulo a",
   pensado para el patrón de hero-noticias/novedades-destacada, donde el
   título envuelve su propio <a> y el resto del héroe no es un enlace). Aquí
   el banner ENTERO es un único <a> (widget-custom-banner), así que sin
   este color explícito el <p> heredaría el color de enlace global (a{color:
   var(--ce-color-semantic-link)}, celosia.css) en vez del texto primario.
   Con la foto + velo de marca de fondo (ver arriba), además, el texto tiene
   que ser claro -- var(--ce-color-semantic-text-primary) ya no sirve. */
.widget-custom-banner .ce-hero__titulo {
    color: var(--ce-color-neutral-0);
    margin: 0;
}
.widget-custom-banner .ce-hero__resumen {
    color: var(--ce-color-brand-100);
    margin: 0;
}

.widget-custom-texto {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
}

@media (max-width: 575px) {
    .widget-custom-banner .ce-hero__contenido { flex-direction: column; align-items: flex-start; padding: 28px; }
    .widget-custom-banner .ce-btn--icon { align-self: flex-end; }
}

/* Consulta de listas: a diferencia de las otras dos cajas (una línea de
   texto + botón en fila), esta apila título/aviso, formulario y resultado
   -- vuelve al layout de columna que .ce-hero__contenido ya trae por
   defecto, sobrescribiendo el `flex-direction: row` de arriba solo para
   esta variante (mismo mecanismo de especificidad, sin !important). */
.widget-custom-banner--listas .ce-hero__contenido {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
}
.widget-consulta-listas-form input {
    /* Reusa el buscador de hero-noticias-vacío (pensado para foto + tarjeta
       clara), pero aquí el fondo es un degradado de marca opaco -- la
       píldora blanca del input sigue leyendo bien encima igualmente. */
    width: 100%;
}
.widget-consulta-listas-resultado:empty { display: none; }
.widget-consulta-listas-resultado {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.widget-consulta-listas-item,
.widget-consulta-listas-mensaje {
    margin: 0;
    padding: 10px 12px;
    border-radius: var(--ce-size-radius-md);
    background: color-mix(in srgb, var(--ce-color-neutral-0) 16%, transparent);
    color: var(--ce-color-neutral-0);
    font-size: .85rem;
    line-height: 1.4;
}
.widget-consulta-listas-reiniciar {
    align-self: flex-start;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ce-color-brand-100);
    font: inherit;
    font-size: .8rem;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}
.widget-consulta-listas-reiniciar:hover { color: var(--ce-color-neutral-0); }

/* === public/estudiar/index.php ===
   Nota: las clases .ficha-* de más arriba (Fase D, antes .etapa-*) son de las páginas de
   UNA etapa concreta (estudiar/bachillerato/, etc.); estas .estudiar-* son del listado
   general estudiar/index.php -- ficheros/páginas distintos, sin solape de selectores.
   Rediseño completo (antes: utilities de Bootstrap + colores literales #555/#777/#1a1a1a/
   #eef1fb/#f7f9fb/#444, invisibles en modo oscuro -- mismo defecto que .novedades-card antes
   de su propio rediseño, ver comentario en la sección de novedades más arriba). Ahora usa
   .ce-card/.ce-tag/.ce-btn --icon, el mismo lenguaje visual que ya tiene la tarjeta de
   widgets/oferta-educativa.php en la home, en vez de un dialecto propio sin relación. */
.estudiar-intro { font-size: 1.05rem; color: var(--ce-color-semantic-text-secondary); margin-bottom: var(--ce-space-5); max-width: 760px; }

/* Navegación rápida a las secciones que de verdad se van a pintar más abajo (la cifra real
   de programas/etapas ya viaja fundida en la frase de .estudiar-intro, ver index.php) --
   con una sola sección no aporta nada, por eso index.php solo la imprime si
   count($_estudiar_secciones) > 1. */
.estudiar-nav-rapida {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ce-space-2);
    margin-bottom: var(--ce-space-6);
    padding-bottom: var(--ce-space-5);
    border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
}

.estudiar-categoria { margin-bottom: var(--ce-space-8); scroll-margin-top: var(--ce-space-6); }
.estudiar-categoria:last-child { margin-bottom: 0; }
.estudiar-categoria-titulo {
    font-size: var(--ce-font-size-lg);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-brand-600);
    margin-bottom: var(--ce-space-5);
}
.estudiar-grado-titulo {
    font-size: var(--ce-font-size-xs);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-text-secondary);
    text-transform: uppercase;
    letter-spacing: .03em;
    margin: var(--ce-space-6) 0 var(--ce-space-3);
}
.estudiar-grado-titulo:first-of-type { margin-top: 0; }

/* Tarjeta: fondo/borde/radio/position ya vienen de .ce-card (celosia-componentes.css). Flex
   en columna para que .estudiar-card-cta pueda anclarse abajo con margin-top:auto cuando la
   fila (Bootstrap .row.g-4 + .h-100 en el propio <a>) estira todas las tarjetas a la misma
   altura. */
.estudiar-card {
    display: flex;
    flex-direction: column;
    color: inherit;
    text-decoration: none;
}
.estudiar-card-icono-badge {
    width: 52px;
    height: 52px;
    border-radius: var(--ce-size-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    background: rgba(var(--ce-color-brand-600-rgb), 0.12156862745098039);
    color: var(--ce-color-brand-600);
    margin-bottom: var(--ce-space-4);
}
/* align-self:flex-start -- sin esto, align-items:stretch (por defecto en un flex en
   columna) estiraría la pastilla de .ce-tag a lo ancho de toda la tarjeta. */
.estudiar-card .ce-tag { margin-bottom: var(--ce-space-2); align-self: flex-start; }
.estudiar-card-nombre {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--ce-color-semantic-text-primary);
    line-height: 1.3;
    transition: color var(--ce-duration-fast);
}
.estudiar-card:hover .estudiar-card-nombre { color: var(--ce-color-brand-600); }
.estudiar-card-descripcion {
    font-size: .9rem;
    color: var(--ce-color-semantic-text-secondary);
    line-height: 1.5;
    margin-top: var(--ce-space-2);
}
.estudiar-card-cta {
    display: flex;
    align-items: center;
    gap: var(--ce-space-1);
    margin-top: auto;
    padding-top: var(--ce-space-3);
    font-size: .85rem;
    font-weight: 700;
    color: var(--ce-color-brand-600);
}
.estudiar-card-cta i { font-size: .78rem; transition: transform var(--ce-duration-fast); }
.estudiar-card:hover .estudiar-card-cta i { transform: translateX(3px); }

/* Variante con foto: a sangre completa (mismo motivo que .ce-card--flush no encaja aquí,
   ver comentario gemelo en .widget-oferta-educativa-card--foto -- img + flecha absolutamente
   posicionada como hermanos, no "un solo primer hijo"). min-height es necesario, no solo
   cosmético: sin altura propia, un <a> cuyos únicos hijos son position:absolute mide 0px. */
.estudiar-card--foto {
    padding: 0;
    overflow: hidden;
    min-height: 260px;
}
.estudiar-card-foto {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .35s ease;
}
.estudiar-card--foto:hover .estudiar-card-foto { transform: scale(1.06); }
/* Velo y pastilla en negro/blanco fijos, no tokens: es contraste contra la FOTO, no contra
   la superficie del sitio (mismo principio que .ce-hero--marca / .widget-oferta-educativa-*). */
.estudiar-card-tag {
    position: absolute;
    top: var(--ce-space-3);
    left: var(--ce-space-3);
    z-index: 1;
    background-color: rgba(0,0,0,.45);
    border-color: var(--ce-color-neutral-0);
    color: var(--ce-color-neutral-0);
}
.estudiar-card-velo {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding: var(--ce-space-4);
    padding-right: 56px;
    background: linear-gradient(to top, rgba(0,0,0,.82) 0%, rgba(0,0,0,.35) 45%, transparent 75%);
}
.estudiar-card-nombre-foto {
    color: var(--ce-color-neutral-0);
    font-size: 1.1rem;
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -.01em;
}
.estudiar-card-flecha {
    position: absolute;
    right: var(--ce-space-3);
    bottom: var(--ce-space-3);
    width: 36px;
    height: 36px;
    min-height: 36px;
    min-width: 36px;
    padding: 0;
    font-size: .8rem;
}

/* === public/cita-previa/index.php (antes <style nonce> inline) === */
.cp-progreso {
    display: flex;
    flex-wrap: wrap;
    gap: 24px;
    margin-bottom: 36px;
    padding-bottom: 20px;
    border-bottom: 1px solid #eee;
}
.cp-progreso-paso {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: .88rem;
    font-weight: 600;
    color: #aaa;
}
.cp-progreso-num {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    border-radius: 50%;
    background: #eee;
    color: #999;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .8rem;
}
.cp-progreso-paso.is-activo { color: var(--ce-color-brand-600); }
.cp-progreso-paso.is-activo .cp-progreso-num {
    background: var(--ce-color-brand-600);
    color: var(--ce-color-neutral-0);
}

.cp-paso-titulo { font-size: var(--ce-font-size-xl); font-weight: var(--ce-font-weight-bold); margin-bottom: var(--ce-space-5); }

.cp-grupo-titulo {
    font-size: var(--ce-font-size-sm);
    font-weight: var(--ce-font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--ce-color-semantic-text-secondary);
    margin: var(--ce-space-5) 0 var(--ce-space-3);
}
.cp-grupo-titulo:first-child { margin-top: 0; }

.cp-cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    grid-auto-flow: dense;
    gap: 14px;
}

.cp-card-ancha { grid-column: span 2; }
@media (max-width: 480px) {
    .cp-card-ancha { grid-column: span 1; }
}

/* .cp-card retira fondo/borde/radio (sdd/celosia-componentes W6, cierre):
   la superficie de tarjeta ahora la da .ce-card (celosia-componentes.css)
   en el propio <button>, ver crearCard() en cita-previa/index.php. Aquí
   solo queda el layout de columna que .ce-card no cubre (flex, alineación,
   cursor de "botón que parece tarjeta") y el estado de selección, que
   sigue siendo específico de página. El hover con transform + cambio de
   border-color se retira: .ce-card:hover/:focus-within ya aporta la
   afordancia (cambio de background-color, D1) — mismo criterio que el
   resto de adopters de .ce-card (ver comentario de .menu-categoria-card
   más arriba).
   Trampa de contraste real encontrada y corregida aquí (comprobación
   proactiva antes de dar la unidad por cerrada): `<button>` NO hereda
   `color` de sus ancestros por defecto (el reset de Bootstrap solo hace
   `button{margin:0;font-family:inherit;font-size:inherit;line-height:
   inherit}` — sin `color:inherit`, a diferencia de otros normalize.css).
   Con el `background:#fff` fijo anterior (pre-W6) el texto por defecto
   del botón (negro, UA) siempre caía bien por accidente; con el fondo
   ahora tokenizado y sensible a modo oscuro de .ce-card, ese negro fijo
   se vuelve ilegible sobre el neutral-800 oscuro. Se fija explícitamente
   al alias semántico correcto en vez de confiar en la herencia rota. */
.cp-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
    cursor: pointer;
    color: var(--ce-color-semantic-text-primary);
}
/* Trampa de contraste distinta, detectada aquí en W6 y cerrada en W10
   (open item 2, apply-progress) -- misma clase de defecto que
   --bs-link-color-rgb (open item 1, ver celosia-bootstrap.css): un token
   usado en modo oscuro sin haber recibido nunca redirección propia.
   --ce-color-brand-tint no tenía redirección de modo oscuro (se quedaba
   fijo en su hex claro en los 7 presets); con el `color` de .cp-card ya
   corregido arriba a text-primary, en oscuro ese texto pasaba a
   neutral-50 (casi blanco) sobre este fondo que se quedaba siempre claro
   -- casi blanco sobre casi blanco. Ya lo arrastraba .cp-card.is-
   seleccionada desde antes de W6; el arreglo (reapuntar brand-tint a
   brand-900 en oscuro, un paso YA EXISTENTE de la propia escala del
   preajuste) vive en celosia-bootstrap.css porque ese fichero carga
   DESPUÉS de preset-*.css en la cascada -- ver la nota completa allí. */
.cp-card.is-seleccionada {
    border-color: var(--ce-color-brand-600);
    background: var(--ce-color-brand-tint);
}
.cp-card-nombre { display: block; font-weight: 700; font-size: .95rem; }
.cp-card-descripcion { display: block; font-size: .85rem; color: var(--ce-color-semantic-text-secondary); margin-top: 6px; }
.cp-card-meta { display: block; font-size: .8rem; color: var(--ce-color-semantic-text-secondary); margin-top: 6px; }
.cp-card-huecos { display: block; font-size: .78rem; color: var(--ce-color-semantic-text-secondary); margin-top: 4px; font-weight: 400; }

.cp-hora { justify-self: stretch; text-align: center; padding: 12px; font-weight: 700; }

.cp-input-fecha {
    width: 100%;
    max-width: 260px;
    padding: 10px 14px;
    border: 1px solid #dcdcdc;
    border-radius: var(--ce-size-radius-container);
    font-size: .95rem;
    margin-bottom: 20px;
}

.cp-datos-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; max-width: 720px; }
.cp-campo { display: flex; flex-direction: column; gap: 6px; }
.cp-campo-label { font-size: .82rem; font-weight: 700; color: #555; }
.cp-input {
    padding: 10px 14px;
    border: 1px solid #dcdcdc;
    border-radius: var(--ce-size-radius-container);
    font-size: .95rem;
    width: 100%;
}
.cp-telefono-grupo { display: flex; gap: 8px; }
.cp-select-codigo { flex: 0 0 96px; padding-left: 8px; padding-right: 4px; }
.cp-input-telefono { flex: 1 1 auto; min-width: 0; }

.cp-campo-opcional { font-weight: 400; color: #999; }
.cp-campo-error { font-size: .8rem; color: #b3261e; }
.cp-campo-comentarios { max-width: 720px; margin-top: 14px; }
.cp-textarea { resize: vertical; font-family: inherit; }

.cp-privacidad-aviso { font-size: .85rem; color: #666; max-width: 720px; margin-bottom: 20px; }
.cp-privacidad-aviso a { color: var(--ce-color-brand-600); }

.cp-privacidad-check {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    max-width: 720px;
    margin-top: 20px;
    font-size: .88rem;
    color: #444;
    cursor: pointer;
}
.cp-privacidad-check input { margin-top: 3px; flex: 0 0 auto; }

/* .cp-boton/-primario/-secundario retirados (sdd/celosia-componentes W6):
   los tres botones del asistente (#cp-atras/-siguiente/-confirmar) usan
   ahora .ce-btn/.ce-btn--secondary (celosia-componentes.css). El único
   resto de layout específico de página que .ce-btn no cubre -- empujar el
   botón primario al extremo derecho de .cp-nav -- se engancha por ID, no
   por una clase de dialecto nueva: ambos IDs ya existían para el JS del
   asistente. */
.cp-nav { display: flex; gap: 12px; margin-top: 32px; }
#cp-siguiente,
#cp-confirmar { margin-left: auto; }

/* .cp-mensaje retira fondo/color por severidad (sdd/celosia-componentes
   W6, cierre): la superficie neutra + borde + radio + color de texto los
   da ahora .ce-notice (celosia-componentes.css) sobre el propio
   div#cp-mensaje; la severidad pasa al icono (fa-check-circle/
   fa-exclamation-circle, distinta FORMA, no solo color) + la palabra en
   negrita dentro de .ce-notice__contenido, mismo patrón que
   aviso-legal/accesibilidad (W8) — ver mostrarMensaje() en
   cita-previa/index.php. Delta visible de diseño, deliberado, no un
   defecto: el mensaje pasa de texto centrado en negrita sobre fondo de
   color a un aviso alineado a la izquierda con icono sobre superficie
   neutra — consecuencia directa de "reutiliza .ce-notice, no lo
   redefinas", igual que el delta ya señalado para .ce-hero en W6. Solo
   queda aquí el margen de separación específico de página. */
.cp-mensaje { margin-top: 24px; }
.agenda-vacio-mes .agenda-vacio-icono { font-size: 1.5rem; margin-bottom: 10px; }

/* === public/aviso-legal, public/privacidad, public/cookies,
   public/accesibilidad + inc_legal_actualizacion.php (rediseño páginas
   legales). Sin TOC ni sub-nav entre páginas (petición expresa: fuera el
   menú de enlaces legales bajo el título, y fuera el índice "Contenido de
   esta página") -- un único bloque de contenido, alineado a la izquierda,
   con el aviso de última actualización como primer elemento dentro de él. */
/* --ce-size-measure (70ch) aplicado al CONTENEDOR, no a cada hijo por
   separado -- a propósito no global (celosia.css lo deja sin consumidor por
   diseño). `ch` es relativo al font-size del propio elemento que lo usa: un
   primer intento de poner max-width:70ch en p/li/h2/h3 por separado producía
   columnas DE ANCHO DISTINTO (604px en los párrafos, 1244px en los h2, con
   su font-size mayor) -- títulos largos como "5. Comunicación de quejas,
   sugerencias y solicitudes de información accesible" se salían de la
   columna de los párrafos de debajo (verificado en vivo: 960-1130px de
   texto real en 3 de las 4 páginas). Puesto en el contenedor, los 70ch se
   resuelven UNA vez contra el font-size base de .legal-contenido, y todo lo
   de dentro (párrafos, listas, títulos, el aviso de actualización) comparte
   la misma columna real en píxeles.

   La tabla de cookies (public/cookies/index.php) es la EXCEPCIÓN deliberada:
   vive en un .legal-tabla-cookies hermano, FUERA de .legal-contenido (la
   página abre un segundo .legal-contenido después de la tabla) -- 5 columnas
   de datos comprimidas en 604px envolvían cada celda en 6-8 líneas
   (verificado en vivo), justo lo contrario de "ayudar a seguir la lectura
   fácilmente". Una tabla de datos no es prosa: usa el ancho completo de la
   columna exterior (.col-12), no la medida de lectura. */
.legal-contenido {
    max-width: var(--ce-size-measure);
    text-align: left;
}

.legal-contenido h2 {
    margin-top: var(--ce-space-7);
    margin-bottom: var(--ce-space-3);
    scroll-margin-top: var(--ce-space-5);
    /* Sin tipografía propia, esta cabecera de sección heredaba el tamaño/peso por defecto del
       navegador (revisión de encabezados: única regla del sitio sin ningún token --ce-*). */
    font-size: var(--ce-font-size-lg);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-text-primary);
}

.legal-contenido h3 {
    margin-top: var(--ce-space-5);
    margin-bottom: var(--ce-space-2);
    scroll-margin-top: var(--ce-space-5);
    font-size: var(--ce-font-size-md);
    font-weight: var(--ce-font-weight-bold);
    color: var(--ce-color-semantic-text-primary);
}

.legal-contenido .ce-notice {
    margin-bottom: var(--ce-space-6);
}

/* Tabla de cookies (public/cookies/index.php §2 tabla-cookies). Wrapper con
   overflow-x para que la tabla no rompa el layout en móvil; las categorías de
   cada fila reutilizan .ce-tag (celosia-componentes.css) tal cual. */
.legal-tabla-cookies { overflow-x: auto; margin: var(--ce-space-5) 0; }
.legal-tabla-cookies table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--ce-font-size-sm);
}
.legal-tabla-cookies th,
.legal-tabla-cookies td {
    padding: var(--ce-space-3);
    border-bottom: var(--ce-size-border-width) solid var(--ce-color-semantic-border-subtle);
    text-align: left;
    vertical-align: top;
}
.legal-tabla-cookies th {
    color: var(--ce-color-semantic-text-secondary);
    font-weight: var(--ce-font-weight-bold);
    white-space: nowrap;
}
