/* ============================================================================
   Alumne Flex — capa de interfaz.

   Esta hoja es COMÚN A TODOS LOS TEMAS y no contiene ni un color literal con
   tinte: todo sale de los tokens que aporta el tema activo
   (webroot/themes/<slug>/). Si necesitas un color nuevo, añádelo como token al
   tema, no aquí.

   Las únicas excepciones, todas deliberadas y comentadas en su sitio: blancos y
   negros neutros sobre superficies invertidas o telones (--surface-stage,
   --gradient-ink, --gradient-brand), el fondo blanco del QR —que necesita
   contraste real de lector, no el del tema—, el tablero de cuadros de la
   transparencia, y los dos data-URI SVG de Bootstrap donde no cabe un var().

   Orden de carga en el layout:
     font/fonts.css → vendor/bootstrap → themes/<slug>/theme.css → este fichero

   Secciones:
     1. Puente con Bootstrap
     2. Base
     3. Tipografía y utilidades de texto
     4. Botones
     5. Formularios
     6. Tarjetas, paneles y superficies
     7. Tablas
     8. Badges, tags y estados
     9. Shell: sidebar, topbar, contenido
    10. Patrones: KPI, hero, listas, estado vacío
    11. Pantallas de autenticación
    12. APP móvil (lanzadera pública)
    13. Utilidades y responsive
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1. Puente con Bootstrap
   Se remapean las variables de Bootstrap 5.3 a los tokens. A partir de aquí,
   los componentes de Bootstrap (modal, dropdown, offcanvas, alert, grid) salen
   ya con el lenguaje visual de Alumne Flex sin tocar su marcado.
   --------------------------------------------------------------------------- */
:root {
  --bs-primary: var(--action-primary);
  --bs-secondary: var(--text-muted);
  --bs-success: var(--status-success-solid);
  --bs-warning: var(--status-warning-solid);
  --bs-danger: var(--status-danger-solid);
  --bs-info: var(--status-info-solid);

  --bs-body-font-family: var(--font-sans);
  --bs-body-font-size: var(--type-body-size);
  --bs-body-font-weight: var(--weight-regular);
  --bs-body-line-height: var(--leading-normal);
  --bs-body-color: var(--text-body);
  --bs-body-bg: var(--surface-canvas);
  --bs-emphasis-color: var(--text-strong);
  --bs-secondary-color: var(--text-muted);
  --bs-tertiary-color: var(--text-subtle);
  --bs-border-color: var(--border-subtle);
  --bs-border-radius: var(--radius-control);
  --bs-border-radius-sm: var(--radius-sm);
  --bs-border-radius-lg: var(--radius-card);
  --bs-border-radius-xl: var(--radius-panel);
  --bs-border-radius-pill: var(--radius-pill);
  --bs-link-color: var(--text-link);
  --bs-link-hover-color: var(--text-link-hover);
  --bs-focus-ring-color: color-mix(in srgb, var(--focus-ring) 28%, transparent);
  --bs-focus-ring-width: 3px;
  --bs-box-shadow: var(--shadow-md);
  --bs-box-shadow-sm: var(--shadow-sm);
  --bs-box-shadow-lg: var(--shadow-lg);
  --bs-font-monospace: var(--font-mono);
}

/* Utilidades que Bootstrap deriva de un TRIPLETE RGB (`rgba(var(--bs-*-rgb), α)`).
   Un triplete no se puede componer desde un token de color: `rgba()` no acepta
   `var(--action-primary)` dentro. La alternativa sería duplicar cada color como
   triplete en los ocho temas —y otra vez en cada bloque oscuro, quedando a la
   deriva en cuanto alguien retoque una escala—, así que se reescriben aquí los
   consumidores, que son estos y ninguno más. Por eso NO existe ningún token
   `--*-rgb` en el contrato: si añades uno, estás reintroduciendo el problema.
   `a` y `a:hover` (los otros dos consumidores de --bs-link-color-rgb) ya se
   pintan con var(--text-link) en la sección 2. */
.text-primary { color: var(--action-primary) !important; }
.bg-primary { background-color: var(--action-primary) !important; }
.border-primary { border-color: var(--border-brand) !important; }
.text-bg-primary { color: var(--action-primary-fg) !important; background-color: var(--action-primary) !important; }
.focus-ring-primary { --bs-focus-ring-color: color-mix(in srgb, var(--action-primary) 28%, transparent); }
.link-primary { color: var(--action-primary) !important; text-decoration-color: var(--action-primary) !important; }
.link-primary:focus, .link-primary:hover {
  color: var(--action-primary-hover) !important; text-decoration-color: var(--action-primary-hover) !important; }
.link-underline-primary { text-decoration-color: var(--action-primary) !important; }
.link-underline { text-decoration-color: var(--text-link) !important; }
.icon-link { text-decoration-color: color-mix(in srgb, var(--text-link) 50%, transparent); }

/* Bootstrap fija estos por componente, así que hay que repetirlos ahí. */
.modal { --bs-modal-bg: var(--surface-card); --bs-modal-border-color: var(--border-subtle);
  --bs-modal-border-radius: var(--radius-panel); --bs-modal-box-shadow: var(--shadow-xl);
  --bs-modal-header-border-color: var(--border-subtle); --bs-modal-footer-border-color: var(--border-subtle);
  --bs-modal-color: var(--text-body); }
.modal-backdrop { --bs-backdrop-bg: var(--surface-overlay); --bs-backdrop-opacity: 1; backdrop-filter: blur(3px); }
.dropdown-menu { --bs-dropdown-bg: var(--surface-card); --bs-dropdown-border-color: var(--border-subtle);
  --bs-dropdown-border-radius: var(--radius-card); --bs-dropdown-box-shadow: var(--shadow-md);
  --bs-dropdown-link-color: var(--text-body); --bs-dropdown-link-hover-bg: var(--action-ghost-hover);
  --bs-dropdown-link-hover-color: var(--text-strong); --bs-dropdown-link-active-bg: var(--surface-brand-soft);
  --bs-dropdown-link-active-color: var(--text-brand); --bs-dropdown-padding-y: 6px; --bs-dropdown-item-padding-y: 9px;
  --bs-dropdown-item-padding-x: 10px; --bs-dropdown-font-size: var(--text-sm); }
.dropdown-menu .dropdown-item { border-radius: var(--radius-sm); }
/* Los items son `display:block`, así que un icono de Lucide (que es un <svg>
   inline) queda ~2px por debajo del centro del texto. Con flex se centran, igual
   que ya hace Bootstrap con `.btn`. El icono no se estruja con textos largos. */
.dropdown-menu .dropdown-item { display: flex; align-items: center; gap: 8px; }
.dropdown-menu .dropdown-item > svg { flex: 0 0 auto; }
.offcanvas { --bs-offcanvas-bg: var(--surface-card); --bs-offcanvas-border-color: var(--border-subtle);
  --bs-offcanvas-color: var(--text-body); }
.tooltip { --bs-tooltip-bg: var(--surface-inverse); --bs-tooltip-color: var(--text-inverse);
  --bs-tooltip-border-radius: var(--radius-sm); --bs-tooltip-font-size: var(--text-2xs); --bs-tooltip-opacity: 1; }
.progress { --bs-progress-bg: var(--border-subtle); --bs-progress-border-radius: var(--radius-pill);
  --bs-progress-bar-bg: var(--action-primary); --bs-progress-height: 8px; }

/* Estos cuatro se saltan --bs-focus-ring-color: Bootstrap 5.3 les quema su azul
   (rgba(13,110,253,.25) / #0d6efd) en un valor propio del componente, así que
   hay que repuntarlos al anillo del tema uno a uno. `.btn-close` es la X de
   todos los modales y sale en 84 plantillas; el acordeón y los pills no se usan
   hoy, pero se dejan cerrados para que nadie estrene el azul de Bootstrap. */
.btn-close { --bs-btn-close-focus-shadow: var(--ring-focus); }
.accordion { --bs-accordion-btn-focus-box-shadow: var(--ring-focus);
  --bs-accordion-active-color: var(--text-brand); --bs-accordion-active-bg: var(--surface-brand-soft); }
.nav-link:focus-visible { box-shadow: var(--ring-focus); }
.nav-pills { --bs-nav-pills-link-active-color: var(--action-primary-fg);
  --bs-nav-pills-link-active-bg: var(--action-primary); }

/* La X de `.btn-close` es un SVG con `fill='%23212529'` quemado dentro del
   data-URI, donde no entra un token. Bootstrap la invierte con
   [data-bs-theme=dark], un atributo que esta plataforma no escribe —usa
   data-theme—, así que sin esta regla en modo oscuro la X es tinta sobre tinta
   y el modal parece no tener cierre. */
:root[data-theme="dark"] .btn-close { filter: invert(1) grayscale(100%) brightness(200%); }

/* ---------------------------------------------------------------------------
   2. Base
   --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { background: var(--surface-canvas); }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-body);
  color: var(--text-body);
  background: var(--surface-canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: var(--text-link); text-decoration: none; }
a:hover { color: var(--text-link-hover); text-decoration: underline; text-underline-offset: 3px; }

code, kbd, pre, samp { font-family: var(--font-mono); font-size: .92em; }

:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--brand-primary) 26%, var(--surface-card)); color: var(--text-strong); }

hr { border: 0; border-top: 1px solid var(--border-subtle); opacity: 1; margin: var(--space-7) 0; }

/* ---------------------------------------------------------------------------
   3. Tipografía
   --------------------------------------------------------------------------- */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--text-strong);
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-snug);
  font-weight: var(--weight-semibold);
  margin: 0;
}
h1 { font-size: var(--type-h1-size); letter-spacing: var(--tracking-display); }
h2 { font-size: var(--type-h2-size); letter-spacing: var(--tracking-display); }
h3 { font-size: var(--type-h3-size); }
h4 { font-size: var(--type-h4-size); }
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-base); }

.af-display { font-family: var(--font-display); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-display); }
.af-mono { font-family: var(--font-mono); }

/* Eyebrow: la única mayúscula del sistema. */
.af-eyebrow {
  font-size: var(--type-eyebrow-size);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0;
}
.af-eyebrow-accent { color: var(--text-accent); }
.af-eyebrow-brand { color: var(--text-brand); }

.af-muted { color: var(--text-muted); }
.af-subtle { color: var(--text-subtle); }
.af-strong { color: var(--text-strong); }
.af-prose { max-width: var(--prose-max); line-height: var(--leading-relaxed); }

.af-code-inline {
  font-family: var(--font-mono); font-size: var(--text-xs);
  background: var(--surface-sunken); color: var(--text-body);
  padding: 1px 6px; border-radius: var(--radius-xs);
}

/* ---------------------------------------------------------------------------
   4. Botones
   --------------------------------------------------------------------------- */
.btn {
  --bs-btn-font-family: var(--font-sans);
  --bs-btn-font-size: var(--text-sm);
  --bs-btn-font-weight: var(--weight-semibold);
  --bs-btn-border-radius: var(--radius-control);
  --bs-btn-padding-y: 0;
  --bs-btn-padding-x: var(--space-5);
  --bs-btn-focus-box-shadow: var(--ring-focus);
  height: var(--control-h-md);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-3);
  letter-spacing: var(--tracking-label);
  white-space: nowrap;
  transition: var(--transition-control);
}
.btn:active { transform: scale(.975); }
.btn:disabled, .btn.disabled { opacity: .45; cursor: not-allowed; transform: none; }
.btn i[data-lucide] { flex: 0 0 auto; }

.btn-sm { --bs-btn-font-size: var(--text-xs); --bs-btn-padding-x: var(--space-4); height: var(--control-h-sm); gap: var(--space-2); }
.btn-lg { --bs-btn-font-size: var(--text-base); --bs-btn-padding-x: var(--space-7); height: var(--control-h-lg); }

/* Primario: el naranja de acción. Es el único botón con halo de marca. */
.btn-brand {
  --bs-btn-color: var(--action-primary-fg);
  --bs-btn-bg: var(--action-primary);
  --bs-btn-border-color: var(--action-primary);
  --bs-btn-hover-color: var(--action-primary-fg);
  --bs-btn-hover-bg: var(--action-primary-hover);
  --bs-btn-hover-border-color: var(--action-primary-hover);
  --bs-btn-active-bg: var(--action-primary-press);
  --bs-btn-active-border-color: var(--action-primary-press);
  --bs-btn-active-color: var(--action-primary-fg);
  --bs-btn-disabled-color: var(--action-primary-fg);
  --bs-btn-disabled-bg: var(--action-primary);
  --bs-btn-disabled-border-color: var(--action-primary);
  box-shadow: var(--shadow-brand);
}

/* Paginación integrada con la identidad de la plataforma: la página activa usa el
   naranja de marca (en vez del azul por defecto de Bootstrap) y los enlaces son
   neutros con acento de marca al pasar el ratón. Respeta «Marca rápida» y temas. */
.pagination {
  --bs-pagination-color: var(--text-body);
  --bs-pagination-bg: var(--surface-card);
  --bs-pagination-border-color: var(--border-subtle);
  --bs-pagination-hover-color: var(--action-primary);
  --bs-pagination-hover-bg: var(--action-ghost-hover);
  --bs-pagination-hover-border-color: var(--border-default);
  --bs-pagination-focus-color: var(--action-primary);
  --bs-pagination-focus-bg: var(--action-ghost-hover);
  --bs-pagination-focus-box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--action-primary) 30%, transparent);
  --bs-pagination-active-color: var(--action-primary-fg);
  --bs-pagination-active-bg: var(--action-primary);
  --bs-pagination-active-border-color: var(--action-primary);
  --bs-pagination-disabled-color: var(--text-subtle);
  --bs-pagination-disabled-bg: var(--surface-card);
  --bs-pagination-disabled-border-color: var(--border-subtle);
  gap: 4px;
}
.pagination .page-link {
  border-radius: var(--radius-sm);
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.btn-brand:hover { box-shadow: var(--shadow-brand); }

/* Secundario sólido: navy. Para acciones de sistema, no de marca. */
.btn-ink {
  --bs-btn-color: var(--text-inverse);
  --bs-btn-bg: var(--action-secondary);
  --bs-btn-border-color: var(--action-secondary);
  --bs-btn-hover-color: var(--text-inverse);
  --bs-btn-hover-bg: var(--action-secondary-hover);
  --bs-btn-hover-border-color: var(--action-secondary-hover);
  --bs-btn-active-bg: var(--action-secondary-hover);
  --bs-btn-active-color: var(--text-inverse);
}

.btn-outline-brand {
  --bs-btn-color: var(--text-body);
  --bs-btn-bg: var(--surface-card);
  --bs-btn-border-color: var(--border-default);
  --bs-btn-hover-color: var(--text-strong);
  --bs-btn-hover-bg: var(--action-ghost-hover);
  --bs-btn-hover-border-color: var(--border-default);
  --bs-btn-active-bg: var(--action-ghost-hover);
  --bs-btn-active-color: var(--text-strong);
  --bs-btn-active-border-color: var(--border-default);
}

.btn-ghost {
  --bs-btn-color: var(--text-muted);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--text-strong);
  --bs-btn-hover-bg: var(--action-ghost-hover);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-bg: var(--action-ghost-hover);
  --bs-btn-active-color: var(--text-strong);
  --bs-btn-active-border-color: transparent;
}

.btn-danger-soft {
  --bs-btn-color: var(--status-danger-fg);
  --bs-btn-bg: var(--status-danger-bg);
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--status-danger-solid);
  --bs-btn-hover-border-color: var(--status-danger-solid);
  --bs-btn-active-bg: var(--status-danger-solid);
  --bs-btn-active-color: #fff;
}

/* Botón cuadrado que solo lleva icono. */
.af-icon-btn {
  width: 36px; height: 36px; padding: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--radius-control);
  background: transparent; color: var(--text-muted); cursor: pointer;
  transition: var(--transition-control);
}
.af-icon-btn:hover { background: var(--action-ghost-hover); color: var(--text-strong); }
.af-icon-btn:active { transform: scale(.975); }
.af-icon-btn-sm { width: 30px; height: 30px; }
.af-icon-btn-bordered { border-color: var(--border-subtle); background: var(--surface-card); }

/* ---------------------------------------------------------------------------
   5. Formularios
   --------------------------------------------------------------------------- */
.form-label {
  font-size: var(--type-label-size);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
  letter-spacing: var(--tracking-label);
  margin-bottom: var(--space-2);
}

.form-control, .form-select {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-body);
  background-color: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-control);
  min-height: var(--control-h-md);
  padding: var(--space-3) var(--space-4);
  transition: var(--transition-control);
}
.form-control::placeholder { color: var(--text-subtle); }
.form-control:focus, .form-select:focus {
  border-color: var(--border-brand);
  box-shadow: var(--ring-focus);
  background-color: var(--surface-card);
  color: var(--text-body);
}
.form-control:disabled, .form-select:disabled,
.form-control[readonly] {
  background-color: var(--surface-sunken);
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 1;
}
textarea.form-control { min-height: 96px; line-height: var(--leading-normal); }
.form-control-sm, .form-select-sm { min-height: var(--control-h-sm); font-size: var(--text-xs); padding: var(--space-2) var(--space-3); }
.form-text { font-size: var(--text-2xs); color: var(--text-muted); margin-top: var(--space-2); }

.form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238B837A' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m4 6 4 4 4-4'/%3e%3c/svg%3e");
  background-size: 16px 12px;
}

/* Campo con icono a la izquierda, como en el prototipo. */
.af-field { position: relative; }
.af-field-icon {
  position: absolute; left: var(--space-4); top: 50%; transform: translateY(-50%);
  color: var(--text-subtle); pointer-events: none; display: inline-flex;
}
.af-field-icon ~ .form-control { padding-left: 40px; }
.af-field-sm .af-field-icon { left: var(--space-3); }
.af-field-sm .af-field-icon ~ .form-control { padding-left: 34px; }

/* Ojito de contraseña. */
.af-password { position: relative; }
.af-password .form-control { padding-right: 44px; }
.af-password-toggle {
  position: absolute; top: 50%; right: var(--space-2); transform: translateY(-50%);
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: var(--radius-pill);
  background: transparent; color: var(--text-subtle); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: var(--transition-control);
}
.af-password-toggle:hover { background: var(--action-ghost-hover); color: var(--text-strong); transform: translateY(-50%); }

.form-check-input {
  border-color: var(--border-default);
  background-color: var(--surface-card);
  width: 1.05em; height: 1.05em; margin-top: .18em;
}
.form-check-input:checked { background-color: var(--action-primary); border-color: var(--action-primary); }
.form-check-input:focus { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.form-check-label { font-size: var(--text-sm); color: var(--text-body); }
.form-switch .form-check-input:checked { background-color: var(--action-accent); border-color: var(--action-accent); }
/* Al enfocar un interruptor apagado, Bootstrap repinta el pomo de su azul
   (#86b7fe) metido DENTRO de un data-URI SVG, donde no entra un token. Se
   devuelve el pomo neutro de reposo: el foco ya se ve por el anillo, que sí
   sigue al tema. Hace falta :not(:checked) porque esta hoja carga después que
   Bootstrap y si no le ganaría también al pomo blanco del estado encendido. */
.form-switch .form-check-input:focus:not(:checked) {
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
}

.invalid-feedback { font-size: var(--text-2xs); color: var(--status-danger-fg); }
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--status-danger-solid); background-image: none; }
.form-control.is-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-danger-solid) 22%, transparent); }
.error-message { font-size: var(--text-2xs); color: var(--status-danger-fg); margin-top: var(--space-2); }

/* Medidor de fuerza de contraseña. */
.af-password-rules {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  margin-top: var(--space-3); font-size: var(--text-2xs); color: var(--text-muted);
}
.af-password-rules span[data-password-rule] { display: inline-flex; align-items: center; gap: var(--space-2); }
.af-password-rules span[data-password-rule].is-met { color: var(--status-success-fg); font-weight: var(--weight-semibold); }

/* ---------------------------------------------------------------------------
   6. Tarjetas, paneles y superficies
   --------------------------------------------------------------------------- */
.af-card {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm);
}
.af-panel {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-sm);
}
.af-card-body, .af-panel-body { padding: var(--gutter-card); }
.af-panel-body { padding: var(--gutter-panel); }

.af-card-interactive { transition: var(--transition-surface); }
.af-card-interactive:hover { box-shadow: var(--shadow-lg); transform: translateY(-2px); }

.af-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-5) var(--gutter-panel);
  border-bottom: 1px solid var(--border-subtle);
}
.af-panel-head h2, .af-panel-head h3 { font-size: var(--text-base); }

/* Panel oscuro: la voz técnica del producto. Un gradiente ink por vista. */
.af-panel-ink {
  background: var(--gradient-ink);
  border-radius: var(--radius-panel);
  color: #fff;
  border: 0;
}
.af-panel-ink h1, .af-panel-ink h2, .af-panel-ink h3 { color: #fff; }
.af-panel-ink .af-muted { color: rgba(255, 255, 255, .7); }

/* Cuadrado con icono dentro, para dar peso a un glifo. */
.af-icon-box {
  width: 38px; height: 38px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md);
  background: var(--surface-brand-soft); color: var(--text-brand);
}
.af-icon-box-sm { width: 34px; height: 34px; }
.af-icon-box-accent { background: var(--surface-accent-soft); color: var(--text-accent); }
.af-icon-box-ai { background: var(--status-ai-bg); color: var(--status-ai-fg); }
.af-icon-box-success { background: var(--status-success-bg); color: var(--status-success-fg); }
.af-icon-box-warning { background: var(--status-warning-bg); color: var(--status-warning-fg); }
.af-icon-box-danger { background: var(--status-danger-bg); color: var(--status-danger-fg); }

/* ---------------------------------------------------------------------------
   7. Tablas
   --------------------------------------------------------------------------- */
.af-table { width: 100%; margin: 0; border-collapse: separate; border-spacing: 0; }
.af-table th {
  font-size: var(--type-label-size); font-weight: var(--weight-semibold);
  color: var(--text-muted); letter-spacing: var(--tracking-label);
  text-align: left; padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-sunken);
  white-space: nowrap;
}
.af-table th:first-child { border-top-left-radius: var(--radius-card); }
.af-table th:last-child { border-top-right-radius: var(--radius-card); }
.af-table td {
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--text-sm); color: var(--text-body); vertical-align: middle;
}
.af-table tbody tr:last-child td { border-bottom: 0; }
.af-table tbody tr { transition: background-color var(--dur-fast) var(--ease-standard); }
.af-table tbody tr:hover { background: var(--surface-sunken); }
.af-table .af-table-actions { display: flex; gap: var(--space-2); justify-content: flex-end; }
.af-table-wrap { overflow-x: auto; border-radius: var(--radius-card); }

/* En móvil la tabla se convierte en fichas apiladas. */
@media (max-width: 767.98px) {
  .af-table-stack thead { display: none; }
  .af-table-stack tbody tr {
    display: block; margin-bottom: var(--space-4);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-card);
    background: var(--surface-card); padding: var(--space-2) 0;
  }
  .af-table-stack tbody td {
    display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4);
    border-bottom: 0; padding: var(--space-3) var(--space-5);
  }
  .af-table-stack tbody td::before {
    content: attr(data-label);
    font-size: var(--type-label-size); font-weight: var(--weight-semibold);
    color: var(--text-muted); flex: 0 0 auto;
  }
  .af-table-stack tbody td:empty { display: none; }
  .af-table-stack .af-table-actions { justify-content: flex-start; }

  /* Se acabó la caja dentro de la caja: el panel que solo envuelve una tabla
     apilada deja de ser tarjeta y le cede el papel a las fichas de dentro. Los
     listados no cuelgan todos igual —unos de `.af-table-wrap`, otros de un
     `.af-panel-body`—, y se excluye el panel que tiene cabecera propia, que sí
     es una tarjeta con su título y debe seguir pareciéndolo. */
  .af-panel:not(:has(> .af-panel-head)):has(> .af-table-wrap > .af-table-stack),
  .af-panel:not(:has(> .af-panel-head)):has(> .af-panel-body > .af-table-stack),
  .af-panel:not(:has(> .af-panel-head)):has(> .af-table-stack) {
    background: none; border: 0; box-shadow: none; border-radius: 0;
  }
  .af-panel:has(.af-table-stack) > .af-table-wrap { overflow-x: visible; }
  .af-panel:has(> .af-panel-body > .af-table-stack) > .af-panel-body { padding: 0; }

  /* Objetivo de toque: 30 px es cómodo con el ratón y pequeño con el dedo. */
  .af-table-stack .af-table-actions .af-icon-btn { width: 40px; height: 40px; }

  /* ------------------------------------------------------------------------
     Ficha completa (`af-table-cards`): para los listados cuyas filas son una
     entidad con nombre —una persona, un curso, un documento—. Ahí el nombre de
     la columna sobra: se ve de qué habla cada dato. En los registros y los
     resultados no se usa, porque una fila con tres fechas necesita sus etiquetas.
     ------------------------------------------------------------------------ */
  .af-table-cards tbody tr { position: relative; padding: var(--space-4) 0 var(--space-3); }
  .af-table-cards tbody td {
    display: block; padding: var(--space-1) var(--space-5); gap: 0;
  }
  .af-table-cards tbody td::before { content: none; }

  /* La casilla flota sobre la ficha, sin ocupar sitio. La celda es el área de
     toque —44 px, cómodos con el dedo—, pero no se pinta: lo único que se ve es
     la casilla, que ya se explica sola y no debe parecer un botón. */
  .af-table-cards tbody td:has(> input[type="checkbox"]) {
    position: absolute; top: 0; right: 0; z-index: 2;
    display: grid; place-items: center; width: 44px; height: 44px; padding: 0;
  }
  .af-table-cards tbody td input[type="checkbox"] {
    width: 20px; height: 20px; margin: 0; accent-color: var(--action-primary); cursor: pointer;
  }
  /* Fila sin casilla (la que este administrador no puede tocar): la celda queda
     vacía y no debe dejar un hueco en la ficha. */
  .af-table-cards tbody td:first-child:not(:has(*)) { display: none; }

  /* Identidad: la primera celda que no es la casilla. El medio sube arriba y el
     nombre baja debajo, que es el orden con el que se lee una ficha. */
  .af-table-cards tbody td:first-child:not(:has(> input[type="checkbox"])) > .af-identity,
  .af-table-cards tbody td:has(> input[type="checkbox"]) + td > .af-identity {
    /* `!important` porque enfrente están las utilidades de Bootstrap del propio
       marcado (`d-inline-flex`, `align-items-center`), que ya lo llevan. */
    display: flex !important; flex-direction: column; align-items: flex-start !important;
    gap: var(--space-3); padding-right: 48px; /* deja libre la esquina de la casilla */
  }
  .af-table-cards .af-identity > * { max-width: 100%; }
  .af-table-cards .af-identity .af-avatar { width: 64px; height: 64px; font-size: var(--text-lg); }
  .af-table-cards .af-identity .af-course-thumb { width: 100%; height: auto; aspect-ratio: 16 / 9; }
  .af-table-cards .af-identity .af-course-thumb img { width: 100%; height: 100%; object-fit: cover; }

  /* Metadatos: una línea por dato, sin etiqueta, con su icono por delante. */
  .af-table-cards tbody td:not(:has(.af-table-actions)) { color: var(--text-muted); }
  .af-table-cards .af-cell-icon { width: 14px; height: 14px; flex: 0 0 auto; color: var(--text-subtle); }

  /* Las acciones, al final y detrás del engranaje que monta table-cards.js. */
  .af-table-cards tbody td:has(.af-table-actions) { padding-top: var(--space-3); }
  .af-table-stack tbody td:has(.af-row-actions) { justify-content: space-between; }
  /* El engranaje va donde se espera en una ficha: en el borde derecho, alineado
     con la casilla de arriba. Y el menú cuelga de él, no del ancho de la celda. */
  .af-table-stack .af-row-actions { display: flex; justify-content: flex-end; }
  .af-table-stack .af-row-actions .dropdown-menu { right: 0; left: auto; }
  /* Gana a `.af-table .af-table-actions`, que fija `display:flex` fuera de toda
     media query: sin esto el menú se vería siempre abierto. */
  .af-table-stack .af-row-actions .dropdown-menu { display: none; }
  .af-table-stack .af-row-actions .dropdown-menu.show {
    display: flex; flex-direction: column; align-items: stretch; min-width: 200px;
  }
  .af-row-actions .dropdown-menu .af-icon-btn {
    width: 100%; height: var(--control-h-md); justify-content: flex-start;
    gap: var(--space-3); padding: 0 var(--space-4); border: 0; background: none;
    color: var(--text-body); border-radius: var(--radius-sm);
  }
  .af-row-actions .dropdown-menu .af-icon-btn:hover { background: var(--action-ghost-hover); }
  .af-row-actions > .af-icon-btn { width: 44px; height: 44px; }
}

/* En escritorio el contenedor de acciones vuelve a ser la fila de iconos de
   siempre: es el mismo marcado, así que no hay botones duplicados. */
@media (min-width: 768px) {
  .af-row-actions .dropdown-menu {
    display: flex !important; position: static !important; transform: none !important;
    background: none; border: 0; box-shadow: none; padding: 0; min-width: 0; margin: 0;
  }
}

/* ---------------------------------------------------------------------------
   8. Badges, tags y estados
   --------------------------------------------------------------------------- */
.af-badge {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--text-muted);
  letter-spacing: var(--tracking-label); white-space: nowrap;
}
.af-badge-brand   { background: var(--surface-brand-soft); color: var(--text-brand); }
.af-badge-accent  { background: var(--surface-accent-soft); color: var(--text-accent); }
.af-badge-success { background: var(--status-success-bg); color: var(--status-success-fg); }
.af-badge-warning { background: var(--status-warning-bg); color: var(--status-warning-fg); }
.af-badge-danger  { background: var(--status-danger-bg); color: var(--status-danger-fg); }
.af-badge-info    { background: var(--status-info-bg); color: var(--status-info-fg); }
.af-badge-ai      { background: var(--status-ai-bg); color: var(--status-ai-fg); }
.af-badge-ink     { background: var(--surface-inverse); color: var(--text-inverse); }

.af-dot { width: 8px; height: 8px; border-radius: var(--radius-pill); flex: 0 0 auto; display: inline-block; }
.af-dot-success { background: var(--status-success-solid); }
.af-dot-warning { background: var(--status-warning-solid); }
.af-dot-danger  { background: var(--status-danger-solid); }
.af-dot-brand   { background: var(--action-primary); }
.af-dot-accent  { background: var(--action-accent); }

/* Píldoras de selección.

   Estado seleccionado en navy sólido, no en naranja: el naranja se reserva para las
   acciones, y una píldora marcada no es una acción, es un estado.

   La variante `af-chip-toggle` envuelve una casilla real y la esconde: así la nube de
   píldoras se maneja con el teclado y viaja en el formulario como cualquier grupo de
   casillas, sin JavaScript que la sostenga. */
.af-chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  border: 1px solid var(--border-default); background: var(--surface-card);
  color: var(--text-body); font-size: var(--text-xs); font-weight: var(--weight-medium);
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill);
  cursor: pointer; transition: var(--transition-control);
}
.af-chip:hover { background: var(--action-ghost-hover); }
.af-chip.is-selected { background: var(--action-secondary); border-color: var(--action-secondary); color: var(--text-inverse); }
.af-chip i { width: 15px; height: 15px; }

.af-chip-toggle { position: relative; margin: 0; }
.af-chip-toggle input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none;
}
.af-chip-toggle input:checked + .af-chip {
  background: var(--action-secondary); border-color: var(--action-secondary); color: var(--text-inverse);
}
.af-chip-toggle input:focus-visible + .af-chip { outline: var(--focus-ring); outline-offset: 2px; }
.af-chip-toggle input:disabled + .af-chip { opacity: .5; cursor: not-allowed; }

/* La marca de comprobación solo ocupa sitio cuando la píldora está marcada. */
.af-chip-check { display: none; }
.af-chip-toggle input:checked + .af-chip .af-chip-check { display: inline-flex; }

.af-chip-cloud { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Píldora con aspa para quitarla. La píldora en sí no hace nada —por eso pierde el
   cursor de mano—: lo pulsable es solo el aspa. Se usa en las listas que se van
   componiendo a mano, como los correos sueltos de un envío programado. */
.af-chip-fixed { cursor: default; }
.af-chip-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; border: 0; background: transparent;
  color: inherit; opacity: .65; border-radius: var(--radius-pill);
  cursor: pointer; transition: var(--transition-control);
}
.af-chip-remove:hover { opacity: 1; background: var(--action-ghost-hover); }
.af-chip-remove:focus-visible { outline: var(--focus-ring); outline-offset: 1px; opacity: 1; }
.af-chip-remove i { width: 13px; height: 13px; }
/* Avatar */
.af-avatar {
  width: 36px; height: 36px; flex: 0 0 auto; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  background: var(--surface-brand-soft); color: var(--text-brand);
  font-family: var(--font-display); font-size: var(--text-sm); font-weight: var(--weight-semibold);
}
.af-avatar img { width: 100%; height: 100%; object-fit: cover; }
.af-avatar-sm { width: 32px; height: 32px; font-size: var(--text-2xs); }
.af-avatar-lg { width: 56px; height: 56px; font-size: var(--text-lg); }
.af-avatar-xl { width: 84px; height: 84px; font-size: var(--text-2xl); }

/* Mensajes flash */
.message {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-5);
  border-radius: var(--radius-card); font-size: var(--text-sm);
  border: 1px solid transparent; cursor: pointer;
}
.message.success { background: var(--status-success-bg); color: var(--status-success-fg); }
.message.error   { background: var(--status-danger-bg); color: var(--status-danger-fg); }
.message.warning { background: var(--status-warning-bg); color: var(--status-warning-fg); }
.message.info, .message:not(.success):not(.error):not(.warning) {
  background: var(--status-info-bg); color: var(--status-info-fg);
}
.message.hidden { display: none; }

/* ---------------------------------------------------------------------------
   9. Shell
   --------------------------------------------------------------------------- */
.af-shell { display: flex; min-height: 100vh; background: var(--surface-canvas); }

.af-sidebar {
  width: var(--sidebar-w); flex: 0 0 auto;
  background: var(--surface-card);
  border-right: 1px solid var(--border-subtle);
  display: flex; flex-direction: column;
  padding: var(--space-4) var(--space-4);
  overflow: hidden;
  position: sticky; top: 0; height: 100vh;
  transition: width var(--dur-slow) var(--ease-standard);
}
.af-sidebar-head {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-6);
  min-height: 46px;
}
/* El logo lo pone el tema (o la cuenta), asi que su proporcion es desconocida: uno
   muy apaisado a 42px de alto se saldria de los 248px de la barra. Con las cuatro
   propiedades a la vez el navegador lo encoge respetando la proporcion hasta caber
   en el alto Y en el ancho, en vez de deformarlo. El `min-width:0` es lo que permite
   que el enlace, que es un item flexible, pueda encoger por debajo de su contenido. */
.af-sidebar-head > a { min-width: 0; }
.af-sidebar-logo { display: block; min-width: 0; }
.af-sidebar-logo img { max-height: 42px; max-width: 100%; height: auto; width: auto; }
.af-sidebar-mark { display: none; }
.af-sidebar-mark img { height: 28px; width: 28px; margin: 0 auto; }
/* Variante de logo según el tema: en oscuro se usan las versiones claras.
   El display va en cada variante (no en `.af-sidebar-mark img`) para que la regla
   de ocultado no la pise por especificidad y no salga el isotipo duplicado. */
.af-mark-light { display: block; }
.af-logo-dark, .af-mark-dark { display: none; }
:root[data-theme="dark"] .af-logo-light,
:root[data-theme="dark"] .af-mark-light { display: none; }
:root[data-theme="dark"] .af-logo-dark { display: inline; }
:root[data-theme="dark"] .af-mark-dark { display: block; }

.af-nav { display: flex; flex-direction: column; gap: var(--space-2); }
.af-nav-link {
  display: flex; align-items: center; gap: var(--space-3);
  height: 38px; padding: 0 var(--space-3);
  border-radius: var(--radius-control);
  color: var(--text-body); font-size: var(--text-sm); font-weight: var(--weight-medium);
  text-decoration: none; transition: var(--transition-control);
}
.af-nav-link:hover { background: var(--action-ghost-hover); color: var(--text-strong); text-decoration: none; }
.af-nav-link.active { background: var(--surface-brand-soft); color: var(--text-brand); font-weight: var(--weight-semibold); }
.af-nav-link i[data-lucide] { flex: 0 0 auto; }
.af-nav-link span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.af-sidebar-foot {
  margin-top: auto; padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.af-sidebar-user {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-2); border-radius: var(--radius-control);
  color: inherit; text-decoration: none; transition: var(--transition-control);
}
.af-sidebar-user:hover { background: var(--action-ghost-hover); text-decoration: none; }
.af-sidebar-user-name { font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.af-sidebar-user-role { font-size: var(--text-3xs); color: var(--text-muted); }
.af-sidebar-version {
  font-family: var(--font-mono); font-size: var(--text-3xs); color: var(--text-subtle);
  padding: var(--space-3) var(--space-2) 0; letter-spacing: .02em;
}

/* Estado plegado: solo iconos. */
.af-shell.is-collapsed .af-sidebar { width: var(--sidebar-w-collapsed); padding-left: var(--space-3); padding-right: var(--space-3); }
.af-shell.is-collapsed .af-sidebar-logo,
.af-shell.is-collapsed .af-nav-link span,
.af-shell.is-collapsed .af-sidebar-user-info,
.af-shell.is-collapsed .af-sidebar-version { display: none; }
.af-shell.is-collapsed .af-sidebar-mark { display: block; }
.af-shell.is-collapsed .af-nav-link { justify-content: center; padding: 0; }
.af-shell.is-collapsed .af-sidebar-user { justify-content: center; }

.af-sidebar-toggle {
  position: fixed; top: 25px; left: calc(var(--sidebar-w) - 14px);
  width: 28px; height: 28px; z-index: 45;
  border: 1px solid var(--border-subtle); border-radius: var(--radius-pill);
  background: var(--surface-card); color: var(--text-muted);
  box-shadow: var(--shadow-sm); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: left var(--dur-slow) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}
.af-sidebar-toggle:hover { color: var(--text-strong); box-shadow: var(--shadow-md); }
.af-shell.is-collapsed .af-sidebar-toggle { left: calc(var(--sidebar-w-collapsed) - 14px); }

.af-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.af-topbar {
  height: var(--topbar-h); flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-4);
  padding: 0 var(--gutter-panel);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-topbar);
  backdrop-filter: var(--blur-glass);
  -webkit-backdrop-filter: var(--blur-glass);
  position: sticky; top: 0; z-index: 40;
}
.af-topbar-title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: var(--weight-semibold);
  color: var(--text-strong); letter-spacing: var(--tracking-heading); line-height: 1.15; margin: 0; }
.af-crumbs { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-2xs);
  color: var(--text-subtle); margin-bottom: 3px; list-style: none; padding: 0; }
.af-crumbs a { color: var(--text-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
.af-crumbs a:hover { color: var(--text-strong); text-decoration: none; }
.af-crumbs .af-crumb-current { color: var(--text-body); font-weight: var(--weight-semibold); }

.af-topbar-search { width: 250px; }
.af-notif-dot {
  position: absolute; top: 5px; right: 6px; width: 8px; height: 8px;
  border-radius: var(--radius-pill); background: var(--action-primary);
  box-shadow: 0 0 0 2px var(--surface-card); pointer-events: none;
}

.af-content { flex: 1; padding: var(--space-8) var(--gutter-panel); }
.af-content-narrow { max-width: var(--content-max); margin: 0 auto; width: 100%; }

.af-page-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-5); flex-wrap: wrap; margin-bottom: var(--space-7); }
/* Las acciones de la cabecera siempre a la derecha. Cuando no caben junto al título
   la fila baja de línea, y sola en su línea `space-between` la dejaba a la izquierda. */
.af-page-head > .af-row { margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
.af-page-head h1 { font-size: var(--text-3xl); }
.af-page-head p { color: var(--text-muted); font-size: var(--text-sm); margin: var(--space-2) 0 0; max-width: var(--prose-max); }

/* Hoja inferior con las acciones de la cabecera (ver templates/element/page_actions.php).
   Por debajo del punto de corte, page-actions.js trae aquí los botones de la fila.
   El alto de toque se normaliza dentro de la hoja: un botón que en la cabecera es
   `btn-sm` recupera aquí el alto normal sin que el JS le toque ninguna clase. */
.af-actions-sheet { --bs-offcanvas-height: auto;
  border-top-left-radius: var(--radius-panel); border-top-right-radius: var(--radius-panel); }
.af-actions-sheet .offcanvas-body { display: grid; gap: var(--space-3); max-height: 60vh; overflow-y: auto; }
.af-actions-sheet .offcanvas-body .btn { width: 100%; justify-content: center; height: var(--control-h-md); }

/* ---------------------------------------------------------------------------
   10. Patrones
   --------------------------------------------------------------------------- */
.af-kpi { padding: var(--space-5) var(--gutter-card); }
.af-kpi-label { font-size: var(--text-2xs); color: var(--text-muted); display: flex; align-items: center; gap: var(--space-2); }
.af-kpi-value { font-family: var(--font-display); font-size: var(--text-3xl); font-weight: var(--weight-semibold);
  color: var(--text-strong); line-height: 1.05; margin: var(--space-3) 0 0; letter-spacing: var(--tracking-display); }
.af-kpi-foot { font-size: var(--text-2xs); color: var(--text-muted); margin-top: var(--space-2); display: flex; align-items: center; gap: var(--space-2); }
.af-kpi-trend-up { color: var(--status-success-fg); font-weight: var(--weight-semibold); }
.af-kpi-trend-down { color: var(--status-danger-fg); font-weight: var(--weight-semibold); }

.af-hero { padding: var(--space-7) var(--space-8); display: flex; flex-direction: column; gap: var(--space-4); }
.af-hero-title { font-family: var(--font-display); font-size: var(--text-3xl); font-weight: var(--weight-semibold);
  color: #fff; line-height: 1.15; letter-spacing: var(--tracking-display); margin: 0; max-width: 24ch; }
.af-hero-sub { font-size: var(--text-sm); color: rgba(255, 255, 255, .7); max-width: 56ch; margin: 0; }
.af-hero-live {
  display: inline-flex; align-items: center; gap: var(--space-3); align-self: flex-start;
  background: rgba(255, 255, 255, .14); color: #fff;
  font-size: var(--text-3xs); font-weight: var(--weight-bold); letter-spacing: var(--tracking-eyebrow);
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-pill); text-transform: uppercase;
}
.af-hero-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); margin-top: auto; }
.af-hero-stat { background: rgba(255, 255, 255, .10); border: 1px solid var(--border-inverse);
  border-radius: var(--radius-md); padding: var(--space-4); }
.af-hero-stat-value { font-family: var(--font-display); font-size: var(--text-xl); font-weight: var(--weight-semibold); color: #fff; }
.af-hero-stat-label { font-size: var(--text-3xs); color: rgba(255, 255, 255, .65); margin-top: 2px; }

/* Lista de dos líneas con marca de tiempo, como el «Pulso de hoy». */
.af-timeline { display: flex; flex-direction: column; gap: var(--space-4); }
.af-timeline-item { display: flex; gap: var(--space-3); align-items: flex-start; }
.af-timeline-time { font-family: var(--font-mono); font-size: var(--text-3xs); color: var(--text-subtle);
  width: 38px; flex: 0 0 auto; padding-top: 2px; }
.af-timeline-title { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-strong); line-height: 1.3; }
.af-timeline-sub { font-size: var(--text-2xs); color: var(--text-muted); margin-top: 1px; }

/* Lista de datos en ficha. */
.af-detail-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-5) var(--space-7); margin: 0; }
.af-detail-list dt { font-size: var(--type-eyebrow-size); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase; color: var(--text-muted); }
.af-detail-list dd { margin: var(--space-1) 0 0; color: var(--text-strong); font-size: var(--text-sm); }

/* Pasos numerados de una pantalla explicativa. El número lo pone el navegador:
   así se renumeran solos al insertar un paso y no hay que tocar el marcado. */
.af-steps-list { margin: 0; padding-left: var(--space-6); color: var(--text-body); font-size: var(--text-sm); }
.af-steps-list li + li { margin-top: var(--space-4); }
.af-steps-list li::marker { color: var(--text-brand); font-weight: var(--weight-semibold); }
.af-steps-list strong { display: block; color: var(--text-strong); }

/* Estado vacío: borde discontinuo, según el sistema. */
.af-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-4); padding: var(--space-11) var(--space-7);
  border: 1px dashed var(--border-default); border-radius: var(--radius-card);
  background: var(--surface-card);
}
.af-empty h3 { font-size: var(--text-lg); }
.af-empty p { color: var(--text-muted); font-size: var(--text-sm); margin: 0; max-width: 46ch; }

/* Pasos del instalador. */
.af-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.af-step {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-card);
  background: var(--surface-card);
}
.af-step-number {
  width: 26px; height: 26px; flex: 0 0 auto; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-sunken); color: var(--text-muted);
  font-size: var(--text-2xs); font-weight: var(--weight-bold);
}
.af-step-text { flex: 1; font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-body); }
.af-step-status { font-size: var(--text-2xs); color: var(--text-subtle); }
.af-step.is-current { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.af-step.is-current .af-step-number { background: var(--gradient-brand); color: #fff; }
.af-step.is-complete .af-step-number { background: var(--status-success-bg); color: var(--status-success-fg); }
.af-step.is-complete .af-step-status { color: var(--status-success-fg); font-weight: var(--weight-semibold); }

/* Diagnóstico del servidor y recuperación de claves, en el paso de configuración. */
.af-diagnostics {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-5);
}
.af-check-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--space-2) var(--space-4);
}
.af-check { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.af-check.is-ok { color: var(--status-success-fg); }
.af-check.is-warn { color: var(--status-warning-fg); }
.af-recovery {
  border: 1px solid var(--border-subtle); border-radius: var(--radius-card);
  background: var(--surface-sunken); padding: var(--space-5);
}

/* Recortador de foto. */
.af-cropper-preview {
  width: 84px; height: 84px; flex: 0 0 auto; border-radius: var(--radius-pill);
  background: var(--surface-sunken); border: 1px solid var(--border-subtle);
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  color: var(--text-subtle);
}
.af-cropper-preview img { width: 100%; height: 100%; object-fit: cover; }
.af-cropper-stage {
  position: relative; width: 100%; aspect-ratio: 1; max-width: 340px; margin: 0 auto;
  overflow: hidden; border-radius: var(--radius-card); background: var(--surface-stage); cursor: grab;
}
.af-cropper-stage:active { cursor: grabbing; }
.af-cropper-stage img { position: absolute; transform-origin: 0 0; user-select: none; -webkit-user-drag: none; }
.af-cropper-mask {
  position: absolute; inset: 0; pointer-events: none;
  box-shadow: 0 0 0 9999px color-mix(in srgb, var(--surface-stage) 62%, transparent); border-radius: var(--radius-pill);
}

/* Recortador de imagen 16:9 (cursos y otros contenidos anchos). */
.af-imgcrop-preview {
  width: 220px; max-width: 100%; aspect-ratio: 16 / 9; flex: 0 0 auto;
  border-radius: var(--radius-card); background: var(--surface-sunken);
  border: 1px solid var(--border-subtle); overflow: hidden; color: var(--text-subtle);
  display: inline-flex; align-items: center; justify-content: center;
}
.af-imgcrop-preview img { width: 100%; height: 100%; object-fit: cover; }
.af-imgcrop-stage {
  position: relative; width: 100%; aspect-ratio: 16 / 9; max-width: 560px; margin: 0 auto;
  overflow: hidden; border-radius: var(--radius-card); background: var(--surface-stage); cursor: grab;
}
.af-imgcrop-stage:active { cursor: grabbing; }
.af-imgcrop-stage img { position: absolute; transform-origin: 0 0; user-select: none; -webkit-user-drag: none; }
.af-imgcrop-frame {
  position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .85); border-radius: var(--radius-card);
}

/* Recortador de marca (logo / isotipo). Proporción variable vía --brand-ratio.
   Fondo a cuadros para que se vea la transparencia del PNG. */
.af-brandcrop-preview {
  height: 72px; aspect-ratio: var(--brand-ratio, 1 / 1); max-width: 100%; flex: 0 0 auto;
  border-radius: var(--radius-md); border: 1px solid var(--border-subtle); overflow: hidden;
  color: var(--text-subtle); display: inline-flex; align-items: center; justify-content: center;
  background-color: var(--surface-sunken);
  background-image:
    linear-gradient(45deg, rgba(128, 128, 128, .18) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, .18) 75%),
    linear-gradient(45deg, rgba(128, 128, 128, .18) 25%, transparent 25%, transparent 75%, rgba(128, 128, 128, .18) 75%);
  background-size: 14px 14px; background-position: 0 0, 7px 7px;
}
.af-brandcrop-preview img { width: 100%; height: 100%; object-fit: contain; }
.af-brandcrop-stage {
  position: relative; width: 100%; aspect-ratio: var(--brand-ratio, 1 / 1); max-width: 560px; margin: 0 auto;
  overflow: hidden; border-radius: var(--radius-card); cursor: grab;
  background-color: var(--surface-stage);
  background-image:
    linear-gradient(45deg, rgba(255, 255, 255, .07) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .07) 75%),
    linear-gradient(45deg, rgba(255, 255, 255, .07) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .07) 75%);
  background-size: 20px 20px; background-position: 0 0, 10px 10px;
}
.af-brandcrop-stage:active { cursor: grabbing; }
.af-brandcrop-stage img { position: absolute; transform-origin: 0 0; user-select: none; -webkit-user-drag: none; }
.af-brandcrop-frame {
  position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .85); border-radius: var(--radius-card);
}
.af-color-swatch {
  width: 40px; height: 38px; flex: 0 0 auto; padding: 2px; cursor: pointer;
  border: 1px solid var(--border-default); border-radius: var(--radius-md);
  background: var(--surface-card);
}
.af-color-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.af-color-swatch::-webkit-color-swatch { border: none; border-radius: calc(var(--radius-md) - 3px); }
.af-color-swatch::-moz-color-swatch { border: none; border-radius: calc(var(--radius-md) - 3px); }

/* Editor de texto enriquecido ligero. */
.af-rte { display: flex; flex-direction: column; gap: 0; }
.af-rte-toolbar {
  display: flex; flex-wrap: wrap; gap: var(--space-1);
  padding: var(--space-2); border: 1px solid var(--border-subtle);
  border-bottom: 0; border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: var(--surface-sunken);
}
.af-rte-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-subtle); cursor: pointer;
}
.af-rte-btn:hover { background: var(--surface-1); color: var(--text-strong); border-color: var(--border-subtle); }
.af-rte-btn i { width: 16px; height: 16px; }
.af-rte-area {
  min-height: 180px; border-radius: 0 0 var(--radius-md) var(--radius-md);
  overflow-y: auto; line-height: 1.55;
}
.af-rte-area:focus { outline: none; box-shadow: none; }

/* Aspecto del texto enriquecido, compartido por el área de edición y por TODOS los
   sitios donde luego se muestra lo guardado (`.af-rich-content`: descripción de un
   curso, enunciado de una pregunta, mensaje de un foro, corrección de una práctica…).
   Van juntos a propósito. Mientras el editor tuvo estilos propios y lo mostrado
   heredaba los de la página, un título escrito con la barra de herramientas se veía
   en `--text-lg` al escribirlo y saltaba a `--text-2xl` al guardarlo: parecía que
   guardar cambiaba el formato, y lo que cambiaba era la hoja de estilos. Lo que se
   escribe aquí es texto dentro de una página, no la portada de la página, así que
   sus títulos se quedan por debajo de los de la pantalla que los enmarca. */
.af-rich-content, .af-rte-area { line-height: 1.55; }
/* El área de edición hereda de `.form-control` el tamaño de un campo de
   formulario, y lo que se escribe dentro no es un campo: es el texto de una
   página. Con `--text-sm` se escribía a 14px lo que luego se leía a 15px. */
.af-rte-area { font-size: var(--type-body-size); }
.af-rich-content h2, .af-rte-area h2 { font-size: var(--text-xl); margin: .8em 0 .3em; letter-spacing: normal; }
.af-rich-content h3, .af-rte-area h3 { font-size: var(--text-lg); margin: .6em 0 .3em; letter-spacing: normal; }
.af-rich-content h4, .af-rte-area h4 { font-size: var(--text-base); margin: .6em 0 .3em; letter-spacing: normal; }
.af-rich-content h5, .af-rte-area h5 { font-size: var(--text-md); margin: .6em 0 .3em; letter-spacing: normal; }
.af-rich-content ul, .af-rich-content ol,
.af-rte-area ul, .af-rte-area ol { padding-left: 1.4em; margin: .4em 0; }
.af-rich-content p, .af-rte-area p { margin: 0 0 .6em; }
.af-rich-content blockquote, .af-rte-area blockquote {
  margin: .6em 0; padding-left: var(--space-4);
  border-left: 3px solid var(--border-subtle); color: var(--text-muted);
}
.af-rich-content pre, .af-rte-area pre {
  margin: .6em 0; padding: var(--space-3); overflow-x: auto;
  background: var(--surface-sunken); border-radius: var(--radius-sm);
}
.af-rich-content img, .af-rte-area img { max-width: 100%; height: auto; }
.af-rich-content table, .af-rte-area table { width: 100%; margin: .6em 0; border-collapse: collapse; }
.af-rich-content th, .af-rich-content td,
.af-rte-area th, .af-rte-area td {
  border: 1px solid var(--border-subtle); padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm); text-align: left; vertical-align: top;
}
.af-rich-content > *:first-child, .af-rte-area > *:first-child { margin-top: 0; }
.af-rich-content > *:last-child, .af-rte-area > *:last-child { margin-bottom: 0; }
/* Editor de plantillas de correo: área enriquecida más alta para cuerpos largos. */
[data-email-template-editor] .af-rte-area { min-height: 420px; }

/* Códigos de sustitución (píxeles de shortcode). Compartidos por el editor de
   diplomas y el de plantillas de correo para que sean visualmente idénticos. */
.af-shortcode-pill {
  border: 1px solid var(--border-default);
  background: var(--surface-card);
  color: var(--text-body);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  font-size: var(--text-3xs);
  cursor: pointer;
  transition: var(--transition-control);
}
.af-shortcode-pill:hover { background: var(--surface-brand-soft); border-color: var(--border-brand); color: var(--text-strong); }
.af-shortcode-pill.is-copied { background: var(--status-success-bg); border-color: transparent; }

/* Buscador predictivo (configuración y plantillas de correo). */
.af-config-search { position: relative; max-width: 620px; }
.af-config-search-results {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 8px);
  list-style: none; margin: 0; padding: 6px;
  background: var(--surface-card); border: 1px solid var(--border-default);
  border-radius: var(--radius-panel); box-shadow: var(--shadow-lg);
  max-height: 360px; overflow-y: auto;
}
.af-config-search-item {
  display: block; width: 100%; text-align: left; padding: 10px 12px;
  border: 0; background: transparent; border-radius: var(--radius-md);
  cursor: pointer; color: inherit;
}
.af-config-search-item:hover,
.af-config-search-item.is-active { background: var(--surface-brand-soft); }
.af-config-search-item .af-cs-opt { display: block; font-weight: 600; }
.af-config-search-item .af-cs-crumb {
  display: flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); color: var(--text-muted); margin-top: 2px;
}
.af-config-search-item mark {
  background: var(--surface-accent-soft); color: inherit;
  padding: 0 1px; border-radius: 3px;
}
/* Fijado en el teléfono al bajar por la pantalla (config-search.js lo pone y lo
   quita). Mismo aspecto que la barra de filtros móvil. */
@media (max-width: 767.98px) {
  .af-config-search.is-stuck {
    position: fixed; top: var(--topbar-h); left: 0; right: 0; z-index: 35;
    max-width: none; margin: 0 !important;
    padding: var(--space-3) var(--space-5);
    background: var(--surface-card); box-shadow: var(--shadow-md);
  }
  .af-config-search.is-stuck .af-config-search-results {
    left: var(--space-5); right: var(--space-5); max-height: calc(100dvh - var(--topbar-h) - 90px);
  }
}

/* Buscador global de la barra superior.

   El desplegable se ancla a la derecha y es más ancho que el campo: el campo mide
   250px porque tiene que convivir con los iconos de la barra, pero los resultados
   llevan nombres de personas y de cursos que ahí no caben. */
.af-gs-kbd {
  position: absolute; right: var(--space-2); top: 50%; transform: translateY(-50%);
  font-family: inherit; font-size: var(--text-3xs); color: var(--text-subtle);
  background: var(--surface-2); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm); padding: 1px 5px; pointer-events: none;
}
.af-topbar-search .form-control { padding-right: 56px; }
.af-gs-panel {
  position: absolute; z-index: 1040; right: 0; top: calc(100% + 8px);
  width: min(440px, 80vw); padding: 6px;
  background: var(--surface-card); border: 1px solid var(--border-default);
  border-radius: var(--radius-panel); box-shadow: var(--shadow-lg);
  max-height: min(70vh, 520px); overflow-y: auto;
}
.af-gs-note { margin: 0; padding: var(--space-4); text-align: center; color: var(--text-muted); font-size: var(--text-sm); }
/* Aviso de «buscando». Va pegado arriba y con fondo propio para que se lea como
   una barra de estado del desplegable y no como un resultado más. */
.af-gs-loading {
  position: sticky; top: -6px; z-index: 1;
  display: flex; align-items: center; gap: var(--space-2);
  margin: -6px -6px 0; padding: 10px 14px;
  background: var(--surface-card); border-bottom: 1px solid var(--border-subtle);
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
  font-size: var(--text-xs); color: var(--text-muted);
}
.af-gs-loading .spinner-border { width: .85rem; height: .85rem; border-width: .12em; color: var(--action-primary); }
.af-gs-group + .af-gs-group { margin-top: var(--space-2); border-top: 1px solid var(--border-subtle); padding-top: var(--space-2); }
.af-gs-group-head {
  margin: 0; padding: 4px 12px;
  font-size: var(--text-3xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-subtle);
}
/* Cada resultado es una rejilla de dos columnas: el hueco de la imagen y el texto.
   Con el icono dentro de la línea del título quedaba pegado arriba y desalineado
   respecto a un resultado de dos líneas; así el hueco se centra contra el bloque
   entero, mida lo que mida. */
.af-gs-item {
  display: grid; grid-template-columns: 28px minmax(0, 1fr);
  gap: 10px; align-items: center; text-decoration: none;
}
.af-gs-item:hover { text-decoration: none; }
.af-gs-media {
  width: 28px; height: 28px; align-self: center;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex: none;
  font-family: var(--font-display); font-size: var(--text-3xs);
  font-weight: var(--weight-semibold); line-height: 1;
}
.af-gs-media svg { width: 16px; height: 16px; color: var(--text-subtle); }
.af-gs-media img { width: 100%; height: 100%; object-fit: cover; }
/* Redondo para las personas y cuadrado para los cursos, como en el resto de la
   plataforma: la forma ya dice qué clase de cosa es antes de leer nada. */
.af-gs-media-round {
  border-radius: var(--radius-pill);
  background: var(--surface-brand-soft); color: var(--text-brand);
}
.af-gs-media-square { border-radius: var(--radius-sm); background: var(--surface-2); }
.af-gs-body { min-width: 0; }
.af-gs-item-head { display: flex; align-items: center; gap: var(--space-2); }
.af-gs-item-head .af-cs-opt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.af-gs-item .af-cs-crumb { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.af-gs-badge {
  flex: none; font-size: var(--text-3xs); color: var(--text-muted);
  background: var(--surface-2); border-radius: var(--radius-pill); padding: 1px 8px;
}
.af-gs-all, .af-gs-more {
  display: block; padding: 8px 12px; border-radius: var(--radius-md);
  font-size: var(--text-xs); color: var(--text-muted); text-decoration: none;
}
.af-gs-more {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2); border-top: 1px solid var(--border-subtle);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.af-gs-more svg { width: 14px; height: 14px; flex: none; }
.af-gs-all:hover, .af-gs-more:hover,
.af-gs-all.is-active, .af-gs-more.is-active { background: var(--surface-brand-soft); color: var(--text-strong); }

/* Página de resultados: los mismos grupos, sin el tope del desplegable. */
.af-search-page { max-width: 820px; }
.af-search-tabs { display: flex; flex-wrap: wrap; gap: .35rem; }
.af-search-tab {
  font-size: var(--text-xs); text-decoration: none; color: var(--text-muted);
  padding: .3rem .7rem; border-radius: 999px; border: 1px solid var(--border-subtle);
}
.af-search-tab:hover { color: var(--text-strong, inherit); }
.af-search-tab.is-active {
  color: var(--action-primary); border-color: var(--action-primary);
  background: color-mix(in srgb, var(--action-primary) 8%, transparent);
}
.af-search-group + .af-search-group { margin-top: var(--space-5); }

/* Panel de asociación de participantes: lista seleccionable de usuarios. */
.min-h-0 { min-height: 0; }
/*
 * `min-width: 0` para que `text-truncate` funcione dentro de un flex.
 *
 * Un hijo de un contenedor flex tiene `min-width: auto`, así que no se encoge por
 * debajo de su contenido y los puntos suspensivos no aparecen nunca: el nombre largo
 * empuja el resto de la fila fuera y deja una barra de scroll horizontal.
 *
 * La clase se venía usando desde hace versiones en los selectores de documentos
 * —el del temario, el de las prácticas, el de la guía de FUNDAE, el de noticias, el
 * del CMS— sin estar definida en ninguna parte, que es exactamente por lo que todos
 * ellos desbordaban con un fichero de nombre largo.
 */
.min-w-0 { min-width: 0; }
.af-pick-list {
  overflow-y: auto; border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--surface-1); padding: var(--space-2);
}
.af-pick-row {
  display: flex; align-items: center; gap: var(--space-3); width: 100%;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  cursor: pointer; margin: 0;
}
.af-pick-row:hover { background: var(--surface-sunken); }
.af-pick-row input[type="checkbox"] { flex: 0 0 auto; }
/*
 * La fila también puede ser un enlace: así enseña la pantalla de aceptación los
 * documentos legales. Sin esto hereda del tema el color y el subrayado de enlace, y
 * una lista entera de filas subrayadas no se parece a ninguna otra de la plataforma.
 * Lo que marca que se puede pulsar es el fondo al pasar por encima, como en el resto.
 */
a.af-pick-row, a.af-pick-row:hover { color: inherit; text-decoration: none; }
/*
 * Fila que se enseña pero no se puede elegir (p. ej. un PDF sin URL pública en el
 * selector de documentos legales). Se atenúa pero SIGUE siendo pulsable: al
 * pulsarla se explica qué le falta, que es mejor que no responder.
 */
.af-pick-row-muted { opacity: .6; }

/* Árbol de contenidos del curso (módulos, submódulos y materiales). */
.af-tree { --af-tree-indent: 20px; }
.af-tree-list { list-style: none; margin: 0; padding: 0; }
.af-tree-item .af-tree-list { padding-left: var(--af-tree-indent); }
.af-tree-item { margin: 0; }
.af-tree-row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); margin-bottom: var(--space-2);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--surface-1);
}
.af-tree-module > .af-tree-row { background: var(--surface-sunken); }
.af-tree-toggle {
  flex: 0 0 auto; width: 22px; height: 22px; padding: 0; border: 0; background: transparent;
  color: var(--text-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
}
.af-tree-toggle:hover { background: var(--surface-1); color: var(--text-strong); }
/* Lucide reemplaza el <i> por un <svg>, así que la rotación anima sobre el svg. */
.af-tree-toggle i, .af-tree-toggle svg { width: 16px; height: 16px; transition: transform .2s ease; }
.af-tree-toggle-empty { pointer-events: none; }
.af-tree-count { flex: 0 0 auto; }
/* Módulo plegado: se oculta su contenido y el chevron gira hacia la derecha. */
.af-tree-collapsed > .af-tree-row .af-tree-toggle i,
.af-tree-collapsed > .af-tree-row .af-tree-toggle svg { transform: rotate(-90deg); }
.af-tree-collapsed > .af-tree-list { display: none; }

/* Selector de documentos: carpetas y migas. */
.af-pick-folder { width: 100%; border: 0; background: transparent; text-align: left; cursor: pointer; color: inherit; }
.af-crumbs .af-crumb-link {
  border: 0; background: transparent; padding: 0; cursor: pointer;
  color: var(--text-link); font-size: var(--text-2xs); display: inline-flex; align-items: center; gap: 3px;
}
.af-crumbs .af-crumb-link:hover { text-decoration: underline; }
.af-crumbs .af-crumb-sep { color: var(--text-subtle); margin: 0 5px; font-size: var(--text-2xs); }
.af-tree-check { flex: 0 0 auto; margin: 0; cursor: pointer; }
/* Fila seleccionada: el borde de marca se ve tanto en material como en modulo. */
.af-tree-item.is-checked > .af-tree-row { border-color: var(--action-primary); background: var(--surface-brand-soft); }
.af-tree-grip { cursor: grab; color: var(--text-subtle); display: inline-flex; flex: 0 0 auto; }
.af-tree-grip i { width: 16px; height: 16px; }
.af-tree-icon { display: inline-flex; flex: 0 0 auto; color: var(--text-muted); }
.af-tree-icon i { width: 17px; height: 17px; }
.af-tree-title { flex: 1 1 auto; min-width: 0; font-weight: var(--weight-medium); }
.af-tree-type { flex: 0 0 auto; }
.af-tree-actions { display: inline-flex; align-items: center; gap: var(--space-1); flex: 0 0 auto; }
.af-tree-dragging { opacity: .45; }
.af-tree-list.af-drop-hint { outline: 2px dashed var(--action-primary); outline-offset: 2px; border-radius: var(--radius-md); min-height: 14px; }
/* Al soltar dentro de un módulo se resalta su fila. */
.af-tree-module.af-drop-into > .af-tree-row { outline: 2px solid var(--action-primary); outline-offset: 1px; background: var(--surface-brand-soft); }
/* Los módulos vacíos conservan una pequeña zona para poder soltar dentro. */
.af-tree-module > .af-tree-list:empty { min-height: 12px; margin-bottom: var(--space-2); }

/* --- Reordenar arrastrando (0.124.0) -------------------------------------- */
/* Compartido por el temario del curso y el gestor documental. Ver af-reorder.js. */
.af-grip { cursor: grab; color: var(--text-subtle); display: inline-flex; align-items: center; }
.af-grip:active { cursor: grabbing; }
.af-grip:hover { color: var(--text-muted); }
.af-cell-grip { width: 28px; padding-right: 0; }
.af-row-dragging { opacity: .45; }
/* Línea de inserción: dice exactamente en qué hueco va a caer lo que se arrastra,
   que es más claro que resaltar la fila de al lado. En una tabla no se puede meter
   un elemento entre filas, así que es un borde. */
.af-drop-before > td, .af-drop-before { box-shadow: inset 0 2px 0 0 var(--action-primary); }
.af-drop-after > td, .af-drop-after { box-shadow: inset 0 -2px 0 0 var(--action-primary); }
/* Destello de lo recién soltado. */
@keyframes af-reorder-flash {
  from { background-color: var(--surface-brand-soft); }
  to { background-color: transparent; }
}
.af-reorder-flash, .af-reorder-flash > td { animation: af-reorder-flash .7s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .af-reorder-flash, .af-reorder-flash > td { animation: none; }
}

/* Zona de arrastre de ficheros (subida). */
.af-dropzone {
  border: 2px dashed var(--border-subtle); border-radius: var(--radius-md);
  padding: var(--space-6); text-align: center; cursor: pointer;
  color: var(--text-muted); transition: border-color .15s, background .15s;
}
.af-dropzone:hover { border-color: var(--action-primary); background: var(--surface-sunken); }
.af-dropzone.is-dragover { border-color: var(--action-primary); background: var(--surface-sunken); }

/* Barra de acciones en bloque sobre el listado de participantes. */
.af-bulkbar {
  display: flex; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--surface-sunken);
}
.af-bulkbar.d-none { display: none !important; }
/* Variante fijada: en el temario la lista es larga y la barra tiene que seguir
   alcanzable mientras se marcan contenidos. Fondo opaco y sombra para que el arbol
   no se transparente por debajo al desplazar. */
.af-bulkbar-sticky {
  position: sticky; top: calc(var(--topbar-h) + var(--space-3)); z-index: 5;
  background: var(--surface-1); box-shadow: var(--shadow-sm);
}
/* En un teléfono el listado se desplaza mucho y una barra de acciones que se
   pierde por arriba no sirve de nada: ahí se fija siempre, sin que cada pantalla
   tenga que pedirlo. Se cuelga bajo la barra superior, que también es pegajosa y
   mide `--topbar-h`, y con menos z-index que ella para no taparla. Va pegada a
   ella y no a unos píxeles: por cualquier hueco entre las dos se vería pasar el
   contenido, y eso se lee como un fallo de pintado. */
@media (max-width: 767.98px) {
  .af-bulkbar {
    /* Si la pantalla tiene barra de filtros fijada, la de acciones se cuelga debajo
       (filter-bar.js publica su alto en `--filterbar-h`). */
    position: sticky; top: calc(var(--topbar-h) + var(--filterbar-h, 0px)); z-index: 20;
    background: var(--surface-card); box-shadow: var(--shadow-md);
  }
}

/* ---------------------------------------------------------------------------
   Barra de filtros móvil (0.141.0)

   En un teléfono, un buscador de listado ocupa media pantalla antes de enseñar un
   solo resultado, y en cuanto se baja deja de estar a mano. Por debajo de 768 px
   el formulario se convierte en una barra fijada bajo la barra superior: el campo
   principal con un «+» delante, que despliega el resto de filtros como un menú, y
   el botón de buscar detrás. En tablet y escritorio no hay ninguna regla: el
   formulario es exactamente el de siempre, y los dos botones nuevos llevan
   `d-md-none`.

   Marcado (ver templates/element/filterbar/*):
     [data-filterbar].af-filterbar        la sección o el formulario
       form > [data-filterbar-main]       columna del campo principal
         .af-filterbar-main-row           toggle + campo + botón de buscar
       form > (resto de columnas)         se ocultan salvo con `.is-open`
   --------------------------------------------------------------------------- */
.af-filterbar-main-row { display: flex; align-items: center; gap: var(--space-2); }
.af-filterbar-main-row > .form-control,
.af-filterbar-main-row > .af-field { flex: 1 1 auto; min-width: 0; }
/* Cuadrados del alto del campo. No se usa `.af-icon-btn`: pisa el fondo y el borde
   de `.btn-brand` / `.btn-outline-brand`, y los dos botones perderían su color. */
.af-filterbar-toggle, .af-filterbar-submit {
  flex: 0 0 auto; width: var(--control-h-md, 42px); height: var(--control-h-md, 42px);
  padding: 0; align-items: center; justify-content: center;
}
.af-filterbar-toggle { position: relative; }
@media (max-width: 767.98px) {
  .af-filterbar-toggle, .af-filterbar-submit { display: inline-flex; }
}
.af-filterbar-toggle svg { transition: transform var(--duration-fast, .15s) ease; }
.af-filterbar-dot {
  position: absolute; top: 4px; right: 4px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--action-primary); box-shadow: 0 0 0 2px var(--surface-card);
}
@media (max-width: 767.98px) {
  .af-filterbar {
    position: sticky; top: var(--topbar-h); z-index: 25;
    margin-inline: calc(-1 * var(--space-5));
    border-radius: 0; border-left: 0; border-right: 0;
    background: var(--surface-card); box-shadow: var(--shadow-md);
    max-height: calc(100dvh - var(--topbar-h));
    /* `overflow-x` explícito: con solo `overflow-y: auto` el eje X pasa también a
       `auto`, y los márgenes negativos de `.row` sacan una barra de desplazamiento. */
    overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  }
  /* Sin tarjeta alrededor (el buscador del catálogo) también tiene que ser barra. */
  form.af-filterbar { padding: var(--space-3) var(--space-5); }
  .af-filterbar .af-panel-body { padding: var(--space-3) var(--space-5); }
  .af-filterbar form { row-gap: var(--space-3); }
  /* La etiqueta del campo principal sobra en la barra: el marcador de posición ya
     dice qué se busca. Se oculta visualmente, no para los lectores de pantalla. */
  .af-filterbar [data-filterbar-main] > label {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .af-filterbar:not(.is-open) form > :not([data-filterbar-main]),
  form.af-filterbar:not(.is-open) > :not([data-filterbar-main]) { display: none !important; }
  .af-filterbar.is-open .af-filterbar-toggle svg { transform: rotate(45deg); }
  .af-filterbar.is-open .af-filterbar-dot { display: none; }
}

/* Miniatura de curso en el listado (proporción 16:9, altura de una fila). */
.af-course-thumb {
  flex: 0 0 auto; width: 64px; height: 36px; border-radius: var(--radius-sm);
  overflow: hidden; background: var(--surface-sunken); border: 1px solid var(--border-subtle);
  display: inline-flex; align-items: center; justify-content: center; color: var(--text-subtle);
}
.af-course-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* En pantalla grande la carátula es la pista visual con la que el administrador
   localiza el curso, así que se ve un 50 % más grande que la miniatura base, y
   crece otro 50 % al pasar el ratón por la fila, a modo de lupa. Se mantiene el
   16:9 en los tres tamaños para que `object-fit: cover` no recorte de más.
   Fuera de este bloque no se toca nada: en móvil manda la ficha de
   `.af-table-cards`, que la pinta a todo el ancho. */
@media (min-width: 768px) {
  .af-course-thumb { width: 96px; height: 54px; transition: width .2s ease, height .2s ease; }

  /* Solo con puntero: en táctil el `:hover` se queda pegado tras el toque. */
  @media (hover: hover) {
    .af-table tbody tr:hover .af-course-thumb { width: 144px; height: 81px; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .af-course-thumb { transition: none; }
}

/* Overlay de carga a pantalla completa (bloquea la interacción durante un envío). */
.af-loading-overlay {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-overlay); backdrop-filter: blur(3px);
}
.af-loading-box {
  display: inline-flex; align-items: center; gap: var(--space-3);
  padding: var(--space-5) var(--space-6); border-radius: var(--radius-card);
  background: var(--surface-card); border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-lg); color: var(--text-strong);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
}
.af-loading-box .spinner-border { color: var(--action-primary); width: 1.4rem; height: 1.4rem; }

/* Variante apilada del overlay, para esperas largas: al spinner se le suman un
   texto de apoyo, una barra de avance y una estimación de tiempo, de modo que la
   espera no parezca un cuelgue. */
.af-loading-box-block {
  flex-direction: column; gap: var(--space-2); text-align: center;
  max-width: 26rem; padding: var(--space-6);
}
.af-loading-box-block .spinner-border { width: 2rem; height: 2rem; margin-bottom: var(--space-2); }
.af-loading-box-block .af-loading-title { font-size: var(--text-md); }
.af-loading-box-block .af-loading-text {
  margin: 0; font-size: var(--text-2xs); font-weight: var(--weight-regular); color: var(--text-muted);
}
.af-loading-progress {
  width: 100%; height: 6px; margin-top: var(--space-2);
  border-radius: 999px; background: var(--surface-sunken); overflow: hidden;
}
.af-loading-progress > span {
  display: block; width: 0; height: 100%; border-radius: 999px;
  background: var(--action-primary); transition: width .4s linear;
}
.af-loading-eta {
  margin: 0; font-size: var(--text-2xs); color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}

/* Árbol de estructura del itinerario: módulos con cursos como cards visuales. */
.af-itinerary-module-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  background: var(--surface-sunken); color: var(--brand-primary);
}
.af-itinerary-course-card {
  display: flex; align-items: stretch; height: 100%;
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  overflow: hidden; text-decoration: none; color: var(--text-strong);
  background: var(--surface-card);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.af-itinerary-course-card:hover {
  border-color: var(--action-primary); box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  /* Solo resalta la card; nunca subraya ni tiñe el texto (anula a:hover global). */
  text-decoration: none; color: var(--text-strong);
}
.af-itinerary-course-card,
.af-itinerary-course-card:hover .af-itinerary-course-name { text-decoration: none; }
.af-itinerary-course-thumb {
  position: relative; flex: 0 0 88px; width: 88px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-sunken); color: var(--text-subtle); overflow: hidden;
}
.af-itinerary-course-thumb img { width: 100%; height: 100%; object-fit: cover; }
.af-itinerary-course-flag {
  position: absolute; left: 4px; top: 4px; margin: 0;
  font-size: 10px; padding: 2px 7px;
}
.af-itinerary-course-body {
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  gap: var(--space-2); padding: var(--space-3); min-width: 0;
}
.af-itinerary-course-name {
  font-weight: var(--weight-semibold); line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Editor de opciones de campo personalizado. */
.af-option-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: var(--space-3); margin-bottom: var(--space-3); }
@media (max-width: 575.98px) { .af-option-row { grid-template-columns: 1fr; } }

/* Grupo de campos personalizados. */
.af-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.af-fieldset > legend {
  float: none; width: auto; padding: 0; margin-bottom: var(--space-4);
  font-size: var(--type-eyebrow-size); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase; color: var(--text-subtle);
}

/* Laboratorio de IA. El violeta marca todo lo generado por IA, nunca el naranja:
   el naranja es siempre una acción del usuario. */
.af-ai-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: var(--space-5); align-items: start; }
.af-ai-chat { display: flex; flex-direction: column; min-height: 420px; }
.af-ai-messages { flex: 1; overflow-y: auto; padding: var(--gutter-card); display: flex; flex-direction: column; gap: var(--space-5); }
.af-ai-empty { margin: auto; text-align: center; color: var(--text-muted); }
.af-ai-mark {
  width: 46px; height: 46px; margin: 0 auto var(--space-4);
  display: grid; place-items: center; border-radius: var(--radius-md);
  background: var(--status-ai-bg); color: var(--status-ai-fg);
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-sm);
}
.af-ai-message { display: flex; flex-direction: column; gap: var(--space-2); }
.af-ai-message-meta { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-2xs); color: var(--text-subtle); }
.af-ai-message-body {
  padding: var(--space-4) var(--space-5); border-radius: var(--radius-card);
  background: var(--surface-sunken); font-size: var(--text-sm); line-height: var(--leading-relaxed);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.af-ai-message.is-ai .af-ai-message-body { background: var(--status-ai-bg); color: var(--status-ai-fg); }
.af-ai-composer {
  display: flex; align-items: flex-end; gap: var(--space-3);
  padding: var(--space-4) var(--gutter-card); border-top: 1px solid var(--border-subtle);
}
.af-ai-composer textarea { resize: none; min-height: var(--control-h-md); max-height: 160px; }
.af-ai-attachment {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 var(--gutter-card) var(--space-3);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
  background: var(--surface-accent-soft); color: var(--text-accent); font-size: var(--text-2xs);
}
.af-ai-image-output img { width: 100%; border-radius: var(--radius-card); display: block; }

@media (max-width: 991.98px) { .af-ai-grid { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------------
   11. Autenticación
   --------------------------------------------------------------------------- */
.af-auth {
  position: relative; min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-8) var(--space-5);
  background: var(--surface-stage); overflow: auto;
}
.af-auth-bg { position: absolute; inset: 0; opacity: .5; pointer-events: none; }
/* Imagen de fondo aportada por el tema (zona loginBackground).
   Va en su propio contenedor (z-index:0) para pintarse bajo la tarjeta SIN
   crear un stacking context en .af-auth-inner: si lo tuviera, atraparía el
   modal de Bootstrap por debajo de su backdrop (inyectado en <body>). */
.af-auth-bgwrap { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.af-auth-bgimg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Velo suave sobre la imagen (no sobre la tarjeta) para dar contraste al fondo. */
.af-auth-bgwrap::after { content: ""; position: absolute; inset: 0;
  background: radial-gradient(60% 60% at 50% 45%,
    color-mix(in srgb, var(--surface-stage) 18%, transparent), transparent 70%); }
.af-auth-inner { position: relative; width: min(408px, 100%); }
.af-auth-card {
  background: var(--surface-card);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-xl);
  padding: var(--space-8);
}
.af-auth-wide { width: min(560px, 100%); }
.af-auth-logo { height: 36px; width: auto; display: block; margin: 0 auto var(--space-7); }
.af-auth-title { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-semibold);
  color: var(--text-strong); letter-spacing: var(--tracking-heading); margin: 0; text-align: center; }
.af-auth-sub { font-size: var(--text-sm); color: var(--text-muted); text-align: center; margin: var(--space-3) 0 var(--space-7); }
.af-auth-host { font-family: var(--font-mono); }
.af-auth-foot {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  margin-top: var(--space-6); color: rgba(255, 255, 255, .5); font-size: var(--text-2xs); text-align: center;
}
.af-auth-foot-text { max-width: 30ch; }
.af-auth-foot-copy { color: rgba(255, 255, 255, .38); }
/*
 * Los enlaces del pie (los documentos legales) están sobre la fotografía, no sobre
 * la tarjeta: con el color de enlace del tema se pierden contra ella. Toman el
 * blanco del propio pie, un punto por encima del texto de al lado para que se lean
 * como pulsables sin gritar.
 */
.af-auth-foot a {
  color: rgba(255, 255, 255, .82); font-weight: var(--weight-medium); text-decoration: none;
}
.af-auth-foot a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.af-auth-sep { display: flex; align-items: center; gap: var(--space-4); color: var(--text-subtle); font-size: var(--text-2xs); }
.af-auth-sep::before, .af-auth-sep::after { content: ""; flex: 1; height: 1px; background: var(--border-subtle); }


/* Botón de un proveedor de inicio de sesión único (Microsoft, Google…).
   Deliberadamente neutro y no `btn-brand`: el color de marca de la plataforma es
   el del botón «Entrar», y dos botones de marca compitiendo en la misma tarjeta
   confunden sobre cuál es la vía normal de acceso. Además, las guías de marca de
   los proveedores piden un botón sobrio con su logotipo, no teñido. */
.af-auth-sso {
  display: flex; align-items: center; justify-content: center; gap: var(--space-3);
  width: 100%; padding: var(--space-3) var(--space-4);
  background: var(--surface-card); color: var(--text-strong);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-control);
  font-size: var(--text-sm); font-weight: var(--weight-medium); text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.af-auth-sso:hover { border-color: var(--border-strong, var(--text-subtle)); box-shadow: var(--shadow-sm); color: var(--text-strong); }
.af-auth-sso:focus-visible { outline: 2px solid var(--action-primary); outline-offset: 2px; }
.af-auth-sso-icon { width: 18px; height: 18px; flex: 0 0 auto; display: block; }
/* Aviso de la puerta trasera del modo «solo SSO». Discreto a propósito: es una
   salida de emergencia para quien administra, no una segunda vía de acceso que
   invite a usarla. */
.af-auth-local-note { font-size: var(--text-2xs); color: var(--text-subtle); text-align: center; margin-top: var(--space-4); }

/* ---------------------------------------------------------------------------
   12. APP móvil
   La pantalla pública que el alumno guarda en la pantalla de inicio de su
   teléfono. Se apoya en `--surface-canvas` y no en `--surface-stage` como el
   login: aquí no hay un telón oscuro que enmarque una tarjeta, sino una página
   que quiere parecer una aplicación del cliente, así que sigue el modo claro u
   oscuro del teléfono.
   --------------------------------------------------------------------------- */
.af-app {
  position: relative; min-height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-7) var(--space-5);
  background: var(--gradient-canvas), var(--surface-canvas); overflow: auto;
}
.af-app--image { background: var(--surface-canvas); }
.af-app-bgwrap { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.af-app-bgimg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.af-app-inner { position: relative; width: min(420px, 100%); }
.af-app-card {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-xl);
  padding: var(--space-7);
  text-align: center;
}
/* Sin `display`: el par claro/oscuro lo gobiernan `.af-logo-light` /
   `.af-logo-dark` (§9), y fijarlo aquí —más abajo en la hoja, misma
   especificidad— pisaría esa regla y saldrían los dos logotipos a la vez.
   Centra el `text-align` de la tarjeta, que ya vale para una imagen en línea. */
.af-app-logo { height: 40px; width: auto; max-width: 100%; margin-bottom: var(--space-6); }
.af-app-title {
  font-family: var(--font-display); font-size: var(--text-2xl);
  font-weight: var(--weight-semibold); letter-spacing: var(--tracking-heading);
  color: var(--text-strong); margin: 0;
}
.af-app-sub { font-size: var(--text-sm); color: var(--text-muted); margin: var(--space-2) 0 var(--space-6); }
.af-app-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  font-size: var(--text-base); padding: var(--space-4) var(--space-5);
}
.af-app-steps {
  margin-top: var(--space-7); padding-top: var(--space-6);
  border-top: 1px solid var(--border-subtle); text-align: left;
}
.af-app-steps-head {
  display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-display); font-size: var(--text-base);
  font-weight: var(--weight-semibold); color: var(--text-strong); margin: 0;
}
.af-app-steps-intro { font-size: var(--text-sm); color: var(--text-muted); margin: var(--space-3) 0 0; }
.af-app-os { margin-top: var(--space-5); }
.af-app-os-name {
  font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-eyebrow);
  font-weight: var(--weight-semibold); color: var(--text-brand); margin: 0 0 var(--space-2);
}
.af-app-os-list { margin: 0; padding-left: var(--space-6); font-size: var(--text-sm); color: var(--text-body); }
.af-app-os-list li + li { margin-top: var(--space-1); }
.af-app-warn {
  display: flex; align-items: flex-start; gap: var(--space-2);
  margin: var(--space-6) 0 0; padding: var(--space-4);
  border-radius: var(--radius-card);
  background: var(--status-warning-bg); color: var(--status-warning-fg);
  font-size: var(--text-xs); text-align: left;
}
.af-app-warn i { flex: 0 0 auto; margin-top: 2px; }

/* Código QR del acceso rápido, en «Mi perfil». Nace tapado a propósito: quien lo
   escanee entra en la cuenta, así que verlo es una decisión, no un accidente. */
.af-qr-reveal {
  position: relative;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
  padding: var(--space-6);
  min-height: 232px;
  display: flex; align-items: center; justify-content: center;
}
.af-qr-cover { color: var(--text-muted); max-width: 30ch; }
.af-qr-holder { width: 200px; max-width: 100%; }
/* El QR se sirve como SVG: se le fija el ancho y se le da fondo blanco propio,
   porque un lector de códigos necesita contraste real y no el del tema. */
.af-qr-holder svg {
  width: 100%; height: auto; display: block;
  background: #fff; border-radius: var(--radius-sm); padding: var(--space-2);
}

@media (max-width: 575.98px) {
  .af-app { padding: var(--space-5) var(--space-4); }
  .af-app-card { padding: var(--space-6) var(--space-5); }
}

/* ---------------------------------------------------------------------------
   13. Utilidades y responsive
   --------------------------------------------------------------------------- */
.af-stack { display: flex; flex-direction: column; gap: var(--space-5); }
.af-stack-sm { display: flex; flex-direction: column; gap: var(--space-3); }
.af-row { display: flex; align-items: center; gap: var(--space-3); }
.af-grid-kpi { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-5); }
.af-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
.af-grid-hero { display: grid; grid-template-columns: 1.9fr 1fr; gap: var(--space-5); align-items: stretch; }

.af-theme-swatch {
  display: inline-flex; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--border-subtle); flex: 0 0 auto;
}
.af-theme-swatch span { width: 16px; height: 28px; display: block; }

.af-theme-option { cursor: pointer; }
.af-theme-option .af-card { transition: var(--transition-control); height: 100%; }
.af-theme-option input { position: absolute; opacity: 0; pointer-events: none; }
.af-theme-option input:checked + .af-card { border-color: var(--border-brand); box-shadow: var(--ring-focus); }
.af-theme-option input:focus-visible + .af-card { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Comunicaciones de acceso (0.140.0): una tarjeta por correo que se puede mandar.
   Misma familia visual que las tarjetas del selector de bloques de los cuadros de
   mando (analytics.css), con pie propio porque aquí cada tarjeta es una acción. */
.af-comm-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); }
.af-comm-card {
  display: flex; flex-direction: column; gap: var(--space-2);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-card, var(--radius-md));
  background: var(--surface-card); padding: var(--space-4);
  transition: var(--transition-control);
}
.af-comm-card:not(.is-disabled):hover {
  border-color: var(--action-primary); transform: translateY(-2px);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--action-primary) 18%, transparent);
}
.af-comm-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-1); }
.af-comm-card-title { font-size: var(--text-base); font-weight: 600; color: var(--text-strong); margin: 0; }
.af-comm-card-desc { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.45; margin: 0; }
.af-comm-card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  margin-top: auto; padding-top: var(--space-3); border-top: 1px solid var(--border-subtle);
}
.af-comm-card-foot a { font-size: var(--text-xs); }
.af-comm-card.is-disabled { background: var(--surface-sunken); }
.af-comm-card.is-disabled .af-icon-box { background: var(--surface-card); color: var(--text-muted); }
.af-comm-card-reason { font-size: var(--text-xs); color: var(--status-warning-fg); margin: 0; }
.af-comm-result {
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-control);
  background: var(--status-success-bg); color: var(--status-success-fg); font-size: var(--text-sm);
}
@media (max-width: 767.98px) { .af-comm-grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { .af-comm-card:not(.is-disabled):hover { transform: none; } }

@media (max-width: 1199.98px) {
  .af-grid-kpi { grid-template-columns: repeat(2, 1fr); }
  .af-grid-hero { grid-template-columns: 1fr; }
  .af-hero-stats { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 991.98px) {
  .af-sidebar, .af-sidebar-toggle { display: none; }
  .af-content { padding: var(--space-6) var(--space-5); }
  .af-topbar { padding: 0 var(--space-5); }
  .af-topbar-search { display: none; }
}

/*
 * La campana en el teléfono.
 *
 * Anclado al icono el panel no cabe —mide 370 y a la izquierda de la campana no hay
 * tanto sitio—, así que Popper lo empuja contra el borde izquierdo de la pantalla y
 * deja un hueco a la derecha que no se parece a nada del resto de la plataforma. En
 * pantalla estrecha deja de colgar del icono y se comporta como una lámina: de
 * margen a margen, justo bajo la barra superior.
 *
 * Los `!important` no son pereza. Popper escribe `position`, `inset` y `transform`
 * en el atributo `style` del elemento, y el ancho también va en línea desde la
 * plantilla: contra eso no hay especificidad que valga.
 */
@media (max-width: 575.98px) {
  .af-notif .dropdown-menu {
    position: fixed !important;
    top: var(--topbar-h) !important;
    left: var(--space-3) !important;
    right: var(--space-3) !important;
    transform: none !important;
    width: auto !important;
  }
}

@media (max-width: 767.98px) {
  .af-grid-kpi, .af-grid-2 { grid-template-columns: 1fr; }
  .af-hero-stats { grid-template-columns: repeat(2, 1fr); }
  .af-page-head h1 { font-size: var(--text-2xl); }
  .af-hero-title { font-size: var(--text-2xl); }
  .af-auth-card { padding: var(--space-6); }
  .af-detail-list { grid-template-columns: 1fr; }
  /* Migas de la barra superior: en un teléfono no caben y el título de la
     página, que se queda, ya da el contexto. Acotado a `.af-topbar` a
     propósito: `.af-crumbs` se reutiliza para las migas de CARPETAS del
     selector de documentos (dentro de offcanvas), y esas sí hacen falta. */
  .af-topbar .af-crumbs { display: none; }
}

/* La sidebar en móvil se sirve como offcanvas de Bootstrap. */
.af-offcanvas-nav .af-sidebar {
  display: flex; width: 100%; height: auto; position: static;
  border-right: 0; padding: 0;
}
.af-offcanvas-nav .af-sidebar-head { display: none; }
/* Mismo criterio que en la barra lateral: el alto es un tope, no una imposicion,
   para que un logo muy apaisado quepa en el ancho del panel sin deformarse. */
.af-offcanvas-logo { display: block; min-width: 0; }
.af-offcanvas-logo img { max-height: 24px; max-width: 100%; height: auto; width: auto; }

@media print {
  .af-sidebar, .af-topbar, .af-sidebar-toggle, .af-table-actions { display: none !important; }
  .af-content { padding: 0; }
  .af-card, .af-panel { box-shadow: none; }
  /* Los tooltips/popovers de Bootstrap se anclan al body y no llevan .af-no-print,
     así que hay que ocultarlos explícitamente o se cuelan en la impresión. */
  .tooltip, .popover, .offcanvas-backdrop, .modal-backdrop { display: none !important; }
}

/* === Notificaciones internas (campana de la barra superior) === */
.af-notif-badge {
  position: absolute; top: -2px; right: -2px; background: var(--action-primary); color: var(--action-primary-fg);
  font-size: 10px; line-height: 1; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center; font-weight: 600;
}
.af-notif-list { max-height: min(60vh, 420px); overflow-y: auto; }
/* La fila es el contenedor; dentro van el contenido (que puede ser un enlace) y
   los botones de acción. Un botón no puede ir dentro del enlace: no sería HTML
   válido y el enlace se llevaría el clic. */
.af-notif-item {
  display: flex; gap: .4rem; align-items: flex-start; padding: .7rem 1rem;
  color: inherit; border-bottom: 1px solid var(--border-subtle);
}
.af-notif-item:last-child { border-bottom: none; }
.af-notif-item:hover { background: var(--action-ghost-hover); }
.af-notif-item-main {
  display: flex; gap: .6rem; align-items: flex-start; text-decoration: none; color: inherit;
  flex: 1 1 auto; min-width: 0;
}
.af-notif-item-icon { flex: 0 0 auto; align-self: center; color: var(--text-muted); }
.af-notif-item-icon i, .af-notif-item-icon svg { width: 18px; height: 18px; }
.af-notif-item-body { display: flex; flex-direction: column; min-width: 0; }
/* El título y la píldora de estado comparten línea y envuelven si no caben. */
.af-notif-item-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.af-notif-item-title { font-weight: 600; font-size: var(--text-sm); }
/* Marca de «sin leer»: una píldora discreta, no un fondo de color. */
.af-notif-unread-pill { flex: 0 0 auto; }
.af-notif-item-text { font-size: var(--text-xs); color: var(--text-muted); overflow-wrap: anywhere; }
.af-notif-item-time { font-size: var(--text-2xs); color: var(--text-muted); margin-top: .15rem; }

/* Acciones de la fila: son `.af-icon-btn.af-icon-btn-sm` del sistema de diseño,
   así que aquí solo se colocan. En escritorio se atenúan hasta pasar por encima;
   en táctil no hay `hover`, así que se dejan siempre visibles. */
.af-notif-item-actions {
  display: flex; gap: var(--space-1); flex: 0 0 auto; align-self: center;
  opacity: .45; transition: opacity .15s ease;
}
.af-notif-item:hover .af-notif-item-actions,
.af-notif-page-item:hover .af-notif-item-actions,
.af-notif-item-actions:focus-within { opacity: 1; }
@media (hover: none) { .af-notif-item-actions { opacity: 1; } }
.af-notif-item-actions .af-icon-btn i, .af-notif-item-actions .af-icon-btn svg { width: 16px; height: 16px; }

/* Filtros del historial. */
.af-notif-filters { display: flex; gap: .35rem; flex-wrap: wrap; }
.af-notif-filter {
  font-size: var(--text-xs); text-decoration: none; color: var(--text-muted);
  padding: .3rem .7rem; border-radius: 999px; border: 1px solid var(--border-subtle);
}
.af-notif-filter:hover { color: var(--text-strong, inherit); }
.af-notif-filter.is-active {
  color: var(--action-primary); border-color: var(--action-primary);
  background: color-mix(in srgb, var(--action-primary) 8%, transparent);
}

.af-notif-page-list { list-style: none; margin: 0; padding: 0; }
/* Icono, contenido y acciones centrados entre sí: la fila se lee como una línea,
   no como tres elementos colgando de arriba. Lo que distingue una notificación
   sin leer es su píldora, no un fondo de color. */
.af-notif-page-item {
  display: flex; gap: var(--space-4); align-items: center;
  padding: .85rem 0; border-bottom: 1px solid var(--border-subtle);
}
.af-notif-page-item:last-child { border-bottom: none; }
.af-notif-page-icon { flex: 0 0 auto; color: var(--text-muted); line-height: 0; }
.af-notif-page-icon i, .af-notif-page-icon svg { width: 22px; height: 22px; }
.af-notif-page-item.is-unread > i, .af-notif-page-item.is-unread > svg { color: var(--action-primary) !important; }

/* ---------------------------------------------------------------------------
   Burbuja flotante (FAB)
   Especificada en 2_UI_DOCS/UI.md. z-index 60: por encima del shell (topbar 40,
   plegado de la barra lateral 45) y por debajo del off-canvas y los modales de
   Bootstrap, que son quienes deben taparla cuando se abren.
   --------------------------------------------------------------------------- */
.af-fab {
  position: fixed; right: var(--space-7); bottom: var(--space-7); z-index: 60;
  width: 52px; height: 52px; border: 0; border-radius: var(--radius-pill);
  background: var(--gradient-brand); color: #fff; box-shadow: var(--shadow-brand);
  display: grid; place-items: center; cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease;
}
.af-fab:hover { transform: translateY(-2px); }
.af-fab:focus-visible { outline: 2px solid var(--action-primary); outline-offset: 3px; }

/* ---------------------------------------------------------------------------
   Historial de versiones de un complemento
   Listado secuencial, de lo más reciente a lo más antiguo. Se lee en vertical:
   nada de rejilla de tarjetas, que obliga a saltar la vista para seguir el hilo.
   --------------------------------------------------------------------------- */
.af-plugin-versions { list-style: none; margin: 0; padding: 0; }
.af-plugin-version { padding: var(--space-4) 0; border-top: 1px solid var(--border-subtle); }
.af-plugin-version:first-child { border-top: 0; padding-top: 0; }
.af-plugin-version:last-child { padding-bottom: 0; }
.af-plugin-version-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); margin-bottom: var(--space-2);
}
.af-plugin-version-number {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-sm); color: var(--text-strong);
}
.af-plugin-version-date { font-size: var(--text-2xs); color: var(--text-muted); flex: 0 0 auto; }
.af-plugin-version-notes {
  margin: 0; font-size: var(--text-xs); color: var(--text-muted);
  line-height: var(--leading-relaxed);
}

/* ---------------------------------------------------------------------------
   Organigrama — recursos del grupo y seguimiento

   Los cursos y los itinerarios asociados se identifican por su carátula y su
   nombre, no por una rejilla de columnas: con dos o tres elementos, una tabla
   gastaba más espacio del que informaba. De ahí la fila tipo tarjeta.

   El árbol del modal de seguimiento usa el mismo lenguaje que el reproductor:
   verde superado, ámbar en proceso, gris sin empezar.
   --------------------------------------------------------------------------- */
.af-orgres-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card); background: var(--surface-card);
  transition: box-shadow .15s ease, border-color .15s ease;
}
.af-orgres-row:hover { box-shadow: var(--shadow-sm); border-color: var(--border-strong, var(--border-subtle)); }
.af-orgres-thumb {
  flex: 0 0 auto; width: 64px; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius-md); background: var(--surface-sunken);
}
.af-orgres-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Variante para tablas y listas de seleccion, donde 64px roba demasiada fila. */
.af-orgres-thumb-sm { width: 48px; }
.af-orgres-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.af-orgres-title {
  font-weight: var(--weight-semibold); font-size: var(--text-sm); color: var(--text-strong);
  text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.af-orgres-title:hover { color: var(--action-primary); text-decoration: underline; }
.af-orgres-sub {
  font-size: var(--text-2xs); color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.af-orgres-pill { flex: 0 0 auto; }

/* Árbol de contenidos del seguimiento */
.af-track-tree { display: flex; flex-direction: column; gap: 1px; }
.af-track-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: var(--surface-card);
}
.af-track-row + .af-track-row { border-top: 1px solid var(--border-subtle); border-radius: 0; }
.af-track-row-module { background: var(--surface-sunken); font-weight: var(--weight-semibold); }
.af-track-mark { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; }
.af-track-mark-completed { color: var(--status-success-fg); }
.af-track-mark-started { color: var(--status-warning-fg); }
.af-track-mark-pending { color: var(--text-muted); opacity: .55; }
.af-track-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--space-2); }
.af-track-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-sm); }
.af-track-type { font-size: var(--text-2xs); color: var(--text-muted); flex: 0 0 auto; }
.af-track-meta {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-4);
  font-size: var(--text-2xs); color: var(--text-muted); text-align: right;
}
.af-track-meta span { min-width: 5.5rem; display: inline-block; }
@media (max-width: 640px) {
  .af-track-meta { display: none; }
}

/* Avance dentro de un paquete SCORM, en la ficha de seguimiento, y su panel de
   detalle desplegable. El panel va dentro de la propia ficha —que ya es un modal—
   porque un modal dentro de otro se pelea por el foco y por el scroll. */
.af-track-scorm-pct {
  flex: 0 0 auto;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--action-primary);
  font-variant-numeric: tabular-nums;
}
.af-track-info {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-subtle);
  cursor: pointer;
}
.af-track-info:hover,
.af-track-info[aria-expanded="true"] { background: var(--surface-raised); color: var(--action-primary); }

.af-scorm-state {
  margin: var(--space-2) var(--space-3) var(--space-4) 0;
  padding: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
}
.af-scorm-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}
.af-scorm-facts dt {
  font-size: var(--text-2xs);
  font-weight: 400;
  color: var(--text-muted);
  margin: 0;
}
.af-scorm-facts dd {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-strong);
  margin: 0;
}
.af-scorm-block + .af-scorm-block { margin-top: var(--space-4); }
.af-scorm-block-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 0 0 var(--space-2);
}
/* El marcador de reanudación viene en una sola línea larguísima y, muchas veces,
   comprimido: sin el corte forzado se sale del panel. */
.af-scorm-pre {
  margin: 0;
  padding: var(--space-3);
  max-height: 11rem;
  overflow: auto;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: var(--text-2xs);
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.af-scorm-cmi { max-height: 16rem; overflow: auto; }
.af-scorm-cmi th { font-weight: 500; white-space: nowrap; }
.af-scorm-cmi th,
.af-scorm-cmi td { font-size: var(--text-2xs); overflow-wrap: anywhere; }

/* Cursos afectados por borrar un documento. Lista corta y legible dentro del aviso:
   si son muchos, se hace scroll en vez de convertir el diálogo en una página. */
.af-doc-delete-courses {
  margin: 0;
  padding-left: 1.1rem;
  max-height: 9rem;
  overflow-y: auto;
  font-size: var(--text-xs);
}

/* Campana de distribución del avance (interactiva) */
.af-bell-bucket { cursor: pointer; }
.af-bell-bucket rect.af-bell-hit { fill: transparent; }
.af-bell-bucket:hover rect.af-bell-hit,
.af-bell-bucket.is-active rect.af-bell-hit { fill: var(--action-primary); fill-opacity: .10; }
.af-bell-bucket.is-active text { fill: var(--action-primary); font-weight: var(--weight-semibold); }
tr.af-row-dimmed { opacity: .32; }
tr.af-row-highlight { background: color-mix(in srgb, var(--action-primary) 9%, transparent); }

/* ---------------------------------------------------------------------------
   Traza del error de verificación de correo

   El icono va junto al botón de verificar de su canal, no en un aviso aparte:
   en esa pantalla se configuran tres canales a la vez y hay que ver de un vistazo
   cuál es el que ha fallado.
   --------------------------------------------------------------------------- */
.af-mail-trace-btn {
  color: var(--status-danger-fg);
  border-color: color-mix(in srgb, var(--status-danger-fg) 40%, transparent);
  background: var(--status-danger-bg);
}
.af-mail-trace-btn:hover {
  color: var(--status-danger-fg);
  border-color: var(--status-danger-fg);
}

/* === Centro de ayuda (0.86.0) ===
   El buscador reutiliza tal cual `.af-config-search*`: es el mismo gesto en la
   misma plataforma y no hay ninguna razón para que se vea distinto. Lo de aquí
   es lo que aquel no necesitaba — el catálogo por áreas y el visor. */
.af-help-search { max-width: 720px; }
.af-help-note {
  padding: 10px 12px; font-size: var(--text-sm); color: var(--text-muted);
}
.af-help-list { list-style: none; margin: 0; padding: 0; }
.af-help-list > li + li { margin-top: 2px; }
.af-help-link {
  display: block; width: 100%; text-align: left;
  padding: 7px 10px; margin-left: -10px;
  border: 0; background: transparent; border-radius: var(--radius-md);
  cursor: pointer; color: inherit; font-size: var(--text-sm);
}
.af-help-link:hover,
.af-help-link:focus-visible { background: var(--surface-brand-soft); }
.af-help-link-title { font-weight: 500; }
.af-help-pill {
  display: inline-block; margin-left: 6px; padding: 1px 7px;
  border-radius: 999px; background: var(--surface-accent-soft);
  color: var(--text-muted); font-size: var(--text-xs); white-space: nowrap;
}
.af-help-viewer { margin-top: var(--space-4, 1rem); }
/* El visor va a dos columnas: el contenido en la ancha y la identidad del
   documento más su indice de apartados en la estrecha, que se queda pegada al
   desplazarse para poder saltar de apartado sin volver arriba. El desplazamiento
   propio y el tope de altura son lo que impide que un documento con muchos
   apartados deje la lista cortada por abajo y sin manera de llegar al final. */
.af-help-aside {
  position: sticky;
  top: calc(var(--topbar-h) + var(--space-4, 1rem));
  max-height: calc(100vh - var(--topbar-h) - var(--space-6, 2rem));
  overflow-y: auto;
  padding-right: 4px;
}
.af-help-title {
  font-size: var(--text-lg); font-weight: 600; line-height: 1.3;
  margin: 0 0 var(--space-2, .5rem);
}
.af-help-subtitle {
  font-size: var(--text-sm); color: var(--text-muted);
  margin: 0 0 var(--space-3, .75rem);
}
/* En pantalla estrecha las columnas se apilan y nada de esto tiene sentido:
   pegar la columna dejaria el indice ocupando la pantalla entera. */
@media (max-width: 991.98px) {
  .af-help-aside { position: static; max-height: none; overflow: visible; }
}
.af-help-notice {
  display: flex; flex-wrap: wrap; align-items: center;
  padding: 10px 14px; margin-bottom: var(--space-4, 1rem);
  border: 1px solid var(--border-default); border-left-width: 3px;
  border-radius: var(--radius-md); background: var(--surface-accent-soft);
  font-size: var(--text-sm);
}
.af-help-toc {
  padding-top: var(--space-3, .75rem);
  border-top: 1px solid var(--border-subtle);
}
.af-help-toc ul { list-style: none; margin: 0; padding: 0; }
.af-help-toc li + li { margin-top: 4px; }
.af-help-toc a { font-size: var(--text-sm); }
.af-help-section + .af-help-section { margin-top: var(--space-5, 1.5rem); }
/* El botón de copiar enlace aparece al pasar por encima del apartado, pero
   siempre que reciba el foco: si solo respondiera al ratón, con el teclado sería
   inalcanzable. */
.af-help-copy { opacity: 0; margin-left: 8px; vertical-align: middle; }
.af-help-doc h3:hover .af-help-copy,
.af-help-copy:focus-visible { opacity: 1; }
.af-help-copy.is-done { opacity: 1; color: var(--status-success-fg, inherit); }
.af-help-links {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: var(--space-5, 1.5rem);
}
.af-help-related {
  margin-top: var(--space-5, 1.5rem); padding-top: var(--space-4, 1rem);
  border-top: 1px solid var(--border-subtle);
}
/* Tablas y contenido ancho dentro de un documento: que se desplace la caja y no
   la página entera. El corpus no debería traer tablas —el escáner avisa— pero si
   alguna se cuela, mejor que no rompa la maquetación. */
.af-help-doc table { display: block; overflow-x: auto; max-width: 100%; }
.af-help-doc pre { overflow-x: auto; }

/* --------------------------------------------- Estrellas de valoración (0.145.0)
   Componente común: lo usan el panel de valoración, la ficha del curso y la del
   catálogo. Solo aporta la disposición; el relleno de cada estrella va en línea
   porque el icono de Lucide es de trazo y hay que forzarle `fill` estrella a
   estrella. El ámbar sale de `--chart-4`, el mismo que `.af-survey-star.is-on`. */
.af-rating-stars { display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
.af-rating-stars i { flex: 0 0 auto; }
.af-rating-value { margin-left: var(--space-2); font-weight: 600; color: var(--text-strong); }
/* Preguntas de un modelo de valoración (RatingForms/view): agarre a la izquierda,
   insignias tras el enunciado y acciones de icono alineadas a la derecha. */
.af-rating-qlist { display: flex; flex-direction: column; gap: var(--space-2); }
.af-rating-qrow { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--surface-sunken); }
.af-rating-qrow.is-dragging { opacity: .5; }
.af-rating-qgrip { display: inline-flex; color: var(--text-subtle); cursor: grab; border-radius: var(--radius-sm); }
.af-rating-qgrip:focus-visible { outline: 2px solid var(--action-primary); outline-offset: 2px; }
.af-rating-qname { font-weight: 600; color: var(--text-strong); min-width: 0; overflow-wrap: anywhere; }
.af-rating-qactions { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-2); }
