/* ============================================================
   SCHOOLARYX — Components CSS
   Todos los componentes UI del tema
   ============================================================ */

/* ======================================================
   CARDS
   ====================================================== */
.card {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  box-shadow: none;
  transition: var(--transition);
  overflow: hidden;
}

.card:hover { box-shadow: none; }

/* Deja que los menús dropdown de acciones (rich-table por fila) escapen del
   recorte de .card (overflow:hidden). El card-header es transparente, así que
   no afecta a las esquinas redondeadas. */
.card.sx-card-overflow { overflow: visible; }

.card-header {
  background: transparent;
  border-bottom: 1px solid var(--sx-border-color);
  padding: 1rem 1.25rem;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--sx-text-heading);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* El padding se expone como variable para que quien necesite salirse de él
   (.sx-table-flush, en pages.css) lo cancele con el valor exacto en vez de
   repetir un 1.25rem que podría divergir de este. */
.card-body { --sx-card-pad: 1.25rem; padding: var(--sx-card-pad); }
.card-footer {
  background: transparent;
  border-top: 1px solid var(--sx-border-color);
  padding: 0.875rem 1.25rem;
  font-size: 0.8rem;
  color: var(--sx-text-muted);
}

/* Card color headers */
.card-header--primary { background: var(--sx-primary-solid); color: #fff; }
.card-header--success { background: var(--sx-success-solid); color: #fff; }
.card-header--danger  { background: var(--sx-danger-solid);  color: #fff; }
.card-header--warning { background: var(--sx-warning); color: var(--sx-dark); }
.card-header--info    { background: var(--sx-info);    color: var(--sx-dark); }
.card-header--dark    { background: var(--sx-dark);    color: #fff; }
.card-header--purple  { background: var(--sx-purple-solid);  color: #fff; }

/* Stat card */
.sx-stat-card {
  position: relative;
  overflow: hidden;
}
a.sx-stat-card { transition: border-color .2s ease; }
a.sx-stat-card:hover { border-color: var(--sx-primary-text); }
a.sx-stat-card:focus-visible { outline: 2px solid var(--sx-primary); outline-offset: 2px; }
.sx-stat-card__icon {
  width: 52px;
  height: 52px;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  flex-shrink: 0;
}
.sx-stat-card__value {
  font-family: var(--font-display);
  font-size: 2rem;
  line-height: 1;
  color: var(--sx-text-heading);
}
.sx-stat-card__label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--sx-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.sx-stat-card__trend {
  font-size: 0.78rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-pill);
}
.sx-stat-card__trend--up   { background: var(--sx-success-light); color: var(--sx-success-text); }
.sx-stat-card__trend--down { background: var(--sx-danger-light);  color: var(--sx-danger-text); }

/* Card colapsable */
.card-collapsible .card-header { cursor: pointer; user-select: none; }
.card-collapsible .card-header .collapse-icon { margin-left: auto; transition: transform 0.25s; }
.card-collapsible .card-header[aria-expanded="false"] .collapse-icon { transform: rotate(-90deg); }

/* Card overlay con gradiente */
.card-overlay { position: relative; overflow: hidden; }
.card-overlay .card-img-overlay {
  background: linear-gradient(to top, rgba(48,47,59,0.85) 0%, transparent 60%);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* ======================================================
   BUTTONS
   ====================================================== */
.btn:focus,
.btn:focus-visible,
.btn-check:focus + .btn,
.btn.focus {
  outline: none !important;
  box-shadow: none !important;
}

.btn {
  font-family: var(--font-body);
  font-weight: 700;
  border-radius: var(--radius);
  padding: 0.5rem 1.25rem;
  font-size: 0.875rem;
  transition: var(--transition-fast);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  border: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}

/* Tamaños */
.btn-sm {
  padding: 0.3rem 0.8rem;
  font-size: 0.78rem;
  border-radius: var(--radius-sm);
  border-width: 1.5px;
}
.btn-lg {
  padding: 0.7rem 1.75rem;
  font-size: 1rem;
  border-radius: var(--radius-lg);
}
.btn-xl {
  padding: 0.9rem 2.2rem;
  font-size: 1.1rem;
  border-radius: var(--radius-lg);
}
/* Botón redondo solo-ícono */
.btn-icon {
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: var(--radius);
  font-size: 1rem;
}
.btn-icon.btn-sm { width: 30px; height: 30px; font-size: 0.85rem; border-radius: var(--radius-sm); }
.btn-icon.btn-lg { width: 48px; height: 48px; font-size: 1.2rem; border-radius: var(--radius-lg); }
.btn-icon-circle { border-radius: 50% !important; }

/* Chevron de "abrir detalle": un control SIN caja, para filas colapsables.
   Sigue siendo <button> (teclado, foco, aria-expanded); solo se le quita el
   marco, que junto a un botón de verdad hacía ruido y parecía otra acción.

   No usa .btn-link: el de Bootstrap pinta --bs-link-color (#0d6efd), el azul
   genérico que el tema NO remapea. */
.sx-det-toggle {
  border: 0;
  background: none;
  padding: 0 .35rem;
  line-height: 1;
  color: var(--sx-text-muted);
  transition: var(--transition);
}
.sx-det-toggle:hover,
.sx-det-toggle[aria-expanded="true"] { color: var(--sx-primary-text); }
.sx-det-toggle:focus-visible {
  outline: 2px solid var(--sx-primary);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Colores sólidos */
.btn-primary    { background: var(--sx-primary-solid);  border-color: var(--sx-primary-solid);  color: #fff; }
.btn-primary:hover  { background: var(--sx-primary-solid-dark); border-color: var(--sx-primary-solid-dark); color: #fff; }
.btn-primary:active,
.btn-primary:focus,
.btn-primary.active,
.btn-primary:not(:disabled):not(.disabled):active,
.show > .btn-primary.dropdown-toggle,
.btn-primary.show,
.btn-primary:focus-visible {
  background: var(--sx-primary-solid-dark) !important;
  border-color: var(--sx-primary-solid-dark) !important;
  color: #fff !important;
  box-shadow: none !important;
}
.btn-primary:disabled,
.btn-primary.disabled {
  background: var(--sx-primary-solid) !important;
  border-color: var(--sx-primary-solid) !important;
  color: #fff !important;
  opacity: .55 !important;
  box-shadow: none !important;
}

.btn-success    { background: var(--sx-success); border-color: var(--sx-success); color: #fff; }
.btn-success:hover  { background: var(--sx-success-dark); border-color: var(--sx-success-dark); color: #fff; }

.btn-danger     { background: var(--sx-danger);  border-color: var(--sx-danger);  color: #fff; }
.btn-danger:hover   { background: var(--sx-danger-dark); border-color: var(--sx-danger-dark); color: #fff; }

.btn-warning    { background: var(--sx-warning); border-color: var(--sx-warning); color: var(--sx-dark); }
.btn-warning:hover  { background: var(--sx-warning-dark); border-color: var(--sx-warning-dark); color: var(--sx-dark); }

.btn-info       { background: var(--sx-info);    border-color: var(--sx-info);    color: var(--sx-dark); }
.btn-info:hover     { background: var(--sx-info-dark); border-color: var(--sx-info-dark); color: var(--sx-dark); }

.btn-purple     { background: var(--sx-purple);  border-color: var(--sx-purple);  color: #fff; }
.btn-purple:hover   { background: var(--sx-purple-dark); border-color: var(--sx-purple-dark); color: #fff; }

.btn-dark       { background: var(--sx-dark);    border-color: var(--sx-dark);    color: #fff; }
.btn-dark:hover     { background: var(--sx-dark-2); border-color: var(--sx-dark-2); color: #fff; }

.btn-secondary  { background: var(--sx-neutral); border-color: var(--sx-neutral); color: var(--sx-text-body); }
.btn-secondary:hover { background: var(--sx-neutral-dark); border-color: var(--sx-neutral-dark); color: var(--sx-text-heading); }

/* Outline buttons */
.btn-outline-primary  { border-color: var(--sx-primary-text);  color: var(--sx-primary-text);  background: transparent; }
.btn-outline-primary:hover,
.btn-outline-primary:active,
.btn-outline-primary:focus,
.btn-outline-primary.active,
.btn-outline-primary:not(:disabled):not(.disabled):active,
.show > .btn-outline-primary.dropdown-toggle {
  background: var(--sx-primary-solid) !important;
  border-color: var(--sx-primary-solid) !important;
  color: #fff !important;
  box-shadow: none !important;
}

/* btn-check seleccionado (segmentos): Bootstrap usa --bs-* genérico → forzar marca */
.btn-check:checked + .btn-outline-primary,
.btn-check:active   + .btn-outline-primary {
  background: var(--sx-primary-solid) !important;
  border-color: var(--sx-primary-solid) !important;
  color: #fff !important;
  box-shadow: none !important;
}

.btn-outline-secondary { border-color: var(--sx-border-color); color: var(--sx-text-body); background: transparent; }
.btn-outline-secondary:hover,
.btn-outline-secondary:active,
.btn-outline-secondary:focus,
.btn-outline-secondary.active,
.btn-outline-secondary:not(:disabled):not(.disabled):active,
.show > .btn-outline-secondary.dropdown-toggle {
  background: var(--sx-dark-2) !important;
  border-color: var(--sx-dark-2) !important;
  color: #fff !important;
  box-shadow: none !important;
}

.btn-outline-success  { border-color: var(--sx-success);  color: var(--sx-success);  background: transparent; }
.btn-outline-success:hover  { background: var(--sx-success);  color: #fff; }

.btn-outline-danger   { border-color: var(--sx-danger);   color: var(--sx-danger);   background: transparent; }
.btn-outline-danger:hover   { background: var(--sx-danger);   color: #fff; }

.btn-outline-warning  { border-color: var(--sx-warning);  color: var(--sx-warning); background: transparent; }
.btn-outline-warning:hover  { background: var(--sx-warning);  color: var(--sx-dark); }

.btn-outline-info     { border-color: var(--sx-info);     color: var(--sx-info);    background: transparent; }
.btn-outline-info:hover     { background: var(--sx-info);     color: var(--sx-dark); }

.btn-outline-purple   { border-color: var(--sx-purple);   color: var(--sx-purple);   background: transparent; }
.btn-outline-purple:hover   { background: var(--sx-purple);   color: #fff; }

.btn-outline-dark     { border-color: var(--sx-dark);     color: var(--sx-dark);     background: transparent; }
.btn-outline-dark:hover     { background: var(--sx-dark);     color: #fff; }

/* Soft buttons — tinte de fondo + TINTA de la familia. El texto va siempre en
   la variante -text (el tono base no llega a AA sobre su propio tinte) y el
   hover pasa al -solid con blanco encima, que sí lo aguanta. */
.btn-soft-primary { background: var(--sx-primary-light); color: var(--sx-primary-text); border-color: transparent; }
.btn-soft-primary:hover { background: var(--sx-primary-solid); color: #fff; }

.btn-soft-success { background: var(--sx-success-light); color: var(--sx-success-text); border-color: transparent; }
.btn-soft-success:hover { background: var(--sx-success-solid); color: #fff; }

.btn-soft-danger  { background: var(--sx-danger-light);  color: var(--sx-danger-text);  border-color: transparent; }
.btn-soft-danger:hover  { background: var(--sx-danger-solid);  color: #fff; }

.btn-soft-warning { background: var(--sx-warning-light); color: var(--sx-warning-text); border-color: transparent; }
.btn-soft-warning:hover { background: var(--sx-warning-solid); color: #fff; }

.btn-soft-info    { background: var(--sx-info-light);    color: var(--sx-info-text);    border-color: transparent; }
.btn-soft-info:hover    { background: var(--sx-info-solid);    color: #fff; }

.btn-soft-purple  { background: var(--sx-purple-light);  color: var(--sx-purple-text);  border-color: transparent; }
.btn-soft-purple:hover  { background: var(--sx-purple-solid);  color: #fff; }

/* No shadow on focus/active */
.btn:focus, .btn:active, .btn:focus-visible { box-shadow: none !important; outline: none !important; }

/* Loading state */
.btn.loading { pointer-events: none; opacity: 0.75; }

/* ======================================================
   BADGES
   ====================================================== */
.badge {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.68rem;
  padding: 0.3em 0.65em;
  border-radius: var(--radius-pill);
  letter-spacing: 0.02em;
}

.badge-primary { background: var(--sx-primary-solid);  color: #fff; }
.badge-success { background: var(--sx-success-solid);  color: #fff; }
.badge-danger  { background: var(--sx-danger-solid);   color: #fff; }
.badge-warning { background: var(--sx-warning);  color: var(--sx-dark); }
.badge-info    { background: var(--sx-info);     color: var(--sx-dark); }
.badge-purple  { background: var(--sx-purple-solid);   color: #fff; }
.badge-dark    { background: var(--sx-dark);     color: #fff; }
.badge-secondary { background: var(--sx-neutral); color: var(--sx-text-body); }

/* Chips de estándares por sección: un poco más grandes y legibles. */
#dtClassStandards .badge { font-size: 0.8rem; padding: 0.35em 0.7em; }

/* Soft badges */
.badge-soft-primary { background: var(--sx-primary-light); color: var(--sx-primary-text); }
.badge-soft-success { background: var(--sx-success-light); color: var(--sx-success-text); }
.badge-soft-danger  { background: var(--sx-danger-light);  color: var(--sx-danger-text); }
.badge-soft-warning { background: var(--sx-warning-light); color: var(--sx-warning-text); }
.badge-soft-info    { background: var(--sx-info-light);    color: var(--sx-info-text); }
.badge-soft-purple  { background: var(--sx-purple-light);  color: var(--sx-purple-text); }
/* El fondo va por token: en hex fijo (#eceef3) quedaba claro también en tema
   oscuro, y como el texto sí seguía a --sx-text-body salía claro sobre claro. */
.badge-soft-secondary { background: var(--sx-neutral); color: var(--sx-text-body); }

/* Solid badges (paleta schoolaryx). Texto oscuro en los claros (info/warning). */
.sx-badge-primary   { background: var(--sx-primary-solid);  color: #fff; }
.sx-badge-success   { background: var(--sx-success);  color: #fff; }
.sx-badge-danger    { background: var(--sx-danger);   color: #fff; }
.sx-badge-info      { background: var(--sx-info);     color: var(--sx-dark); }
.sx-badge-warning   { background: var(--sx-warning);  color: var(--sx-dark); }
.sx-badge-purple    { background: var(--sx-purple);   color: #fff; }
.sx-badge-secondary { background: var(--sx-text-muted); color: #fff; }

/* ======================================================
   ALERTS
   ====================================================== */
.alert {
  border: none;
  border-left: 4px solid transparent;
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
}
/* Variante "contenido adicional" (título + texto + hr + texto): bloque vertical
   en vez de flex, con borde fino. Para alerts tipo Bootstrap additional content. */
.alert.alert-block {
  display: block;
  border-left-width: 1px;
}
.alert.alert-block > :last-child { margin-bottom: 0; }
.alert-icon { font-size: 1.25rem; flex-shrink: 0; margin-top: 0.05rem; }
.alert-title { font-weight: 800; font-size: 0.9rem; margin-bottom: 0.25rem; }
.alert-text { font-size: 0.875rem; padding-top: 6px; }
.alert-dismiss {
  margin-left: auto;
  background: none;
  border: none;
  opacity: 0.6;
  cursor: pointer;
  padding: 0;
  font-size: 1rem;
  line-height: 1;
  transition: var(--transition-fast);
}
.alert-dismiss:hover { opacity: 1; }

.alert-primary { background: var(--sx-primary-light); color: var(--sx-primary-text); border-left-color: var(--sx-primary-text); }
.alert-success { background: var(--sx-success-light); color: var(--sx-success-text); border-left-color: var(--sx-success); }
.alert-danger  { background: var(--sx-danger-light);  color: var(--sx-danger-text);  border-left-color: var(--sx-danger); }
.alert-warning { background: var(--sx-warning-light); color: var(--sx-warning-text); border-left-color: var(--sx-warning); }
.alert-info    { background: var(--sx-info-light);    color: var(--sx-info-text);    border-left-color: var(--sx-info); }
.alert-purple  { background: var(--sx-purple-light);  color: var(--sx-purple-text);  border-left-color: var(--sx-purple); }

/* ------------------------------------------------------
   AVISO EN LÍNEA (.sx-note) — SUSTITUYE A .alert EN TODA LA APP: banners de
   página, avisos de modal, tabla vacía y confirmaciones en línea. Un bloque de
   color compite con el contenido; aquí el texto va en el gris normal y el COLOR
   lo lleva SOLO el icono, según el tipo. Un aviso meramente informativo NO lleva
   color: su icono va en el mismo gris del texto (por eso no hay .sx-note--info).
   Las reglas .alert-* de abajo quedan solo para la plantilla HTML comprada
   (resources/views/templates/), que no es código de la app.
   ------------------------------------------------------ */
.sx-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--sx-text-muted);
}
.sx-note > i:first-child { flex: 0 0 auto; font-size: 1rem; line-height: 1.35; }
/* Avisos con título + cuerpo (tabla vacía, borrado en masa): reusan los hooks
   .alert-title/.alert-text que ya poblaba el JS, sin el relleno del .alert. */
.sx-note .alert-title { font-size: 0.85rem; font-weight: 700; color: var(--sx-text-heading); margin-bottom: 0.15rem; }
.sx-note .alert-text  { font-size: 0.85rem; padding-top: 0; }
.sx-note--warning > i:first-child { color: var(--sx-warning-text); }
.sx-note--danger  > i:first-child { color: var(--sx-danger-text); }
.sx-note--success > i:first-child { color: var(--sx-success-text); }

/* ======================================================
   FORMS
   ====================================================== */
.form-label {
  font-weight: 700;
  font-size: 0.8rem;
  color: var(--sx-text-heading);
  margin-bottom: 0.35rem;
  display: block;
}
.form-label .required { color: var(--sx-danger-text); margin-left: 3px; }

.form-control, .form-select {
  background: var(--sx-card-bg);
  border: 1.5px solid var(--sx-border-color);
  border-radius: var(--radius);
  padding: 0.5rem 0.85rem;
  line-height: 1.5;
  height: calc(1.5em + 1rem + 3px);
  font-family: var(--font-body);
  font-size: 0.875rem;
  color: var(--sx-text-body);
  transition: var(--transition-fast);
  box-shadow: none !important;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
}
.form-control[type="password"] { letter-spacing: 1px; }
.form-control[type="password"]::placeholder { line-height: normal; }
textarea.form-control { height: auto; }
select.form-select { height: calc(1.5em + 1rem + 3px); }

.form-control:focus, .form-select:focus {
  border-color: var(--sx-primary-text) !important;
  box-shadow: none !important;
  outline: none !important;
  background: var(--sx-card-bg);
  color: var(--sx-text-body);
}

.form-control::placeholder { color: var(--sx-text-muted); }

.form-control:disabled, .form-control[readonly],
.form-select:disabled {
  background: var(--sx-body-bg);
  color: var(--sx-text-muted);
  cursor: not-allowed;
  opacity: 0.7;
}

.form-control.is-valid   { border-color: var(--sx-success-text); }
.form-control.is-invalid,
.form-select.is-invalid,
textarea.is-invalid { border-color: var(--sx-danger-text); }
.form-control.is-valid:focus   { box-shadow: none !important; border-color: var(--sx-success-text) !important; }
.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
textarea.is-invalid:focus { box-shadow: none !important; border-color: var(--sx-danger-text) !important; }

/* Quitar el ícono SVG por defecto de Bootstrap en estado inválido: el tema usa
   solo borde rojo + mensaje (como la página de referencia). Ese ícono además
   choca con los campos que ya tienen ícono propio (.form-control-icon). */
.form-control.is-invalid,
.form-select.is-invalid,
textarea.is-invalid {
  background-image: none !important;
  padding-right: 0.875rem !important;
}
.form-control-icon.is-invalid,
textarea.form-control-icon.is-invalid { padding-left: 2.5rem !important; }

/* Checkbox / radio inválidos: borde danger en el control y label en danger.
   (Switches y checks requeridos: aceptar términos, elegir una opción, etc.) */
.form-check-input.is-invalid {
  border-color: var(--sx-danger) !important;
}
.form-check-input.is-invalid:focus {
  border-color: var(--sx-danger) !important;
  box-shadow: none !important;
}
.form-check-input.is-invalid ~ .form-check-label { color: var(--sx-danger-dark); }

/* Etiqueta de un campo requerido inválido: color de peligro (lo aplica el
   validador, global y por-paso, vía la clase sx-invalid-label en el <label>). */
.form-label.sx-invalid-label,
label.sx-invalid-label { color: var(--sx-danger-dark) !important; }

/* Select2: el <select> original queda oculto, asi que pintamos su contenedor.
   Select2 inserta .select2-container como hermano inmediato del <select>.
   El !important + los estados focus/open ganan al borde primario del tema. */
select.is-invalid + .select2-container .select2-selection,
select.is-invalid + .select2-container--focus .select2-selection,
select.is-invalid + .select2-container--open .select2-selection {
  border-color: var(--sx-danger) !important;
}

/* Centra verticalmente la "x" de limpiar (allowClear) en select2 múltiples.
   El contenedor necesita position:relative para anclar la "x" absoluta. */
.select2-container--default .select2-selection--multiple { position: relative;
  align-items: center !important;
  background: var(--sx-card-bg) !important;
  border: 1px solid var(--sx-border-color) !important;
  border-radius: var(--radius) !important;
  color: var(--sx-text-body) !important;
  display: flex !important;
  font-family: var(--font-body) !important;
  font-size: .9rem !important;
  min-height: 42px !important;
  padding: .375rem .75rem !important;
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
  padding-right: 1.6rem; /* deja espacio para la "x" y evita solape con los chips */;
  align-content: center;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 !important;
  padding: 0 !important;
}
/* !important obligatorio, igual que en la "x" del select2 simple (pages.css):
   select2.min.css se carga DESPUÉS de este archivo (va en los $pageStyleUrls de
   cada página), así que su regla —misma especificidad— ganaba por orden y la
   "x" volvía a salir pegada arriba. */
.select2-container--default .select2-selection--multiple .select2-selection__clear {
  position: absolute !important;
  top: 50% !important;
  right: 0.6rem !important;
  transform: translateY(-50%) !important;
  margin: 0 !important;
  padding: 0 !important;
  line-height: 1 !important;
  height: auto !important;
  color: var(--sx-text-muted);
}
.select2-container--default .select2-selection--multiple .select2-selection__clear:hover {
  color: var(--sx-danger);
}

.valid-feedback   { font-size: 0.78rem; color: var(--sx-success-text); }
.invalid-feedback { font-size: 0.78rem; color: var(--sx-danger-text); }

.form-text { font-size: 0.78rem; color: var(--sx-text-muted); margin-top: 0.3rem; }

/* Textarea */
textarea.form-control { resize: vertical; min-height: 100px; }

/* Input Group */
.input-group .input-group-text {
  /* Mismo fondo que el control, no el gris de Bootstrap (--bs-tertiary-bg):
     el addon comparte borde con el input y no lleva separador propio, así que
     un fondo distinto lo partía en dos cajas — se veía sobre todo al enfocar,
     con el borde ya naranja alrededor de un icono todavía gris. */
  background: var(--sx-card-bg);
  border: 1.5px solid var(--sx-border-color);
  border-radius: var(--radius);
  color: var(--sx-text-muted);
  font-size: 0.875rem;
  padding: 0.5rem 0.875rem;
}
.input-group .btn { border-width: 1.5px !important; }
/* Por POSICIÓN, no por tipo de tramo: un grupo puede tener más de dos (icono +
   campo + opción + botón, como los dominios propios de una escuela). Con reglas
   :first-child/:last-child por elemento, el tramo INTERMEDIO se quedaba con su
   borde y su radio completos —una caja suelta dentro del grupo— y el input, al
   cumplir las dos, acababa redondeado por la izquierda en mitad de la fila.
   Solo el primero y el último llevan radio; el borde compartido lo pone el
   vecino de la izquierda. */
.input-group > .form-control,
.input-group > .form-select,
.input-group > .input-group-text,
.input-group > .btn { border-radius: 0; }
.input-group > :not(:first-child) { border-left: none; }
/* El input lleva `height` FIJA (arriba), y una altura fija anula el `stretch`
   del flex: dentro del grupo se quedaba 3 px más corto que los tramos con icono
   —cuya caja de línea la marca el icono, no el texto— y su borde inferior
   cortaba antes que el de los vecinos. Se ve como una línea que no une. Con
   `auto` estira hasta el tramo más alto, sea cual sea su contenido. */
.input-group > .form-control,
.input-group > .form-select { height: auto; }
.input-group > :first-child { border-radius: var(--radius) 0 0 var(--radius); }
.input-group > :last-child  { border-radius: 0 var(--radius) var(--radius) 0; }
.input-group:focus-within .input-group-text,
.input-group:focus-within .btn-outline-secondary {
  border-color: var(--sx-primary-text) !important;
}

/* Checkbox & Radio */
.form-check-input {
  width: 1.15em;
  height: 1.15em;
  border: 2px solid var(--sx-border-color);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: var(--transition-fast);
}
.form-check-input:checked {
  background-color: var(--sx-primary-solid);
  border-color: var(--sx-primary-solid);
}
.form-check-input[type="radio"] { border-radius: 50%; }
.form-check-input:focus {
  box-shadow: none !important;
  border-color: var(--sx-primary-text);
  outline: none;
}
.form-check-label {
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--sx-text-body);
}

/* Switch — estado on/off claro: ✕ en gris (off) y ✓ en verde (on) dentro del
   knob; el track cambia de color. Sigue visible aunque esté deshabilitado.

   Fila en FLEX en vez del float de Bootstrap: con el float, una etiqueta de dos
   líneas ("Memoria persistente habilitada (los maestros pueden…)") se desalinea
   del switch y el texto queda flotando por debajo. Con flex, el switch y la
   PRIMERA línea del texto quedan a la misma altura, tenga la etiqueta una línea
   o cinco. En las de una línea se ve igual que antes (el gap sustituye al
   margin-left que tenía la etiqueta). */
.form-check.form-switch {
  display: flex;
  align-items: flex-start;   /* arriba: centrar dejaría el switch a media altura del párrafo */
  gap: 0.5rem;
  padding-left: 0;
}
.form-switch .form-check-input {
  width: 2.6em;
  height: 1.3em;
  /* Bootstrap lo saca del flujo con float + margin-left negativo. Como flex item
     el float ya no aplica, pero el margen SÍ: sin este reset el switch se sale
     por la izquierda de la tarjeta. flex-shrink:0 para que no lo aplaste una
     etiqueta larga. */
  margin-left: 0;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background-color: var(--sx-text-muted);
  border-color: var(--sx-text-muted);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3cpath d='M-1.2 -1.2 L1.2 1.2 M1.2 -1.2 L-1.2 1.2' stroke='%23939393' stroke-width='0.9' stroke-linecap='round'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
  background-color: var(--sx-success);
  border-color: var(--sx-success);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3cpath d='M-1.5 0 L-0.3 1.2 L1.6 -1.3' fill='none' stroke='%236fc191' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
}
/* Estado indeterminado (el checkbox "Todos" de una selección múltiple, con
   parte de las filas marcadas): color de marca, NO el azul por defecto de
   Bootstrap. Así: gris=off, naranja=parcial, naranja con ✓=todo.

   -solid y NO el naranja base: la raya que dibuja el navegador encima es
   BLANCA, y sobre #ec774b se queda en 2.4:1 — se veía lavada justo al lado del
   mismo checkbox ya marcado, que sí usa -solid. Es la regla de la paleta:
   relleno macizo con algo blanco encima → familia -solid.

   Solo checkbox: un radio sin ninguna opción marcada también matchea
   :indeterminate por spec del navegador (grupo sin selección), y ahí NO debe
   verse como "marcado" — se dejaría el naranja en radios que están vacíos. */
.form-check-input[type="checkbox"]:indeterminate {
  background-color: var(--sx-primary-solid);
  border-color: var(--sx-primary-solid);
}
.form-switch .form-check-input:indeterminate {
  background-color: var(--sx-primary-solid);
  border-color: var(--sx-primary-solid);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3cpath d='M-1.4 0 L1.4 0' stroke='%23939393' stroke-width='0.9' stroke-linecap='round'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:disabled {
  opacity: 0.7;        /* deshabilitado pero el estado/ícono sigue visible */
  cursor: not-allowed;
}
/* El separador lo pone el `gap` de la fila flex (arriba); un margin-left aquí
   se sumaría al gap y descuadraría todos los switches. */
.form-switch .form-check-label { margin-left: 0; }

/* Range */
.form-range::-webkit-slider-thumb {
  background: var(--sx-primary);
}
.form-range::-webkit-slider-runnable-track {
  background: var(--sx-border-color);
  border-radius: var(--radius-pill);
  height: 6px;
}
.form-range:focus { outline: none; }

/* Floating labels */
.form-floating > .form-control,
.form-floating > .form-select {
  height: calc(3.2rem + 2px);
  padding: 1rem 0.875rem 0.25rem;
}
.form-floating > label {
  color: var(--sx-text-muted);
  font-size: 0.875rem;
}

/* Select nativo */
.form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%239395a5' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 12px 12px;
  padding-right: 2.25rem;
  cursor: pointer;
}

/* ======================================================
   MODALS
   ====================================================== */
.modal-content {
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-xl);
  box-shadow: none;
  background: var(--sx-card-bg);
}
.modal-header {
  border-bottom: 1px solid var(--sx-border-color);
  padding: 1.25rem 1.5rem;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.modal-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--sx-text-heading);
}
.modal-body { padding: 1.5rem; }
.modal-footer {
  border-top: 1px solid var(--sx-border-color);
  padding: 1rem 1.5rem;
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
}
.btn-close {
  background-size: 0.75em;
  opacity: 0.5;
  border-radius: var(--radius-sm);
}
.btn-close:hover { opacity: 1; background-color: var(--sx-body-bg); }

/* Modal animación slide */
.modal.modal-slide .modal-dialog {
  transform: translateY(-30px);
  transition: transform 0.3s ease;
}
.modal.modal-slide.show .modal-dialog { transform: translateY(0); }

/* ======================================================
   TABS
   ====================================================== */
.nav-tabs {
  border-bottom: 2px solid var(--sx-border-color);
  gap: 0.15rem;
}
.nav-tabs li,
.nav-tabs .nav-item { margin-bottom: 0rem; }
.nav-tabs .nav-link {
  border: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  color: var(--sx-text-muted);
  font-weight: 700;
  font-size: 0.875rem;
  padding: 0.6rem 1.1rem;
  position: relative;
  transition: var(--transition-fast);
}
.nav-tabs .nav-link::after {
  content: '';
  position: absolute;
  /* -1px, no -2px: el borde de .nav-tabs son 2px, así que a -2px la barra
     quedaba por debajo y se veía despegada de la pestaña. */
  bottom: -1px;
  left: 0; right: 0;
  height: 2px;
  /* -solid: es la barra que marca la pestaña activa junto a un rótulo ya en
     --sx-primary-text, y con el naranja de marca los dos tonos no casaban. */
  background: var(--sx-primary-solid);
  /* Sin border-radius: 2px de alto con esquinas de píldora no se leen como
     redondeadas, solo aclaran las puntas. */
  transform: scaleX(0);
  transition: transform 0.2s ease;
  z-index: 2;
}
.nav-tabs .nav-link:hover { color: var(--sx-primary-text); background: var(--sx-primary-light); }
.nav-tabs .nav-link.active {
  color: var(--sx-primary-text);
  background: transparent;
  border-color: transparent;
}
.nav-tabs .nav-link.active::after { transform: scaleX(1); }

/* Nav pills */
.nav-pills .nav-link {
  border-radius: var(--radius-pill);
  color: var(--sx-text-body);
  font-weight: 700;
  font-size: 0.875rem;
  padding: 0.45rem 1rem;
  transition: var(--transition-fast);
}
.nav-pills .nav-link:hover { background: var(--sx-primary-light); color: var(--sx-primary-text); }
.nav-pills .nav-link.active { background: var(--sx-primary-solid); color: #fff; }

/* Tabs con íconos */
.nav-link .bi { margin-right: 0.4rem; }

/* ------------------------------------------------------
   PESTAÑAS EN COLUMNA (.sx-vtabs)
   Para formularios con MUCHOS grupos (CMS · Personalizar, once o más):
   la fila horizontal se partía en varias líneas y se volvía ilegible.
   En ≥lg la lista es una columna a la izquierda que acompaña al scroll;
   por debajo vuelve a ser UNA fila con desplazamiento lateral, para no
   comerse el ancho del formulario en pantalla estrecha.
   ------------------------------------------------------ */
.sx-vtabs__nav { gap: 0.15rem; }
.sx-vtabs__nav .nav-link { width: 100%; }
/* El foco de teclado tiene que verse: el pill activo lleva texto blanco
   sobre relleno sólido, así que el aro va POR FUERA. */
.sx-vtabs__nav .nav-link:focus-visible {
  outline: 2px solid var(--sx-primary-text);
  outline-offset: 2px;
  box-shadow: none;
}
@media (max-width: 991.98px) {
  .sx-vtabs__nav {
    flex-direction: row !important;
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 0.35rem;
  }
  .sx-vtabs__nav .nav-item { flex: 0 0 auto; }
  .sx-vtabs__nav .nav-link { width: auto; white-space: nowrap; }
}
@media (min-width: 992px) {
  .sx-vtabs__nav { position: sticky; top: 1rem; }
}

/* ======================================================
   ACCORDION
   ====================================================== */
.accordion-item {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius) !important;
  margin-bottom: 0.5rem;
  overflow: hidden;
}
.accordion-button {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--sx-text-heading);
  background: var(--sx-card-bg);
  padding: 1rem 1.25rem;
  border: none;
  box-shadow: none !important;
}
.accordion-button:not(.collapsed) {
  color: var(--sx-primary-text);
  background: var(--sx-primary-light);
  box-shadow: none;
}
.accordion-button::after {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239395a5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.accordion-button:not(.collapsed)::after {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ec774b'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.accordion-body {
  padding: 1rem 1.25rem;
  font-size: 0.875rem;
  color: var(--sx-text-body);
  border-top: 1px solid var(--sx-border-color);
}
/* Bootstrap quita el borde de arriba a todos menos al primero (.accordion-item:
   not(:first-of-type){border-top:0}) porque los apila pegados. Aquí van
   SEPARADOS (margin-bottom .5rem), así que sin esto cada caja se ve abierta por
   arriba: el borde tiene que cerrar los cuatro lados. */
.accordion-item:not(:first-of-type) { border-top: 1px solid var(--sx-border-color); }

.accordion-flush .accordion-item { border-radius: 0 !important; border-left: none; border-right: none; }
.accordion-flush .accordion-item:first-child { border-top: none; }

/* ======================================================
   PROGRESS
   ====================================================== */
.progress {
  background: var(--sx-border-color);
  border-radius: var(--radius-pill);
  height: 10px;
  overflow: hidden;
}
.progress-bar {
  /* Sin esto manda el azul por defecto de Bootstrap (--bs-progress-bar-bg).
     Quien necesite otro color lo pone inline y gana por especificidad. */
  background: var(--sx-primary);
  border-radius: var(--radius-pill);
  transition: width 0.6s ease;
  font-size: 0.65rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.progress-thin { height: 4px; }
.progress-md   { height: 8px; }
.progress-lg   { height: 14px; }

.bg-primary { background: var(--sx-primary-solid) !important; }
.text-primary { color: var(--sx-primary-text) !important; }
.border-primary { border-color: var(--sx-primary-text) !important; }
.link-primary { color: var(--sx-primary-text) !important; }
.link-primary:hover, .link-primary:focus { color: var(--sx-primary-text) !important;
  text-decoration: underline;
}
.bg-success { background: var(--sx-success) !important; }
.bg-danger  { background: var(--sx-danger)  !important; }
.bg-warning { background: var(--sx-warning) !important; }
.bg-info    { background: var(--sx-info)    !important; }
.bg-purple  { background: var(--sx-purple)  !important; }
.bg-dark    { background: var(--sx-dark)    !important; }

/* ======================================================
   SPINNERS
   ====================================================== */
.spinner-border {
  border-width: 3px;
  width: 2rem;
  height: 2rem;
}
.spinner-border-sm { width: 1.1rem; height: 1.1rem; border-width: 2px; }
.spinner-grow-sm   { width: 1rem;   height: 1rem; }

.spinner-border.text-primary { color: var(--sx-primary-text) !important; }
.spinner-border.text-success { color: var(--sx-success-text) !important; }
.spinner-border.text-danger  { color: var(--sx-danger)  !important; }
.spinner-border.text-warning { color: var(--sx-warning-text) !important; }
.spinner-border.text-info    { color: var(--sx-info)    !important; }
.spinner-border.text-purple  { color: var(--sx-purple)  !important; }

/* ======================================================
   TOASTS
   ====================================================== */
.toast {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg);
  box-shadow: none;
  min-width: 300px;
  font-size: 0.875rem;
}
.toast-header {
  background: transparent;
  border-bottom: 1px solid var(--sx-border-color);
  padding: 0.75rem 1rem;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--sx-text-heading);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.toast-body { padding: 0.875rem 1rem; color: var(--sx-text-body); font-size: 0.95rem; }
/* z-index sobre el topbar (1030) para que el toast salga arriba del todo.
   transform: el contenedor vive en su PROPIA capa GPU; así, al aparecer/animar
   el toast, Chrome lo compone encima sin re-rasterizar (parpadear) el topbar que
   queda debajo. El contenedor no tiene descendientes position:fixed, así que el
   containing-block que crea el transform no afecta a nada. */
.toast-container { z-index: 1090; transform: translateZ(0); will-change: transform; }

/* Variantes de toast */
.toast-success .toast-header { color: var(--sx-success-dark); }
.toast-danger  .toast-header { color: var(--sx-danger-dark); }
.toast-warning .toast-header { color: var(--sx-warning-dark); }
.toast-info    .toast-header { color: var(--sx-info-dark); }

/* Barra de progreso del toast (cuenta regresiva del tiempo de vida). */
.toast { position: relative; overflow: hidden; }
.toast-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 100%;
  background: var(--sx-primary);
  border-bottom-left-radius: var(--radius-lg);
}
.toast-progress-success { background: var(--sx-success); }
.toast-progress-danger  { background: var(--sx-danger); }
.toast-progress-warning { background: var(--sx-warning); }
.toast-progress-info    { background: var(--sx-info); }

/* Modal de confirmación de borrado: arriba-centro en vez de centrado vertical. */
.modal-dialog.sx-modal-top {
  margin-top: 4.5rem;
  margin-bottom: auto;
}

/* Fix: cuando un <form> envuelve header+body+footer dentro de un modal
   scrollable, Bootstrap recorta el footer (el form es el unico hijo flex y
   desborda .modal-content). display:contents devuelve header/body/footer como
   hijos directos de .modal-content y restaura el scroll + footer fijo. */
.modal-dialog-scrollable > .modal-content > form { display: contents; }

/* Advertencia reforzada en confirmaciones de borrado (no hay vuelta atras). */
.sx-confirm-danger-warning {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 0.85rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--sx-danger-light);
  border-left: 4px solid var(--sx-danger);
  color: var(--sx-danger-dark);
}
.sx-confirm-danger-warning i {
  font-size: 1.35rem;
  line-height: 1.2;
  color: var(--sx-danger);
  flex-shrink: 0;
}
.sx-confirm-danger-warning strong { font-weight: 700; }
.sx-confirm-danger-warning span { font-size: 0.85rem; opacity: 0.9; }

/* Selector de idioma del topbar: bandera arriba, etiqueta debajo. */
.sx-lang-menu .dropdown-item { padding: 0.6rem 0.75rem; font-weight: 600; }
.sx-lang-menu .dropdown-item img { box-shadow: var(--shadow-sm); }
.sx-lang-menu .dropdown-item span { font-size: 0.8rem; }

/* ======================================================
   TOOLTIPS & POPOVERS
   ====================================================== */
.tooltip .tooltip-inner {
  background: var(--sx-dark);
  border-radius: var(--radius-sm);
  font-size: 0.78rem;
  font-family: var(--font-body);
  font-weight: 600;
  padding: 0.35rem 0.7rem;
  max-width: 200px;
}
.tooltip .tooltip-arrow::before { border-top-color: var(--sx-dark); }

.popover {
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg);
  box-shadow: none;
  font-family: var(--font-body);
  font-size: 0.875rem;
  background: var(--sx-card-bg);
}
.popover-header {
  background: var(--sx-body-bg);
  border-bottom: 1px solid var(--sx-border-color);
  font-weight: 800;
  font-size: 0.9rem;
  color: var(--sx-text-heading);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 0.75rem 1rem;
}
.popover-body { padding: 0.875rem 1rem; color: var(--sx-text-body); }

/* ======================================================
   PAGINATION
   ====================================================== */
.pagination {
  --bs-pagination-focus-box-shadow: none;
  --bs-pagination-color: var(--sx-text-body);
  --bs-pagination-bg: var(--sx-card-bg);
  --bs-pagination-border-color: var(--sx-border-color);
  /* Hover = tinte claro + tinta (no relleno macizo): el macizo se confundía con
     la página activa y no dejaba ver cuál era. El activo sí va macizo, y en
     -solid porque lleva texto blanco encima. */
  /* Bootstrap deja el foco en su gris genérico (#e9ecef) y el tema nunca lo
     remapeaba: el botón que acababas de pulsar se quedaba con un gris ajeno a
     la paleta. Va al mismo tinte que el hover. */
  --bs-pagination-focus-color: var(--sx-primary-text);
  --bs-pagination-focus-bg: var(--sx-primary-light);
  --bs-pagination-hover-color: var(--sx-primary-text);
  --bs-pagination-hover-bg: var(--sx-primary-light);
  --bs-pagination-hover-border-color: var(--sx-primary-text);
  --bs-pagination-active-color: #fff;
  --bs-pagination-active-bg: var(--sx-primary-solid);
  --bs-pagination-active-border-color: var(--sx-primary-solid);
  --bs-pagination-disabled-color: var(--sx-text-muted);
  --bs-pagination-disabled-bg: var(--sx-card-bg);
  --bs-pagination-disabled-border-color: var(--sx-border-color);
  gap: 0.2rem;
  align-items: center;
}
.page-link {
  border: 1.5px solid var(--sx-border-color);
  border-radius: var(--radius-sm) !important;
  font-weight: 700;
  font-size: 0.8rem;
  padding: 0.4rem 0.75rem;
  transition: var(--transition-fast);
  background: var(--sx-card-bg);
  color: var(--sx-text-body);
}
.page-link:hover {
  background: var(--sx-primary-light);
  border-color: var(--sx-primary-text);
  color: var(--sx-primary-text);
}
.page-item.active .page-link { color: #fff;
  background: var(--sx-primary-solid);
  border-color: var(--sx-primary-solid);
}
.page-item.disabled .page-link { opacity: 0.4; pointer-events: none; }

/* Paginación redondeada */
.pagination-rounded .page-link { border-radius: 50% !important; width: 36px; height: 36px; padding: 0; display: flex; align-items: center; justify-content: center; }

/* ======================================================
   LIST GROUP
   ====================================================== */
.list-group-item {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  padding: 0.75rem 1.1rem;
  color: var(--sx-text-body);
  font-size: 0.875rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  transition: var(--transition-fast);
}
.list-group-item:first-child { border-radius: var(--radius) var(--radius) 0 0; }
.list-group-item:last-child  { border-radius: 0 0 var(--radius) var(--radius); }
.list-group-item:only-child  { border-radius: var(--radius); }
/* flush = edge-to-edge dentro de un .card: sin esquinas propias (las da el .card). */
.list-group-flush > .list-group-item:first-child,
.list-group-flush > .list-group-item:last-child,
.list-group-flush > .list-group-item:only-child { border-radius: 0; }
/* …salvo cuando el ítem TOCA el canto del card. "Las da el .card" solo es cierto
   mientras el card recorte: .sx-card-overflow —el que usan las listas con menú
   de acciones por fila, para que el dropdown pueda salirse— tiene
   overflow:visible, y ahí el ítem, que es un rectángulo opaco (fondo
   --sx-card-bg, y --sx-body-bg al pasar el ratón), se pintaba con las esquinas
   rectas por encima del redondeo y borraba el trazo curvo del borde del card.
   Se le devuelve el radio al primer/último ítem cuando la lista abre o cierra el
   card. El radio es el del card MENOS su borde de 1px: la lista va por dentro de
   ese borde, y con los 12px pelados la curva asomaba por fuera. */
.card > .list-group-flush:last-child > .list-group-item:last-child {
  border-bottom-left-radius: calc(var(--radius) - 1px);
  border-bottom-right-radius: calc(var(--radius) - 1px);
}
.card > .list-group-flush:first-child > .list-group-item:first-child {
  border-top-left-radius: calc(var(--radius) - 1px);
  border-top-right-radius: calc(var(--radius) - 1px);
}
.list-group-item + .list-group-item { border-top: none; }
/* Activo = UN solo estilo en toda la app (acceso rápido, plan seleccionado,
   día seleccionado…): fondo gris neutro con texto e ícono en el naranja de la
   marca. El ícono no lleva color propio, hereda este. */
.list-group-item.active { background: var(--sx-primary-light); color: var(--sx-primary-text); border-color: var(--sx-primary-text); font-weight: 700; }
[data-theme="dark"] .list-group-item.active { background: var(--sx-body-bg); color: var(--sx-primary-text); border-color: var(--sx-border-color); }
.list-group-item-action:hover { background: var(--sx-body-bg); cursor: pointer; }

/* ======================================================
   TABLES
   ====================================================== */
.table {
  font-size: 0.875rem;
  color: var(--sx-text-body);
  --bs-table-bg: transparent;
  --bs-table-color: var(--sx-text-body);
}
.table thead th {
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--sx-text-muted);
  border-bottom: 1px solid var(--sx-border-color);
  padding: 0.6rem 0.75rem;
  background: var(--sx-body-bg);
  white-space: nowrap;
}
.table td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--sx-border-color);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: none; }
/* Collapsible Row Tables (.sx-lvl): TODAS las filas conservan su border-bottom,
   incluida la última (la regla global :last-child la quitaría). El !important
   gana a `.table tbody tr:last-child td` (mayor especificidad) para que ninguna
   fila quede sin borde. Aplica al nivel raíz y a las tablas anidadas hijas. */
.sx-lvl > tbody > tr > td,
.sx-lvl > tbody > tr:last-child > td { border-bottom: 1px solid var(--sx-border-color) !important; }
/* EXCEPCIÓN: la última fila de la tabla RAÍZ (la que va a ras del card, hija
   directa del card-body) sí pierde el borde. Ahí la tabla termina justo contra
   el canto inferior del card, así que esa línea caía a 1px de la del propio
   card: se veían dos rayas pegadas al final de la tabla. El canto del card ya
   la cierra.
   Solo la raíz: las tablas ANIDADAS de los niveles hijos viven dentro de
   .sx-detail-inner (su .table-responsive NO es hijo directo del card-body) y no
   terminan contra ningún canto — ahí el borde de la última fila es lo único que
   cierra la tabla, y se conserva.

   SON DOS SELECTORES porque `tr:last-child` NO es la última fila que se VE. En
   estas tablas cada fila de nivel arrastra pegada su fila-detalle (.sx-detail,
   la que contiene la tabla anidada), y esa fila-detalle está en `d-none`
   mientras el nivel está cerrado: el último <tr> del tbody es SIEMPRE una
   .sx-detail, oculta o no.
     · Nivel ABIERTO  → la .sx-detail final se ve y es la que toca el canto del
       card: la pierde ella (primer selector). La fila de nivel de encima
       conserva la suya, que es justo la raya que separa la fila del panel que
       acaba de abrirse.
     · Nivel CERRADO  → la .sx-detail final está oculta y quien toca el canto es
       la fila de nivel anterior: la pierde ella (segundo selector). Sin esto se
       veía una raya de más, pegada a la del card, que engordaba el canto.
   El segundo selector se lee: "la fila cuya SIGUIENTE hermana es una fila-detalle
   oculta y última". Va en su PROPIA regla, no en la lista del primero: un
   navegador sin :has() tira el bloque entero cuando un selector de la lista no le
   suena, y se llevaría por delante también el caso —válido— de arriba. */
.card-body > .table-responsive > .sx-lvl > tbody > tr:last-child > td { border-bottom: 0 !important; }
.card-body > .table-responsive > .sx-lvl > tbody > tr:has(+ tr.sx-detail.d-none:last-child) > td { border-bottom: 0 !important; }
/* La primera columna de toda tabla siempre alineada a la izquierda (text-start),
   aún si el módulo aplica .text-center a sus columnas. */
.table thead th:first-child,
.table tbody td:first-child { text-align: left !important; }
/* Hijos DIRECTOS, no descendientes: con `.table-hover tbody tr:hover td` el
   hover se colaba en cualquier tabla anidada dentro de una celda (el desglose
   por escuela de Salud, sin ir más lejos), porque sus filas también son
   descendientes de la tabla de fuera. Para una tabla normal el resultado es el
   mismo — tbody siempre cuelga de table y td de tr — así que esto solo corta la
   fuga. */
.table-hover > tbody > tr:hover > td { background: var(--sx-row-hover); }
.table-striped tbody tr:nth-of-type(odd) td { background: var(--sx-body-bg); }
.table > :not(caption) > * > * { box-shadow: none; }

/* Selección múltiple + borrado masivo (sx-bulk-delete.js) */
.sx-bulk-col { width: 1px; white-space: nowrap; text-align: center; }
.table > :not(caption) > * > .sx-bulk-col { padding-left: 1rem; padding-right: 1rem; }
.sx-bulk-col .form-check-input { margin: 0; cursor: pointer; float: none; width: 1.1rem; height: 1.1rem; flex: none; vertical-align: middle; }
/* Ya no hay aviso incrustado sobre el listado: la eliminación en masa vive
   entera en el modal de sx-bulk-delete.js (.sx-bulk-modal). */

/* Celda de icono UNIFICADA en tablas (regla global, helper sa_icon_cell()) */
.sx-icon-cell { display: inline-flex; align-items: center; gap: 0.5rem; }
.sx-icon-cell > i { font-size: 1.05rem; line-height: 1; color: var(--sx-text-heading); }
.sx-icon-cell > code { font-size: 0.8em; }

/* Avatar circular (tablas, p.ej. usuarios) */
.sx-avatar {
  width: 34px;
  height: 34px;
  min-width: 34px;
  border-radius: 50%;
  object-fit: cover;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-weight: 700;
  color: #fff;
  text-transform: uppercase;
  line-height: 1;
  border: 2px solid var(--sx-card-bg);
  box-shadow: 0 0 0 1px var(--sx-border-color);
}
.sx-avatar--initials { letter-spacing: 0.02em; background: var(--sx-primary); }

/* Avatar de ICONO (.sx-avatar.sx-avatar--ico): el mismo círculo, pero para lo
   que no es una persona y por tanto no tiene iniciales — una cuenta de banco,
   una categoría, un suplidor, un mes del cierre. Tinte claro + tinta del color,
   como un badge-soft: aquí el icono NO va en blanco (no es relleno macizo).
   Seis módulos tenían esta misma regla copiada con nombre propio
   (.ann-main-cell__icon, .rb-rubric__icon, .asg-eval__icon...); esta es la
   versión con nombre del sistema para lo que se escriba a partir de ahora. */
.sx-avatar--ico {
  flex: 0 0 auto;
  font-size: 0.95rem;
  background: var(--sx-primary-light);
  color: var(--sx-primary-text);
}
.sx-avatar--ico.is-green  { background: var(--sx-green-light);  color: var(--sx-green-text); }
.sx-avatar--ico.is-sky    { background: var(--sx-sky-light);    color: var(--sx-sky-text); }
.sx-avatar--ico.is-purple { background: var(--sx-purple-light); color: var(--sx-purple-text); }
.sx-avatar--ico.is-yellow { background: var(--sx-yellow-light); color: var(--sx-yellow-text); }
.sx-avatar--ico.is-red    { background: var(--sx-red-light);    color: var(--sx-red-text); }
.sx-avatar--ico.is-muted  { background: var(--sx-body-bg);      color: var(--sx-text-muted); }

/* Celda "icono + nombre (+ subtítulo)" de las tablas ricas. Mismo hueco que
   .sx-att-teacher, pero con el avatar de icono. */
/* Sin min-width a propósito: estas celdas también salen en tablas dentro de un
   modal (el reparto de un cobro, el carrito de una venta), y ahí un ancho
   mínimo empuja la tabla fuera de la caja. */
.sx-ico-cell { display: flex; align-items: center; gap: 0.65rem; }
.sx-ico-cell__text { min-width: 0; }
.sx-ico-cell__nm {
  display: block;
  color: var(--sx-text-heading);
  font-family: var(--font-display);
  font-size: 0.9rem;
  font-weight: 800;
}
.sx-ico-cell__sub { display: block; color: var(--sx-text-muted); font-size: 0.78rem; }

/* Avatar en tabla */
.table-avatar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.table-avatar-img {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--sx-primary-solid);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 800;
  flex-shrink: 0;
}

/* ======================================================
   DROPDOWNS
   ====================================================== */
.dropdown-menu {
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg);
  box-shadow: none;
  padding: 0.5rem;
  font-size: 0.875rem;
  background: var(--sx-card-bg);
  animation: dropFade 0.15s ease;
}
.dropdown-item {
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.875rem;
  color: var(--sx-text-body);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  transition: var(--transition-fast);
}
.dropdown-item:hover { background: var(--sx-primary-light); color: var(--sx-primary-text); }
/* :focus iba aparte de :hover → caía al gris de --bs-dropdown-link-hover-bg.
   Y el ítem es lo ÚNICO del dropdown que Bootstrap no resetea (outline:0 solo
   lo pone en .btn), así que al pincharlo salía el anillo de foco por defecto
   del navegador: azul. Ambas cosas se tematizan aquí, para todos los dropdowns. */
.dropdown-item:focus { background: var(--sx-primary-light); color: var(--sx-primary-text); }
.dropdown-item:focus-visible {
  outline: 2px solid var(--sx-primary);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.dropdown-item.active, .dropdown-item:active { background: var(--sx-primary-solid); color: #fff; }
.dropdown-divider { border-color: var(--sx-border-color); margin: 0.35rem 0; }
/* Iconos de las ACCIONES (menú "⋯" y botones inline de la columna Acciones):
   color por INTENCIÓN, no uno por ítem. El mapa va por clase de icono para que
   valga en las ~90 acciones del sistema sin tocar el markup de cada página (los
   menús y los botones los arma el JS/PHP de cada módulo). Se usan los tonos
   TINTA (-text de la familia por nombre: red/green/sky/yellow/purple), no los
   tonos base: un icono es texto y el base no llega a contraste.
   Si añades una acción con un icono nuevo, súmalo al grupo que le toque. */
.dropdown-item > i { flex: 0 0 auto; }

/* El botón inline toma el color de su INTENCIÓN, no de la variante Bootstrap que
   le hubiera tocado a esa página (secondary / primary / danger / info, distinta
   en cada tabla). Quien pinta es el mapa de más abajo: fija en el BOTÓN el tinte
   de fondo (-light) y el tono tinta (-text): el icono hereda el color y el disco
   de fondo es de la misma familia. Mismo par que los `badge-soft-*`.
   El borde no se repite por grupo: sale de `currentColor` rebajado, así que cada
   botón lo saca de su propia tinta —incluido el fallback gris— con una sola
   línea. Esta regla es eso, el fallback: icono fuera del mapa → disco neutro.
   El hover no cambia de color, solo oscurece un punto lo que ya hay. */
.sx-att-actions .btn,
.sx-att-actions .btn:focus,
.sx-att-actions .btn:active,
.sx-att-actions .btn.active,
.sx-att-actions .btn:hover {
  border: 1px solid color-mix(in srgb, currentColor 28%, transparent) !important;
  color: var(--sx-text-muted) !important;
  background: var(--sx-body-bg) !important;
  box-shadow: none !important;
}
.sx-att-actions .btn:hover { filter: brightness(0.95); }
/* Círculo de icono: el botón de acción no lleva rótulo nunca (el nombre va en el
   title/aria-label), así que la caja rectangular solo aportaba ancho. Medida fija
   para que la columna no baile según el icono que le toque a cada fila. */
.sx-att-actions .btn {
  width: 2rem;
  height: 2rem;
  padding: 0 !important;
  border-radius: 50% !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  flex: 0 0 auto;
}
.sx-att-actions .btn > i { color: inherit !important; font-size: 0.875rem; }
/* El disparador "⋯" vive FUERA de .sx-att-actions (lleva su propio .dropdown),
   pero ocupa la misma columna: si no, en una tabla donde unas filas muestran 2
   botones y otras el menú, conviven círculo y rectángulo. El icono de tres
   puntos no se usa en ningún otro sitio del markup, así que basta con él. */
.btn:has(> i[class*="bi-three-dots"]) {
  width: 2rem;
  height: 2rem;
  padding: 0 !important;
  border-radius: 50% !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  border: 1px solid color-mix(in srgb, currentColor 28%, transparent) !important;
  color: var(--sx-text-muted) !important;
  background: var(--sx-body-bg) !important;
  box-shadow: none !important;
}
.btn:has(> i[class*="bi-three-dots"]):hover { filter: brightness(0.95); }

/* Iconos de <h2 class="card-title"> en TODO card-header (app y core): por
   defecto tinte de marca, y el mismo mapa de intención de abajo lo afina
   cuando el icono cae en una categoría conocida (editar/borrar/ver/...).
   Sin fondo ni negrita — es un encabezado, no un botón de acción. */
.card-header .card-title > i[class*="bi-"] { color: var(--sx-primary-text); }

/* Editar / crear → marca */
.dropdown-item > i[class*="bi-pencil"],
.dropdown-item > i[class*="bi-pen"],
.dropdown-item > i[class*="bi-plus"] { color: var(--sx-primary-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-pencil"]),
.sx-att-actions .btn:has(> i[class*="bi-pen"]),
.sx-att-actions .btn:has(> i[class*="bi-plus"]) { color: var(--sx-primary-text) !important; background: var(--sx-primary-light) !important; }
/* Destructivo */
.dropdown-item > i[class*="bi-trash"],
.dropdown-item > i[class*="bi-x-lg"],
.dropdown-item > i[class*="bi-x-circle"],
.dropdown-item > i[class*="bi-slash-circle"],
.dropdown-item > i[class*="bi-x-octagon"],
.card-header .card-title > i[class*="bi-trash"],
.card-header .card-title > i[class*="bi-x-lg"],
.card-header .card-title > i[class*="bi-x-circle"],
.card-header .card-title > i[class*="bi-slash-circle"] { color: var(--sx-red-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-trash"]),
.sx-att-actions .btn:has(> i[class*="bi-x-lg"]),
.sx-att-actions .btn:has(> i[class*="bi-x-circle"]),
.sx-att-actions .btn:has(> i[class*="bi-slash-circle"]),
.sx-att-actions .btn:has(> i[class*="bi-x-octagon"]) { color: var(--sx-red-text) !important; background: var(--sx-red-light) !important; }
/* Confirmar / completar / poner en marcha */
.dropdown-item > i[class*="bi-check"],
.dropdown-item > i[class*="bi-list-check"],
.dropdown-item > i[class*="bi-journal-check"],
.dropdown-item > i[class*="bi-send-check"],
.dropdown-item > i[class*="bi-clipboard-check"],
.dropdown-item > i[class*="bi-database-add"],
.dropdown-item > i[class*="bi-arrow-up-circle"],
.dropdown-item > i[class*="bi-arrow-counterclockwise"],
.dropdown-item > i[class*="bi-play"],
.card-header .card-title > i[class*="bi-check"],
.card-header .card-title > i[class*="bi-list-check"],
.card-header .card-title > i[class*="bi-journal-check"],
.card-header .card-title > i[class*="bi-clipboard-check"],
.card-header .card-title > i[class*="bi-database-add"],
.card-header .card-title > i[class*="bi-play"],
.card-header .card-title > i[class*="bi-graph-up"],
.card-header .card-title > i[class*="bi-mortarboard"] { color: var(--sx-green-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-check"]),
.sx-att-actions .btn:has(> i[class*="bi-list-check"]),
.sx-att-actions .btn:has(> i[class*="bi-journal-check"]),
.sx-att-actions .btn:has(> i[class*="bi-send-check"]),
.sx-att-actions .btn:has(> i[class*="bi-clipboard-check"]),
.sx-att-actions .btn:has(> i[class*="bi-database-add"]),
.sx-att-actions .btn:has(> i[class*="bi-arrow-up-circle"]),
.sx-att-actions .btn:has(> i[class*="bi-arrow-counterclockwise"]),
.sx-att-actions .btn:has(> i[class*="bi-play"]) { color: var(--sx-green-text) !important; background: var(--sx-green-light) !important; }
/* Ver / consultar / imprimir / descargar / abrir */
.dropdown-item > i[class*="bi-eye"],
.dropdown-item > i[class*="bi-printer"],
.dropdown-item > i[class*="bi-download"],
.dropdown-item > i[class*="bi-files"],
.dropdown-item > i[class*="bi-file-earmark"],
.dropdown-item > i[class*="bi-bar-chart"],
.dropdown-item > i[class*="bi-clock-history"],
.dropdown-item > i[class*="bi-list-ol"],
.dropdown-item > i[class*="bi-card-checklist"],
.dropdown-item > i[class*="bi-card-text"],
.dropdown-item > i[class*="bi-paperclip"],
.dropdown-item > i[class*="bi-link-45deg"],
.dropdown-item > i[class*="bi-box-arrow-up-right"],
.card-header .card-title > i[class*="bi-eye"],
.card-header .card-title > i[class*="bi-printer"],
.card-header .card-title > i[class*="bi-download"],
.card-header .card-title > i[class*="bi-files"],
.card-header .card-title > i[class*="bi-file-earmark"],
.card-header .card-title > i[class*="bi-bar-chart"],
.card-header .card-title > i[class*="bi-pie-chart"],
.card-header .card-title > i[class*="bi-clock-history"],
.card-header .card-title > i[class*="bi-list-ol"],
.card-header .card-title > i[class*="bi-card-checklist"],
.card-header .card-title > i[class*="bi-card-text"],
.card-header .card-title > i[class*="bi-paperclip"],
.card-header .card-title > i[class*="bi-link-45deg"],
.card-header .card-title > i[class*="bi-gender-ambiguous"],
.card-header .card-title > i[class*="bi-box-arrow-up-right"] { color: var(--sx-sky-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-eye"]),
.sx-att-actions .btn:has(> i[class*="bi-printer"]),
.sx-att-actions .btn:has(> i[class*="bi-download"]),
.sx-att-actions .btn:has(> i[class*="bi-files"]),
.sx-att-actions .btn:has(> i[class*="bi-file-earmark"]),
.sx-att-actions .btn:has(> i[class*="bi-bar-chart"]),
.sx-att-actions .btn:has(> i[class*="bi-clock-history"]),
.sx-att-actions .btn:has(> i[class*="bi-list-ol"]),
.sx-att-actions .btn:has(> i[class*="bi-card-checklist"]),
.sx-att-actions .btn:has(> i[class*="bi-card-text"]),
.sx-att-actions .btn:has(> i[class*="bi-paperclip"]),
.sx-att-actions .btn:has(> i[class*="bi-link-45deg"]),
.sx-att-actions .btn:has(> i[class*="bi-box-arrow-up-right"]) { color: var(--sx-sky-text) !important; background: var(--sx-sky-light) !important; }
/* Acceso / seguridad / identidad */
.dropdown-item > i[class*="bi-key"],
.dropdown-item > i[class*="bi-shield"],
.dropdown-item > i[class*="bi-incognito"],
.dropdown-item > i[class*="bi-person"],
.dropdown-item > i[class*="bi-people"],
.dropdown-item > i[class*="bi-lock"],
.card-header .card-title > i[class*="bi-key"],
.card-header .card-title > i[class*="bi-shield"],
.card-header .card-title > i[class*="bi-incognito"],
.card-header .card-title > i[class*="bi-person"],
.card-header .card-title > i[class*="bi-people"],
.card-header .card-title > i[class*="bi-lock"] { color: var(--sx-yellow-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-key"]),
.sx-att-actions .btn:has(> i[class*="bi-shield"]),
.sx-att-actions .btn:has(> i[class*="bi-incognito"]),
.sx-att-actions .btn:has(> i[class*="bi-person"]),
.sx-att-actions .btn:has(> i[class*="bi-people"]),
.sx-att-actions .btn:has(> i[class*="bi-lock"]) { color: var(--sx-yellow-text) !important; background: var(--sx-yellow-light) !important; }
/* Comunicación y temáticos */
.dropdown-item > i[class*="bi-megaphone"],
.dropdown-item > i[class*="bi-envelope"],
.dropdown-item > i[class*="bi-chat"],
.dropdown-item > i[class*="bi-send"],
.dropdown-item > i[class*="bi-calendar"],
.dropdown-item > i[class*="bi-globe"],
.dropdown-item > i[class*="bi-puzzle"],
.dropdown-item > i[class*="bi-easel"],
.dropdown-item > i[class*="bi-patch-question"],
.dropdown-item > i[class*="bi-mortarboard"],
.dropdown-item > i[class*="bi-stars"],
.dropdown-item > i[class*="bi-arrow-repeat"],
.card-header .card-title > i[class*="bi-megaphone"],
.card-header .card-title > i[class*="bi-envelope"],
.card-header .card-title > i[class*="bi-chat"],
.card-header .card-title > i[class*="bi-send"],
.card-header .card-title > i[class*="bi-calendar"],
.card-header .card-title > i[class*="bi-globe"],
.card-header .card-title > i[class*="bi-puzzle"],
.card-header .card-title > i[class*="bi-easel"],
.card-header .card-title > i[class*="bi-patch-question"],
.card-header .card-title > i[class*="bi-stars"],
.card-header .card-title > i[class*="bi-arrow-repeat"],
.card-header .card-title > i[class*="bi-kanban"] { color: var(--sx-purple-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-megaphone"]),
.sx-att-actions .btn:has(> i[class*="bi-bell"]),
.sx-att-actions .btn:has(> i[class*="bi-envelope"]),
.sx-att-actions .btn:has(> i[class*="bi-chat"]),
.sx-att-actions .btn:has(> i[class*="bi-send"]),
.sx-att-actions .btn:has(> i[class*="bi-calendar"]),
.sx-att-actions .btn:has(> i[class*="bi-globe"]),
.sx-att-actions .btn:has(> i[class*="bi-puzzle"]),
.sx-att-actions .btn:has(> i[class*="bi-easel"]),
.sx-att-actions .btn:has(> i[class*="bi-patch-question"]),
.sx-att-actions .btn:has(> i[class*="bi-mortarboard"]),
.sx-att-actions .btn:has(> i[class*="bi-stars"]),
.sx-att-actions .btn:has(> i[class*="bi-arrow-repeat"]) { color: var(--sx-purple-text) !important; background: var(--sx-purple-light) !important; }
/* "bi-send-check" (marcar como enviado) cae en los DOS grupos por subcadena:
   verde por -check y morado por bi-send. Gana el último, así que se repite aquí. */
.dropdown-item > i[class*="bi-send-check"] { color: var(--sx-green-text) !important; }
.sx-att-actions .btn:has(> i[class*="bi-send-check"]) { color: var(--sx-green-text) !important; background: var(--sx-green-light) !important; }
.card-header .card-title > i[class*="bi-send-check"] { color: var(--sx-green-text) !important; }
/* El mapa de arriba lo comparten los DOS destinos, pero el disco de color es solo
   del botón inline: en el menú "⋯" el icono va suelto sobre el ítem, sin caja.
   Por eso cada grupo son DOS reglas y no una lista de selectores compartida —
   con una sola, el fondo del botón caía también sobre el icono del menú. */
/* Ítem del menú en estado activo: fondo sólido, así que todo va en blanco. */
.dropdown-item.active > i, .dropdown-item:active > i { color: #fff !important; }
.dropdown-header {
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sx-text-muted);
  padding: 0.5rem 0.875rem 0.25rem;
}

/* ======================================================
   WELCOME BANNER
   ====================================================== */
.sx-welcome-banner {
  background: var(--gradient-primary);
  border-radius: var(--radius);
  padding: 1.1rem 1.5rem;
  color: #fff;
  position: relative;
  overflow: hidden;
  margin-bottom: 1rem;
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  justify-content: space-between;
}
.sx-welcome-banner::before {
  content: '';
  position: absolute;
  right: -40px;
  top: -60px;
  width: 180px;
  height: 180px;
  border-radius: 50%;
  background: rgba(255,255,255,0.07);
}
.sx-welcome-banner::after {
  content: '';
  position: absolute;
  right: 60px;
  bottom: -50px;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  background: rgba(255,255,255,0.06);
}
/* El hijo debe estirarse: si no, el ms-auto del botón "Ver perfil" no tiene
   espacio que repartir y el botón se pega al texto en vez de irse al borde. */
.sx-welcome-banner > div { flex: 1 1 auto; min-width: 0; }
.sx-welcome-banner h2 {
  font-family: var(--font-display);
  color: #fff;
  font-size: 1.25rem;
  margin-bottom: 0.15rem;
  font-weight: 800;
}
.sx-welcome-banner p { color: rgba(255,255,255,0.85); margin-bottom: 0;
  font-size: 0.85rem;
}

/* Banner compacto (dashboard): menos alto, avatar + saludo + botón */
.sx-welcome-banner--compact { padding: 1rem 1.5rem; margin-bottom: 1.25rem; }
.sx-welcome-banner--compact h2 { font-size: 1.35rem; margin-bottom: 0.1rem; }
.sx-welcome-banner--compact p { font-size: 0.85rem; }
.sx-welcome-avatar {
  width: 54px; height: 54px; border-radius: 50%; flex-shrink: 0;
  object-fit: cover; display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid rgba(255,255,255,0.6);
  box-shadow: 0 0 0 3px rgba(255,255,255,0.18);
}
.sx-welcome-avatar--initials {
  background: rgba(255,255,255,0.2); color: #fff;
  font-size: 1.25rem; font-weight: 700; font-family: var(--font-display);
}

/* Badge para el banner de bienvenida (fecha/hora en vivo, año académico).
   Translúcido sobre el degradado, texto blanco. */
.sx-welcome-badge {
  background: rgba(255,255,255,0.18);
  color: #fff;
  border: 1px solid rgba(255,255,255,0.28);
  font-weight: 600;
  white-space: nowrap;
}
.sx-welcome-badge i { opacity: 0.9; }

/* ======================================================
   WIDGET VARIOS
   ====================================================== */

/* Activity feed */
.sx-activity-feed { list-style: none; padding: 0; }
.sx-activity-item {
  display: flex;
  gap: 0.875rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--sx-border-color);
}
.sx-activity-item:last-child { border-bottom: none; }
.sx-activity-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  flex-shrink: 0;
}
.sx-activity-content { flex: 1; }
.sx-activity-text { font-size: 0.8rem; font-weight: 600; color: var(--sx-text-body); margin-bottom: 0.15rem; }
.sx-activity-time { font-size: 0.72rem; color: var(--sx-text-muted); }

/* Countdown widget */
.sx-countdown {
  display: flex;
  gap: 1rem;
  justify-content: center;
}
.sx-countdown-unit {
  text-align: center;
  min-width: 60px;
}
.sx-countdown-value {
  font-family: var(--font-display);
  font-size: 2.5rem;
  color: var(--sx-primary-text);
  line-height: 1;
  display: block;
}
.sx-countdown-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--sx-text-muted);
  letter-spacing: 0.05em;
}

/* Quote widget */
.sx-quote {
  border-left: 4px solid var(--sx-primary);
  padding: 1rem 1.25rem;
  background: var(--sx-primary-light);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.sx-quote-text {
  font-style: italic;
  font-size: 0.9rem;
  color: var(--sx-text-body);
  margin-bottom: 0.5rem;
}
.sx-quote-author { font-size: 0.78rem; font-weight: 800; color: var(--sx-primary-text); }

/* ======================================================
   FILE UPLOAD
   ====================================================== */
.sx-dropzone {
  border: 2px dashed var(--sx-border-color);
  border-radius: var(--radius-lg);
  padding: 2.5rem 1.5rem;
  text-align: center;
  cursor: pointer;
  transition: var(--transition-fast);
  background: var(--sx-body-bg);
}
.sx-dropzone:hover,
.sx-dropzone.drag-over {
  border-color: var(--sx-primary-text);
  background: var(--sx-primary-light);
}
.sx-dropzone-icon {
  font-size: 2.5rem;
  color: var(--sx-primary-text);
  margin-bottom: 0.75rem;
  display: block;
}
.sx-dropzone-title {
  font-weight: 700;
  color: var(--sx-text-heading);
  font-size: 0.95rem;
  margin-bottom: 0.25rem;
}
.sx-dropzone-sub { font-size: 0.8rem; color: var(--sx-text-muted); }

/* ======================================================
   AUTH PAGES
   ====================================================== */
.sx-auth-page {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.sx-auth-brand {
  background: var(--gradient-dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 3rem;
  position: relative;
  overflow: hidden;
}
/* Pauta de cuaderno: renglones horizontales y el margen vertical rojo, el
   patrón que cualquiera reconoce de una libreta escolar. Sustituye a los dos
   círculos decorativos que había antes, que no decían nada del producto. */
.sx-auth-brand::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    /* Perforaciones de carpeta: tres, a la izquierda del margen. Van primero
       en la pila para quedar por encima de los renglones. El aro blanco de
       1.5px es el borde del troquel; sin él parecen manchas. */
    radial-gradient(circle at 42px 22%, rgba(255,255,255,0.10) 0 8px, rgba(255,255,255,0.75) 8px 9.5px, transparent 9.5px),
    radial-gradient(circle at 42px 50%, rgba(255,255,255,0.10) 0 8px, rgba(255,255,255,0.75) 8px 9.5px, transparent 9.5px),
    radial-gradient(circle at 42px 78%, rgba(255,255,255,0.10) 0 8px, rgba(255,255,255,0.75) 8px 9.5px, transparent 9.5px),
    repeating-linear-gradient(180deg,
      transparent 0 37px,
      rgba(255,255,255,0.055) 37px 38px),
    linear-gradient(90deg,
      transparent 0 84px,
      rgba(var(--sx-primary-solid-rgb),0.30) 84px 86px,
      transparent 86px);
  pointer-events: none;
}
/* Velo diagonal: aparta la pauta del centro para que el texto no compita con
   los renglones, y da profundidad sin volver a meter formas geométricas. */
.sx-auth-brand::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg,
    rgba(0,0,0,0.30) 0%,
    rgba(0,0,0,0.05) 45%,
    rgba(var(--sx-primary-solid-rgb),0.12) 100%);
  pointer-events: none;
}
.sx-auth-form {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background: var(--sx-body-bg);
}
.sx-auth-form-inner { width: 100%; max-width: 420px; }

/* Password strength */
.sx-password-strength {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--sx-border-color);
  overflow: hidden;
  margin-top: 0.5rem;
}
.sx-password-strength-bar {
  height: 100%;
  border-radius: var(--radius-pill);
  transition: width 0.4s ease, background 0.4s ease;
}

@media (max-width: 767.98px) {
  .sx-auth-page { grid-template-columns: 1fr; }
  .sx-auth-brand { display: none; }
}

/* ======================================================
   ICONS PAGE
   ====================================================== */
.sx-icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 0.75rem; }
.sx-icon-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 1rem 0.5rem;
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  cursor: pointer;
  transition: var(--transition-fast);
  background: var(--sx-card-bg);
}
.sx-icon-item:hover { border-color: var(--sx-primary-text); background: var(--sx-primary-light); color: var(--sx-primary-text); }
.sx-icon-item .bi { font-size: 1.5rem; }
.sx-icon-item span { font-size: 0.65rem; color: var(--sx-text-muted); text-align: center; word-break: break-all; }
.sx-icon-item:hover span { color: var(--sx-primary-text); }

/* ======================================================
   SECTION TITLES (en páginas de componentes)
   ====================================================== */
.sx-section-title {
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--sx-text-heading);
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--sx-border-color);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.sx-section-title .bi { color: var(--sx-primary-text); }

/* ======================================================
   DARK MODE OVERRIDES
   ====================================================== */
[data-theme="dark"] .card,
[data-theme="dark"] .list-group-item,
[data-theme="dark"] .modal-content,
[data-theme="dark"] .dropdown-menu,
[data-theme="dark"] .toast,
[data-theme="dark"] .popover,
[data-theme="dark"] .accordion-button,
[data-theme="dark"] .accordion-item,
[data-theme="dark"] .page-link { background: var(--sx-card-bg); border-color: var(--sx-border-color); }

[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select { background: var(--sx-dark-2); border-color: var(--sx-border-color); color: var(--sx-text-body); }

[data-theme="dark"] .input-group .input-group-text { background: var(--sx-card-bg); }

[data-theme="dark"] .table thead th { background: var(--sx-dark-2);
  color: var(--sx-text-body);
}
[data-theme="dark"] .sx-dropzone { background: var(--sx-dark-2); }
[data-theme="dark"] .sx-auth-form { background: var(--sx-dark-2); }

/* ======================================================
   INPUT CON ÍCONO INTERNO
   ====================================================== */
.sx-input-icon {
  position: relative;
}
.sx-input-icon .form-control {
  padding-left: 2.6rem;
}
.sx-input-icon .form-control.icon-right {
  padding-left: 0.85rem;
  padding-right: 2.6rem;
}
.sx-input-icon .form-control.icon-both {
  padding-left: 2.6rem;
  padding-right: 2.6rem;
}
.sx-input-icon .sx-icon-left,
.sx-input-icon .sx-icon-right {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: var(--sx-text-muted);
  font-size: 1rem;
  pointer-events: none;
  z-index: 5;
  line-height: 1;
  transition: color var(--transition-fast);
}
.sx-input-icon .sx-icon-left  { left: 0.8rem; }
.sx-input-icon .sx-icon-right { right: 0.8rem; pointer-events: auto; cursor: pointer; }

/* ======================================================
   DATATABLES — toolbar, tabla y paginación (tema unificado)
   ====================================================== */
.dataTables_wrapper { font-family: var(--font-body); }
/* Sin margen en la tabla (global, para todas las DataTables). */
table.dataTable { margin: 0 !important; }

/* Indicador de carga (processing): el spinner de 4 puntos viene de la
   integración bootstrap5 de DataTables en azul Bootstrap por defecto —
   recoloreado a la marca. El texto "Procesando..." se vacía en sx-datatables.js
   (language.processing = ''), así que solo se ven los puntos. */
div.dataTables_processing > div:last-child > div {
  background: var(--sx-primary) !important;
}

/*
 * Sin margen lateral tampoco. Las filas de controles de DataTables (buscador,
 * "mostrar N", info y paginación) son .row de Bootstrap, que trae margen
 * izquierdo/derecho NEGATIVO (-.5 * gutter = -.75rem) y el padding equivalente
 * en sus columnas. Resultado: sobresalían 12px por cada lado respecto de la
 * tabla, que va a ras del card.
 *
 * Se anula la gutter en vez del margen: poniendo margin:0 a secas quedaría el
 * padding de las columnas y los controles se irían 12px hacia DENTRO — el mismo
 * desalineo al revés. Con la gutter a 0 caen los dos a la vez.
 */
.dataTables_wrapper > .row { --bs-gutter-x: 0; }

/* Fila de controles superior (longitud + búsqueda): separada de la tabla */
.dataTables_wrapper > .row:first-child {
  align-items: center;
  padding-bottom: 0.9rem;
  margin-bottom: 0;
  border-bottom: 1px solid var(--sx-border-color);
}
.dataTables_length label,
.dataTables_filter label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin-bottom: 0;
  white-space: nowrap;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--sx-text-muted);
}
.dataTables_filter label { justify-content: flex-end; }
.dataTables_filter input { flex: 0 1 240px;
  background: var(--sx-card-bg) !important;
  border: 1.5px solid var(--sx-border-color) !important;
  border-radius: var(--radius) !important;
  box-shadow: none !important;
  color: var(--sx-text-body) !important;
  font-family: var(--font-body) !important;
  font-size: 0.8rem !important;
  height: calc(1.5em + 0.75rem + 3px) !important;
  padding: 0.375rem 0.75rem !important;
}

.dataTables_length select,
.dataTables_filter input {
  border: 1.5px solid var(--sx-border-color) !important;
  border-radius: var(--radius) !important;
  padding: 0.375rem 0.75rem !important;
  font-family: var(--font-body) !important;
  font-size: 0.8rem !important;
  color: var(--sx-text-body) !important;
  background-color: var(--sx-card-bg) !important;
  box-shadow: none !important;
  outline: none !important;
  transition: var(--transition-fast);
  background: var(--sx-card-bg) !important;
  height: calc(1.5em + 0.75rem + 3px) !important;
}
.dataTables_length select:focus,
.dataTables_filter input:focus {
  border-color: var(--sx-primary-text) !important;
  box-shadow: none !important;
  outline: none !important;
}

@media (max-width: 575.98px) {
  .dataTables_filter label { justify-content: flex-start; }
  .dataTables_filter input { flex: 1 1 auto; }
}

/* Fila inferior (info + paginación): separada de la tabla, alineada */
.dataTables_wrapper > .row:last-child {
  align-items: center;
  padding-top: 0.9rem;
  margin-top: 0;
  border-top: 1px solid var(--sx-border-color);
}
.dataTables_info {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--sx-text-muted);
  padding-top: 0 !important;
}
.dataTables_paginate { display: flex; justify-content: flex-end; }
.dataTables_paginate .pagination { margin-bottom: 0; flex-wrap: wrap; }

/* Botones de exportación (dt-buttons) */
.dt-buttons .btn {
  font-size: 0.8rem !important;
  padding: 0.4rem 0.875rem !important;
  border-radius: var(--radius-sm) !important;
  font-weight: 700 !important;
  margin-right: 0.35rem !important;
}

/* Paginación compacta */
.dataTables_paginate .paginate_button {
  font-size: 0.8rem !important;
  border-radius: var(--radius-sm) !important;
  background: var(--sx-card-bg) !important;
  color: var(--sx-text-body) !important;
  margin: 0 0.1rem !important;
  transition: var(--transition-fast) !important;
  cursor: pointer;
}
/* Default: texto normal (sin !important para que los estados específicos ganen).
   min-width porque Anterior/Siguiente son chevrones (ver preInit.dt en
   sx-datatables.js) y sin él quedarían mucho más estrechos que las cifras; el
   rótulo va en .visually-hidden y no ocupa. */
.dataTables_paginate .paginate_button .page-link {
  color: var(--sx-text-body);
  min-width: 34px;
  text-align: center;
}
/* Activo: fondo primario, texto blanco.
   .current Y .active: el renderer clásico de DataTables marca la página con
   .current y la integración bootstrap5 con .active. Contemplar solo uno dejaba
   la página activa sin color en la mitad de las tablas — y peor, la regla de
   hover de abajo la alcanzaba y se la borraba al pasar el ratón. */
.dataTables_paginate .paginate_button.current,
.dataTables_paginate .paginate_button.active,
.dataTables_paginate .paginate_button.current:hover,
.dataTables_paginate .paginate_button.active:hover {
  background: var(--sx-primary-solid) !important;
  border-color: var(--sx-primary-solid) !important;
  color: #fff !important;
}
.dataTables_paginate .paginate_button.current .page-link,
.dataTables_paginate .paginate_button.active .page-link,
.dataTables_paginate .paginate_button.current:hover .page-link,
.dataTables_paginate .paginate_button.active:hover .page-link {
  color: #fff !important;
  /* También el fondo del <a>, no solo el del <li>: con .active lo pinta Bootstrap
     y basta con recolorearlo, pero con .current el <a> conserva su blanco y el
     número quedaba blanco sobre blanco. */
  background: var(--sx-primary-solid) !important;
  border-color: var(--sx-primary-solid) !important;
}
/* Hover (números y Anterior/Siguiente por igual): tinte claro + tinta, como la
   paginación de Bootstrap. El relleno macizo se confundía con la página activa.
   Antes Anterior/Siguiente ponían el texto en #fff sobre fondo blanco y el
   rótulo desaparecía al pasar el ratón. */
.dataTables_paginate .paginate_button:not(.current):not(.active):not(.disabled):hover {
  background: var(--sx-primary-light) !important;
  border-color: var(--sx-primary-text) !important;
  color: var(--sx-primary-text) !important;
}
.dataTables_paginate .paginate_button:not(.current):not(.active):not(.disabled):hover .page-link,
.dataTables_paginate .paginate_button:not(.current):not(.active):not(.disabled) .page-link:focus {
  color: var(--sx-primary-text) !important;
  background: var(--sx-primary-light) !important;
}
.dataTables_paginate .paginate_button.disabled,
.dataTables_paginate .paginate_button.disabled:hover {
  opacity: 0.4 !important;
  cursor: not-allowed !important;
  background: var(--sx-card-bg) !important;
  color: var(--sx-text-muted) !important;
}
.dataTables_paginate .paginate_button.disabled .page-link,
.dataTables_paginate .paginate_button.disabled:hover .page-link {
  color: var(--sx-text-muted) !important;
}
/* ======================================================
   KANBAN BOARD
   ====================================================== */
.sx-kanban-board {
  display: flex;
  gap: 1.25rem;
  padding-bottom: 1rem;
  align-items: flex-start;
  overflow-x: auto;
}
.sx-kanban-col {
  /* Reparten por igual el ancho completo del row. */
  flex: 1 1 0;
  min-width: 280px;
  background: var(--sx-body-bg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--sx-border-color);
  display: flex;
  flex-direction: column;
  max-width: 280px;
}
/* En pantallas estrechas vuelven a ancho fijo con scroll horizontal. */
@media (max-width: 768px) {
  .sx-kanban-board { overflow-x: auto; }
  .sx-kanban-col { flex: 0 0 260px; min-width: 260px; }
}
.sx-kanban-col__header {
  padding: 1rem 1rem 0.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--sx-border-color);
}
.sx-kanban-col__title {
  font-weight: 800;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sx-text-heading);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.sx-kanban-col__body {
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  min-height: 60px;
}
.sx-kanban-card {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  padding: 0.875rem;
  cursor: grab;
  transition: var(--transition-fast);
}
.sx-kanban-card:active { cursor: grabbing; opacity: 0.85; }
.sx-kanban-card__title { font-weight: 700; font-size: 0.875rem; color: var(--sx-text-heading); margin-bottom: 0.5rem; }
.sx-kanban-card__desc { font-size: 0.78rem; color: var(--sx-text-body); margin-bottom: 0.5rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.sx-kanban-card__meta { font-size: 0.75rem; color: var(--sx-text-muted); display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.sx-kanban-card.sortable-ghost { opacity: 0.4; background: var(--sx-primary-light); border: 2px dashed var(--sx-primary); }
/* Cabecera de tarjeta: título + acciones (editar/eliminar al hover) */
.sx-kanban-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.sx-kanban-card__top .sx-kanban-card__title { margin-bottom: 0.5rem; flex: 1; min-width: 0; }
.sx-kanban-card__actions { display: flex; gap: 0.15rem; opacity: 0; transition: var(--transition-fast); flex-shrink: 0; }
.sx-kanban-card:hover .sx-kanban-card__actions { opacity: 1; }
.sx-kanban-card__btn {
  border: none; background: none; padding: 0.1rem 0.25rem; line-height: 1;
  color: var(--sx-text-muted); border-radius: var(--radius-xs); cursor: pointer; font-size: 0.8rem;
}
.sx-kanban-card__btn:hover { color: var(--sx-primary-text); background: var(--sx-primary-light); }
.sx-kanban-card__btn .bi-trash:hover,
.sx-kanban-card__btn:hover .bi-trash { color: var(--sx-danger, #c0392b); }
/* Placeholder de columna vacía: aviso "sin tareas" */
.sx-kanban-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 1.5rem 0.75rem;
  border: 1px dashed var(--sx-border-color);
  border-radius: var(--radius);
  color: var(--sx-text-muted);
  font-size: 0.8rem;
  text-align: center;
}
.sx-kanban-empty .bi { font-size: 1.5rem; opacity: 0.55; }

/* ======================================================
   MODAL PERFIL — avatar con borde
   ====================================================== */
.sx-profile-avatar {
  width: 90px; height: 90px; border-radius: 50%;
  object-fit: cover; display: inline-flex; align-items: center; justify-content: center;
  border: 4px solid rgba(255,255,255,0.25);
  box-shadow: 0 0 0 3px var(--sx-primary);
  flex-shrink: 0;
}
.sx-profile-avatar--initials {
  background: var(--sx-primary-solid); color: #fff;
  font-size: 1.75rem; font-weight: 700; font-family: var(--font-display);
}

/* ======================================================
   PERMISSION OVERRIDES — matriz de permisos por módulo
   ====================================================== */
.po-matrix { max-height: 60vh; overflow-y: auto; }
/* .sx-att-card pone el card-body en padding 0 para que la tabla vaya a sangre,
   pero aquí ese mismo card-body lleva además los selectores y el aviso: esos sí
   necesitan margen propio o quedan pegados al borde del card. */
.po-controls { padding: 1rem; }
.po-note { margin: 0 1rem 1rem; }
/* El módulo es una fila-encabezado dentro de la MISMA tabla, no una caja
   aparte por módulo: así las columnas quedan alineadas de arriba abajo. */
.po-modhead td {
  background: var(--sx-body-bg);
  font-family: var(--font-display); font-weight: 800; font-size: .8rem;
  color: var(--sx-text-heading);
}

/* Tri-estado por fila: segmentado compacto de un clic. Sustituye al <select>
   con Select2 que había por permiso (284 widgets que se creaban y destruían en
   cada cambio de objetivo). El estado elegido se pinta sólido, así que va con
   los tonos -solid, que son los que aguantan texto blanco encima. */
.sx-seg--xs { padding: 2px; }
.sx-seg--xs button { font-size: .7rem; padding: .25rem .5rem; }
.sx-seg--xs button.on[data-state="on"]  { background: var(--sx-green-solid);  color: #fff; }
.sx-seg--xs button.on[data-state="off"] { background: var(--sx-danger-solid); color: #fff; }

/* ======================================================
   CHAT APP
   ====================================================== */
.sx-chat-layout {
  display: flex;
  height: calc(100vh - var(--topbar-height) - 3rem);
  gap: 0;
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--sx-card-bg);
}
.sx-chat-sidebar {
  width: 300px;
  flex-shrink: 0;
  border-right: 1px solid var(--sx-border-color);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sx-chat-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sx-chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.sx-chat-bubble {
  max-width: 68%;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-lg);
  font-size: 0.875rem;
  line-height: 1.5;
}
.sx-chat-bubble--in {
  background: var(--sx-body-bg);
  border: 1px solid var(--sx-border-color);
  color: var(--sx-text-body);
  align-self: flex-start;
  border-bottom-left-radius: var(--radius-xs);
}
.sx-chat-bubble--out {
  background: var(--sx-primary-solid);
  color: #fff;
  align-self: flex-end;
  border-bottom-right-radius: var(--radius-xs);
}
.sx-chat-input-area {
  border-top: 1px solid var(--sx-border-color);
  padding: 1rem;
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
}
.sx-chat-contact {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  cursor: pointer;
  transition: var(--transition-fast);
  border-bottom: 1px solid var(--sx-border-color);
}
.sx-chat-contact:hover, .sx-chat-contact.active { background: var(--sx-primary-light); }
.sx-chat-avatar {
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--sx-primary-solid);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 0.85rem;
  flex-shrink: 0;
  position: relative;
}
.sx-chat-avatar .sx-online {
  position: absolute; bottom: 1px; right: 1px;
  width: 10px; height: 10px;
  border-radius: 50%;
  border: 2px solid var(--sx-card-bg);
  background: var(--sx-success-solid);
}

/* ======================================================
   FILE MANAGER
   ====================================================== */
.sx-fm-layout {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}
.sx-fm-sidebar {
  width: 220px;
  flex-shrink: 0;
}
.sx-fm-main { flex: 1; }
.sx-fm-tree-item {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.45rem 0.75rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.875rem;
  color: var(--sx-text-body);
  transition: var(--transition-fast);
}
.sx-fm-tree-item:hover, .sx-fm-tree-item.active {
  background: var(--sx-primary-light);
  color: var(--sx-primary-text);
}
.sx-fm-file-card {
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  padding: 1rem;
  text-align: center;
  cursor: pointer;
  transition: var(--transition-fast);
  background: var(--sx-card-bg);
  position: relative;
}
.sx-fm-file-card:hover { border-color: var(--sx-primary-text); background: var(--sx-primary-light); }
.sx-fm-file-card .sx-fm-icon { font-size: 2.5rem; margin-bottom: 0.5rem; display: block; }
.sx-fm-file-card .sx-fm-name { font-size: 0.8rem; font-weight: 700; color: var(--sx-text-heading); word-break: break-all; }
.sx-fm-file-card .sx-fm-size { font-size: 0.72rem; color: var(--sx-text-muted); }

/* ======================================================
   PROFILE PAGES
   ====================================================== */
.sx-profile-header {
  background: var(--gradient-dark);
  border-radius: var(--radius-xl);
  padding: 2rem 2rem 0;
  margin-bottom: 0;
  position: relative;
  overflow: hidden;
}
.sx-profile-header::before {
  content: '';
  position: absolute; top: -60px; right: -60px;
  width: 220px; height: 220px; border-radius: 50%;
  background: rgba(236,119,75,0.12);
}
.sx-profile-avatar {
  width: 90px; height: 90px;
  border-radius: 50%;
  border: 4px solid rgba(255,255,255,0.25);
  object-fit: cover;
  flex-shrink: 0;
}
.sx-profile-avatar-initials {
  width: 90px; height: 90px;
  border-radius: 50%;
  border: 4px solid rgba(255,255,255,0.25);
  background: var(--sx-primary-solid);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: 2rem;
  flex-shrink: 0;
}
.sx-profile-name { font-family: var(--font-display); color: #fff; font-size: 1.75rem; margin-bottom: 0.25rem; }
.sx-profile-role { color: rgba(255,255,255,0.75); font-size: 0.875rem; }
.sx-profile-tabs { border-top: 1px solid rgba(255,255,255,0.12); margin-top: 1.5rem; }
.sx-profile-tabs .nav-link {
  color: rgba(255,255,255,0.6) !important;
  border: none !important;
  border-radius: 0 !important;
  padding: 0.75rem 1.25rem;
  font-weight: 700;
  font-size: 0.875rem;
}
.sx-profile-tabs .nav-link:hover { color: rgba(255,255,255,0.9) !important; background: transparent !important; }
.sx-profile-tabs .nav-link.active {
  color: #fff !important;
  background: transparent !important;
  border-bottom: 3px solid var(--sx-primary) !important;
}
.sx-profile-tabs .nav-link::after { display: none; }

/* ============================================================
   DASHBOARD (Super Admin) — charts + secciones interactivas
   ============================================================ */
.sx-dashboard-chart-card .card-body { min-height: 200px; }
.sx-dashboard-chart-card .card-title { color: var(--sx-text-heading); }
.sx-chart { width: 100%; }
.sx-chart .apexcharts-tooltip {
  background: var(--sx-card-bg) !important;
  border-color: var(--sx-border-color) !important;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  color: var(--sx-text-body);
}
.sx-chart .apexcharts-tooltip-title {
  background: var(--sx-body-bg) !important;
  border-color: var(--sx-border-color) !important;
  color: var(--sx-text-heading);
  font-weight: 700;
}
.sx-chart .apexcharts-tooltip-text-y-label,
.sx-chart .apexcharts-tooltip-text-y-value { color: var(--sx-text-body); }
.sx-chart .apexcharts-legend-text { color: var(--sx-text-muted) !important; }
.sx-dashboard-chart-card .alert { border-radius: 10px; }

/* ===========================================================================
   Estilos movidos desde public/portal/_header.php (regla: nada de <style> inline)
   NOTA: el selector original era un `body { ... }` DESNUDO. components.css se
   carga en TODA la app, así que se renombró a `.sx-portal-body` para no cambiar
   el fondo global. Requiere que <body> del portal lleve class="sx-portal-body".
   =========================================================================== */
.sx-portal-body { background: var(--sx-body-bg); }
.sx-portal-main { max-width: 1100px; margin: 0 auto; padding: 1.5rem 1rem 3rem; }
.sx-portal-card { background: var(--sx-card-bg); border: 1px solid var(--sx-border-color); border-radius: var(--radius-lg, .75rem); }

/* Contador del navbar del portal (avisos / mensajes sin leer). Sustituye al
   `badge rounded-pill bg-danger` de Bootstrap, que pintaba el rojo genérico. */
.sx-portal-badge {
  position: absolute;
  top: .15rem;
  right: -.15rem;
  min-width: 1.15rem;
  padding: .1rem .35rem;
  border-radius: 999px;
  background: var(--sx-danger);
  color: #fff;
  font-size: .68rem;
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
}

/* Cifras de la tira de asistencia del portal (presente / ausente / tardanza). */
.sx-portal-stat-absent { color: var(--sx-danger); }
.sx-portal-stat-late   { color: var(--sx-warning-dark); }

/* ===========================================================================
   TARJETA DE REPORTE (lanzador) — usada en app/reports.php.
   Enlace tipo tarjeta: círculo de icono (badge-soft-*), título + descripción y
   chevron a la derecha. Solo tokens sx; hover con realce de borde.
   =========================================================================== */
.sx-report-card {
  display: flex;
  align-items: center;
  gap: .85rem;
  height: 100%;
  padding: 1rem;
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg, .75rem);
  background: var(--sx-card-bg);
  color: var(--sx-text-body);
  text-decoration: none;
  font: inherit;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  transition: var(--transition, all .25s ease);
}
.sx-report-card:hover {
  border-color: rgba(var(--sx-primary-rgb), .55);
  transform: translateY(-2px);
  color: var(--sx-text-body);
}
.sx-report-card.active {
  border-color: var(--sx-primary-text);
}
.sx-report-card__icon {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm, .5rem);
  font-size: 1.35rem;
}
.sx-report-card__body { display: flex; flex-direction: column; min-width: 0; }
.sx-report-card__title { font-weight: 700; color: var(--sx-text-heading); line-height: 1.2; }
.sx-report-card__desc { font-size: .82rem; color: var(--sx-text-muted); margin-top: .15rem; }
.sx-report-card__go { margin-left: auto; color: var(--sx-text-muted); transition: var(--transition, all .25s ease); }
.sx-report-card:hover .sx-report-card__go { color: var(--sx-primary-text); transform: translateX(2px); }

/* ======================================================
   ASISTENCIA (attendance.php, view-class/day-attendance)
   ====================================================== */
.sx-att-table > tbody > tr > td { vertical-align: middle; padding-top: .8rem; padding-bottom: .8rem; }
.sx-att-clname { font-weight: 700; color: var(--sx-text-heading); line-height: 1.2; }
/* Nombre que además es enlace (admin/users → perfil): mismo aspecto que el
   texto plano y el subrayado solo al pasar por encima, para que la tabla no
   se llene de azul. */
a.sx-att-clname { text-decoration: none; }
a.sx-att-clname:hover, a.sx-att-clname:focus { color: var(--sx-primary); text-decoration: underline; }
.sx-att-rate { display: flex; align-items: center; gap: .5rem; min-width: 132px; }
.sx-att-rate .progress { flex: 1; height: 7px; }
.sx-att-rate__pct { font-family: var(--font-display); font-weight: 700; font-size: .8rem; min-width: 34px; text-align: right; }
.sx-att-chips { display: flex; gap: .3rem; flex-wrap: wrap; }
.sx-att-chips .badge { font-weight: 700; }
.sx-att-teacher { display: inline-flex; align-items: center; gap: .5rem; }
.sx-att-teacher__nm { font-weight: 600; color: var(--sx-text-body); }
/* Control segmentado (filtro por estado): pista clara + pill activo. */
.sx-seg { display: inline-flex; background: var(--sx-body-bg); border: 1px solid var(--sx-border-color); border-radius: var(--radius); padding: 3px; gap: 2px; }
.sx-seg button, .sx-seg .btn-check + label { font-family: var(--font-body); font-weight: 800; font-size: .78rem; line-height: 1; border: 0; background: transparent; color: var(--sx-text-muted); padding: .4rem .7rem; border-radius: calc(var(--radius) - 3px); cursor: pointer; display: inline-flex; align-items: center; gap: .35rem; transition: var(--transition-fast, all .15s ease); }
.sx-seg button:hover, .sx-seg .btn-check + label:hover { color: var(--sx-text-body); }
.sx-seg button.on, .sx-seg .btn-check:checked + label { background: var(--sx-card-bg); color: var(--sx-text-heading); box-shadow: 0 1px 3px rgba(0,0,0,.10); }
.sx-seg .btn-check:checked + .sx-seg__primary { background: var(--sx-primary-solid); color: #fff; }
.sx-seg .btn-check:checked + .sx-seg__sky { background: var(--sx-sky); color: #fff; }
.sx-seg .btn-check:checked + .sx-seg__purple { background: var(--sx-purple); color: #fff; }
.sx-seg .btn-check:checked + .sx-seg__green { background: var(--sx-green); color: #fff; }
.sx-seg__primary > i { color: var(--sx-primary-text); }
.sx-seg__sky > i { color: var(--sx-sky-dark); }
.sx-seg__purple > i { color: var(--sx-purple-dark); }
.sx-seg__green > i { color: var(--sx-green-dark); }
.sx-seg .btn-check:checked + label > i { color: inherit; }
/* Opción apagada. Las DOS variantes: el .btn-check+label y el <button> suelto.
   Solo estaba la primera, así que un <button> deshabilitado se veía idéntico a
   uno activo — se pulsaba y no pasaba nada, sin explicación. */
.sx-seg .btn-check:disabled + label,
.sx-seg button:disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }
.sx-seg__dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.sx-att-actions { display: flex; gap: .35rem; justify-content: center; }
/* Header: mismo color que las demás celdas (--sx-text-body), solo en negrita. */
.sx-att-table > thead > tr > th { font-weight: bold;}
/* Tabla a ras del card: sin padding del card-body y SIN márgenes en tabla/paginación/select/búsqueda. */
.sx-att-card > .card-body { padding: 0; }
.sx-att-card .table { margin-bottom: 0; }

/* Filas de controles de DataTables (arriba: length+search, abajo: info+paginación):
   alineadas con el TEXTO de las celdas y con aire vertical para no pegarse a los
   bordes del card. El 1rem no es arbitrario: es el mismo padding lateral que
   .table thead th / tbody td (ver arriba), así que buscador, paginación y primera
   columna caen todos en la misma línea vertical.
   Antes esto era .25rem y funcionaba de casualidad: la gutter de Bootstrap añadía
   .75rem a las columnas de la fila. Al anular la gutter (más arriba) para quitar el
   margen lateral, esos .75rem desaparecieron y los controles se descolgaron a 4px;
   ahora el valor dice lo que hace y no depende de la gutter. */
.sx-att-card .dataTables_wrapper > .row:first-child { padding: .85rem 1rem !important; margin: 0 !important; }
.sx-att-card .dataTables_wrapper > .row:last-child { padding: .85rem 1rem !important; margin: 0 !important; }
.sx-att-card .dataTables_length,
.sx-att-card .dataTables_filter,
.sx-att-card .dataTables_info,
.sx-att-card .dataTables_paginate,
.sx-att-card .pagination { margin: 0 !important; }
.sx-att-card .dataTables_length,
.sx-att-card .dataTables_filter,
.sx-att-card .dataTables_info,
.sx-att-card .dataTables_paginate { padding: 0 !important; }
.sx-att-card .sx-transcript-empty { padding: 2.5rem 1.1rem; }

/* Texto de ayuda sobre una tabla a ras. El card-body va a padding 0 para que la
   tabla pegue con los bordes, así que el hint tiene que poner el suyo o queda
   escrito contra el borde del card. Mismo .85rem 1rem de las filas de controles
   de DataTables: buscador, primera columna y este texto caen en la misma línea
   vertical. */
.sx-att-card .sx-att-hint { padding: .85rem 1rem 0; margin-bottom: 0; }
/* Un AVISO (.sx-note) sobre la tabla sí necesita separarse de ella: es una caja
   con su icono, no un renglón de texto, y pegado a la primera fila se lee como
   parte del encabezado. El texto de ayuda a secas se queda como estaba. */
.sx-att-card .sx-note.sx-att-hint { padding-bottom: .85rem; }

/* Pie del card a ras (p. ej. el botón Guardar bajo una tabla flush): separa del
   contenido con el borde del tema. No se usa la utilidad .border-top de Bootstrap
   porque pinta con --bs-border-color, que el tema no remapea — daría el gris
   genérico y no seguiría el modo oscuro. */
.sx-att-foot { border-top: 1px solid var(--sx-border-color); }

/* ======================================================
   FULLCALENDAR — barra de controles (Hoy / ‹ › / Mes·Semana·Día·Lista).
   Compartido a propósito: lo usan app/calendar y la pestaña Calendario de
   app/courses, y por defecto FullCalendar los pinta en su azul marino, que no
   es de la paleta. Va sobre `.fc` porque FullCalendar pone esa clase en el
   PROPIO elemento de montaje (no en un hijo) — sirve para los dos calendarios
   sin duplicar reglas ni acoplarse a un id.

   Se sobreescriben sus VARIABLES en vez de sus selectores: son las que usan
   todas sus reglas internas, así que no hace falta un solo !important.
   ====================================================== */
.fc {
  /* Fondo de la rejilla. FullCalendar trae --fc-page-bg-color:#fff fijo, así que
     en modo oscuro las celdas quedaban blancas. */
  --fc-page-bg-color: var(--sx-card-bg);
  --fc-neutral-bg-color: var(--sx-body-bg);
  --fc-neutral-text-color: var(--sx-text-muted);

  --fc-button-bg-color: var(--sx-card-bg);
  --fc-button-border-color: var(--sx-border-color);
  --fc-button-text-color: var(--sx-text-body);
  --fc-button-hover-bg-color: var(--sx-body-bg);
  --fc-button-hover-border-color: var(--sx-border-color);
  --fc-button-active-bg-color: var(--sx-primary-solid);
  --fc-button-active-border-color: var(--sx-primary-solid);
}
.fc .fc-button {
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: .8rem;
  font-weight: 700;
  padding: .35rem .75rem;
  box-shadow: none !important; /* su focus ring propio es azul de Bootstrap */
  /* SIN transition (ni var(--transition), que es `all .25s ease`). Estos botones
     sacan su color de custom properties encadenadas
     (--fc-button-bg-color → --sx-card-bg): con una transición sobre
     background-color, al cambiar a modo oscuro el botón se queda clavado en el
     color del tema anterior —medido: la variable ya valía #2d2c38 y seguía
     pintando blanco, incluso acotando la transición a las propiedades sueltas—.
     Sin ella, el cambio de tema entra al instante y el hover se siente igual. */
}
/* El texto: FullCalendar tiene UNA sola variable para los tres estados, así que
   el activo —fondo de marca— necesita su color aparte o quedaría gris sobre
   naranja. Vale el #fff literal: es texto sobre color, el caso que la guía
   permite. */
.fc .fc-button-primary:not(:disabled).fc-button-active,
.fc .fc-button-primary:not(:disabled):active { color: #fff; }
.fc .fc-button-primary:focus-visible {
  outline: 2px solid var(--sx-primary);
  outline-offset: 2px;
}
/* Grupo Mes·Semana·Día·Lista: se pegan entre sí, redondeando solo los extremos. */
.fc .fc-button-group > .fc-button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.fc .fc-button-group > .fc-button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* Mes·Semana·Día·Lista con el aspecto de .sx-seg (el segmentado de asistencia y
   del modal "Nuevo anuncio"): pastilla hundida y la vista activa en blanco por
   encima, en vez de cuatro botones naranjas pegados.
   Se hace por CSS y no cambiando el markup porque esa barra la genera
   FullCalendar. El grupo se identifica por el botón de Mes (:has), que solo
   tiene ESTE: el de prev/next/hoy se queda como está. */
.fc .fc-button-group:has(.fc-dayGridMonth-button) {
  background: var(--sx-body-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  padding: 3px;
  gap: 2px;
}
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-button,
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-button:not(:first-child),
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-button:not(:last-child) {
  background: transparent;
  border: 0;
  color: var(--sx-text-muted);
  font-weight: 800;
  font-size: .78rem;
  line-height: 1;
  padding: .4rem .7rem;
  /* -3px = el padding del contenedor, para que la pastilla activa encaje. */
  border-radius: calc(var(--radius) - 3px);
  /* FullCalendar solapa los botones 1px para que no se dupliquen los bordes;
     sin bordes ese -1px solo se come el gap. */
  margin-left: 0;
}
/* El hover solo insinúa la pastilla; el color del texto es el de cada vista. */
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-button:not(.fc-button-active):hover {
  background: rgba(var(--sx-dark-rgb), .05);
}
/* Un color por vista, como los tres tonos de .sx-seg en "Nuevo anuncio": en
   reposo el texto tiñe, y al activarse el color pasa al fondo con texto blanco
   (#fff literal: es texto sobre color, el caso que la guía permite).
   Marca para el mes (la vista de siempre), y luego se va cerrando el foco:
   semana, día, lista. */
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-dayGridMonth-button { color: var(--sx-primary-text); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-timeGridWeek-button { color: var(--sx-sky-dark); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-timeGridDay-button  { color: var(--sx-purple-dark); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-listWeek-button     { color: var(--sx-green-dark); }

/* Gana al `color:#fff` del activo general (ese es para el botón suelto de
   marca; aquí cada vista pone SU color de fondo). El box-shadow le da el
   relieve de pastilla sobre el fondo hundido del grupo. */
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-button-primary:not(:disabled).fc-button-active {
  color: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10) !important;
}
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-dayGridMonth-button.fc-button-active { background: var(--sx-primary-solid); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-timeGridWeek-button.fc-button-active { background: var(--sx-sky); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-timeGridDay-button.fc-button-active  { background: var(--sx-purple); }
.fc .fc-button-group:has(.fc-dayGridMonth-button) > .fc-listWeek-button.fc-button-active     { background: var(--sx-green); }

.fc .fc-toolbar-title { font-family: var(--font-display); font-size: 1.1rem; font-weight: 700; color: var(--sx-text-heading); }

/* ======================================================
   CALENDARIO — barra lateral (leyenda de colores + próximos).
   Compartida por app/calendar y la pestaña Calendario de app/courses. Estaba en
   el <style> inline de calendar.php, donde la pestaña no podía alcanzarla.
   ====================================================== */
.cal-legend { display: flex; flex-direction: column; gap: .4rem; }
.cal-legend-item { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.cal-legend-dot {
  width: .8rem; height: .8rem; flex: 0 0 auto;
  border-radius: var(--radius-circle);
  border: 2px solid var(--sx-card-bg);
  box-shadow: 0 0 0 1px var(--sx-border-color);
}
.cal-legend-txt {
  font-size: .75rem; color: var(--sx-text-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Panel vacío de la leyenda (no hay materias con actividad este mes).
   .cal-legend es un flex column pensado para una PILA de entradas alineadas a
   la izquierda; el panel vacío hereda esa alineación y queda arrinconado arriba.
   Aquí se centra en los dos ejes: justify-content lo baja al medio (eje
   principal = vertical en column) y align-items lo centra en horizontal. El
   min-height le da el alto que necesita para tener dónde centrarse — sin él,
   el card se encoge al tamaño del panel y "centrado" no significa nada.
   Alcanza también la pestaña Calendario de app/courses, que comparte la clase. */
.cal-legend:has(> .sx-transcript-empty) {
  justify-content: center;
  align-items: center;
  min-height: 9rem;
}
.cal-legend > .sx-transcript-empty {
  width: 100%;
  padding: .5rem .75rem;
  /* app/calendar reparte su leyenda en DOS columnas con
     `#calLegend { display: grid; grid-template-columns: repeat(2, …) }`
     (el <style> de la página; un id, así que gana a esta regla). Sin esto el
     panel vacío cae en la PRIMERA columna y queda pegado a la izquierda por
     mucho que el contenedor centre. Ocupando las dos columnas vuelve a estar en
     el medio. En la pestaña Calendario de app/courses el contenedor es flex y
     la declaración se ignora sola. */
  grid-column: 1 / -1;
}

/* Próximos: el avatar de fecha (.sx-avatar--date) + hora + título + el tipo.
   Mismo componente que la lista de teacher-profile. */
.cal-upcoming { display: flex; flex-direction: column; max-height: 30rem; overflow-y: auto; }
.cal-up-item {
  display: flex; align-items: center; gap: .75rem;
  width: 100%; text-align: left;
  background: none; border: 0;
  border-bottom: 1px solid var(--sx-border-color);
  padding: .7rem .9rem;
  transition: var(--transition);
}
.cal-up-item:last-child { border-bottom: 0; }
.cal-up-item:hover { background: var(--sx-row-hover); }
/* min-width:0 para que el texto largo se recorte en vez de estirar la fila. */
.cal-up-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; gap: 1px; }
.cal-up-hours { font-size: .7rem; color: var(--sx-text-muted); }
.cal-up-title {
  font-size: .82rem; font-weight: 600; color: var(--sx-text-heading);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-up-class {
  font-size: .68rem; color: var(--sx-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-up-kind { flex: 0 0 auto; font-size: .62rem; align-self: flex-start; }

/* ======================================================
   OJO PARA VER LA CONTRASEÑA — lo inyecta assets/js/sx-password-toggle.js
   dentro del .form-icon del campo. El icono de la IZQUIERDA (.form-icon > i)
   lleva pointer-events:none y z-index:5, asi que este va por encima y si
   recibe clics.
   ====================================================== */
.sx-pw-eye {
  position: absolute;
  top: 50%;
  right: 0.6rem;
  transform: translateY(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: var(--radius-circle);
  color: var(--sx-text-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: var(--transition-fast);
}
.sx-pw-eye:hover { color: var(--sx-primary-text); background: var(--sx-primary-light); }
.sx-pw-eye:focus { outline: none; color: var(--sx-primary-text); }
/* Hueco para que el texto de la clave no pase por debajo del ojo. */
.sx-pw-has-eye { padding-right: 2.6rem; }

/* ======================================================
   CRITERIOS DE CONTRASEÑA — lista en vivo bajo el campo
   Marcado: <ul data-sx-pw-criteria="idInput"><li data-rule="len">…
   Las clases las conmuta assets/js/sx-password-criteria.js.
   Usan en auth/change-password.php y auth/reset-password.php.
   ====================================================== */
.sx-pw-criteria {
  list-style: none;
  margin: .5rem 0 0;
  padding: .6rem .7rem;
  background: var(--sx-body-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  font-size: .72rem;
  /* Dos columnas: son 4 criterios cortos y en una sola fila vertical ocupaban
     media tarjeta. Grid y no columns/float para que las filas se alineen. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .15rem .7rem;
}
.sx-pw-criteria li {
  display: flex;
  align-items: center;
  gap: .35rem;
  /* El color por defecto es el de "no cumple": el estado neutro (is-pending)
     y el de cumplido (is-ok) lo sobreescriben abajo. */
  color: var(--sx-danger);
  line-height: 1.6;
}
.sx-pw-criteria li > i { font-size: .8rem; flex: 0 0 auto; }
/* Todavía sin escribir nada: gris, no rojo. Marcar en rojo lo que la persona
   aún no ha tenido ocasión de teclear es hostil. */
.sx-pw-criteria li.is-pending { color: var(--sx-text-muted); }
.sx-pw-criteria li.is-ok { color: var(--sx-success); }

/* ============================================================
   BOTÓN CIRCULAR DE SOLO ÍCONO (.sx-btn-circle)
   ============================================================
   Se combina con los .btn-* del repo (que ya están sobre --sx-*): la clase solo
   cambia la FORMA, nunca el color. Así `btn btn-primary sx-btn-circle` sigue
   siendo naranja de marca y respeta el branding por escuela.

   Cuadrado exacto vía aspect-ratio + ancho mínimo, en vez de padding calculado:
   el ícono no siempre mide lo mismo (bi-trash y bi-x-lg no son iguales de
   anchos) y con padding los botones salían ovalados de distinto tamaño.

   OJO: sin texto, el botón no tiene nombre accesible. Todo uso lleva
   aria-label + title — está en el markup de cada botón, no aquí, porque el
   texto es bilingüe y sale de $t. */
.sx-btn-circle {
  border-radius: var(--radius-circle);
  aspect-ratio: 1 / 1;
  min-width: 2.5rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.sx-btn-circle > i { margin: 0 !important; }

/* ============================================================
   AVATAR DE FECHA (.sx-avatar.sx-avatar--date)
   ============================================================
   El cuadrado redondeado con el día grande y el mes debajo:

     <span class="sx-avatar sx-avatar--date" style="background:<color>">
       <span class="sx-avatar__day">21</span>
       <span class="sx-avatar__mon">JUL</span>
     </span>

   Variante del .sx-avatar de siempre: hereda el anillo y la sombra, y solo
   cambia forma (cuadrado redondeado en vez de círculo), tamaño y disposición.
   Nació repetido a mano con seis estilos en línea en teacher-profile.php; está
   aquí para que el calendario y esa página no se separen con el tiempo.

   El COLOR lo pone quien lo usa (inline o por clase), porque cada pantalla lo
   tiñe por algo distinto: teacher-profile por hash del título, el calendario por
   materia. Lo que NO se hereda del .sx-avatar es el `color:#fff` fijo: sobre una
   materia amarilla (#f2c917 es real) el texto desaparece. Quien lo pinta decide
   también el color del texto por luminancia — ver readableText() en calendar.js. */
.sx-avatar--date {
  width: 48px;
  height: 48px;
  min-width: 48px;
  flex-direction: column;
  justify-content: center;
  border-radius: var(--radius);
  text-transform: none;
  gap: 1px;
}
.sx-avatar--date .sx-avatar__day { font-size: 1.05rem; font-weight: 700; line-height: 1; }
.sx-avatar--date .sx-avatar__mon { font-size: .65rem; font-weight: 700; text-transform: uppercase; line-height: 1; letter-spacing: .03em; }

/* ============================================================
   AVISO EN EL TOPBAR (impersonación / "actuar como admin de escuela")
   ============================================================
   Centrado ABSOLUTO respecto al topbar (`.sx-topbar` ya es position:fixed,
   sirve de contenedor) — así queda al 100% del centro real sin importar el
   ancho del logo/buscador a la izquierda ni de los íconos a la derecha, en
   vez de acomodarse al flujo flex normal (que antes lo dejaba descentrado y
   lo recortaba con ellipsis en anchos comunes). Para que nunca se solape con
   el buscador, `.sx-topbar--has-notice` (topbar.php) lo oculta mientras el
   aviso esté activo — el resto del ancho libre es de sobra para el aviso.
   Siempre visible mientras dure el modo (no se puede cerrar). El botón
   "volver" es SIEMPRE solo-ícono (ver topbar.php) para no sumar un ancho
   variable de texto. */
.sx-topbar__notice {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: min(90vw, 38rem);
  padding: 0.4rem 0.4rem 0.4rem 0.8rem;
  background: var(--sx-warning-light);
  border: 1px solid var(--sx-warning);
  border-radius: var(--radius-pill);
}
.sx-topbar__notice__icon { color: var(--sx-warning-dark); font-size: 1rem; flex-shrink: 0; }
.sx-topbar__notice__text {
  color: var(--sx-warning-dark);
  font-size: 0.8rem;
  min-width: 0;
}
.sx-topbar__notice .btn { flex-shrink: 0; }
.sx-topbar--has-notice .sx-topbar__search { display: none; }

/* ---------------------------------------------------------------------------
   AVISO FLOTANTE (.sx-notice-bar)
   ---------------------------------------------------------------------------
   Tarjeta fija abajo y centrada, con el mismo aire que el banner de cookies del
   sitio público (site/assets/css/styles.css → .cookie-banner): ícono, texto y
   acciones en una sola fila.
   Se usa para pendientes que NO se descartan (autoevaluación anual), por eso no
   lleva botón de cerrar. Va sobre el contenido pero DEBAJO de los toasts (1090)
   para no taparlos. */
.sx-notice-bar {
  position: fixed;
  left: 50%;
  bottom: 24px;
  z-index: 1050;
  width: min(760px, calc(100% - 32px));
  display: grid;
  grid-template-columns: 42px 1fr auto;
  align-items: center;
  gap: 18px;
  padding: 18px;
  transform: translateX(-50%);
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.sx-notice-bar__icon {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  font-size: 1.25rem;
  border-radius: 12px;
  color: var(--sx-warning-dark);
  background: var(--sx-warning-light);
}
.sx-notice-bar--danger .sx-notice-bar__icon {
  color: var(--sx-danger);
  background: var(--sx-danger-light);
}
.sx-notice-bar__title {
  font-weight: 700;
  color: var(--sx-text-heading);
  font-size: 0.92rem;
  line-height: 1.3;
}
.sx-notice-bar__text {
  margin: 0;
  color: var(--sx-text-body);
  font-size: 0.86rem;
  line-height: 1.55;
}
.sx-notice-bar__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
@media (max-width: 575.98px) {
  .sx-notice-bar {
    grid-template-columns: 36px 1fr;
    bottom: 16px;
    gap: 12px;
    padding: 15px;
  }
  .sx-notice-bar__icon { width: 36px; height: 36px; font-size: 1.05rem; }
  .sx-notice-bar__actions { grid-column: 1 / -1; }
  .sx-notice-bar__actions .btn { width: 100%; justify-content: center; }
}

/* ---------------------------------------------------------------------------
   PILA DE AVISOS FLOTANTES (.sx-notice-stack)
   ---------------------------------------------------------------------------
   Para cuando hay MÁS DE UNO a la vez (anuncios fijados: pueden ser varios).
   El contenedor toma la posición fija y las tarjetas pasan a fluir dentro; sin
   esto cada .sx-notice-bar mantiene su position:fixed y las tres se pintan
   superpuestas en bottom:24px. Mismo ancho, z-index y separación del borde que
   una tarjeta suelta, para que no se note la diferencia con la autoevaluación. */
.sx-notice-stack {
  position: fixed;
  left: 50%;
  bottom: 24px;
  z-index: 1050;
  width: min(760px, calc(100% - 32px));
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.sx-notice-stack .sx-notice-bar {
  position: static;
  left: auto;
  bottom: auto;
  width: 100%;
  transform: none;
}
@media (max-width: 575.98px) {
  .sx-notice-stack { bottom: 16px; }
  /* En móvil .sx-notice-bar baja a 2 columnas y la X caería a una fila propia,
     debajo del ícono. Aquí SÍ hay tercera columna porque el botón es estrecho.
     Va acotado a .sx-notice-stack para no tocar el aviso de autoevaluación, que
     usa la misma tarjeta pero con un botón ancho que sí debe bajar de fila. */
  .sx-notice-stack .sx-notice-bar { grid-template-columns: 36px 1fr auto; }
}

/* Puntuar con rúbrica (app/rubric-grading): la lista de tareas funciona como un
   collapse — cada tarjeta abre debajo la rejilla de puntuaciones. La abierta se
   marca con el color de marca para que se vea de cuál es lo que hay debajo. */
#rgTaskList .js-rg-task { cursor: pointer; }
#rgTaskList .js-rg-task.js-rg-open { border-color: var(--sx-primary-text); }
/* El bloque desplegado es la parte de abajo de ESA tarjeta: línea de separación
   y el mismo padding lateral que .rg-top (rubric-grading.css), para que la
   tabla no toque los bordes del card. */
#rgTaskList .js-rg-task > #rgGradeBox {
  border-top: 1px solid var(--sx-border-color);
  padding: 1rem 1.25rem;
  cursor: default;
}

/* ======================================================================
   COLUMNA LATERAL QUE ACOMPAÑA AL SCROLL (.sx-sticky-aside)
   Para páginas de captura larga donde la col-3 lleva los datos y los
   botones (guardar/imprimir) y el col-9 hace scroll: el card COMPLETO
   (header + cuerpo) sigue al usuario. Solo en ≥xl, que es donde de
   verdad es columna lateral; debajo vuelve a apilarse normal.
   align-self evita que el flex item se estire; si el card es más alto
   que la pantalla, el header queda fijo y el cuerpo hace scroll interno
   (así ningún botón queda inalcanzable).
   ponytail: .eval-fill-aside (evaluations.css) es esta misma regla con
   otro nombre — al tocar esa página, cámbiala por esta y bórrala allá.
   ====================================================================== */
@media (min-width: 1200px) {
  .sx-sticky-aside {
    position: sticky;
    top: 1rem;
    align-self: flex-start;
    max-height: calc(100vh - 2rem);
  }
  .sx-sticky-aside > .card {
    display: flex;
    flex-direction: column;
    max-height: 100%;
  }
  .sx-sticky-aside > .card > .card-body { overflow-y: auto; }
}

/* ======================================================================
   LISTA MAESTRA DE OPCIONES (.sx-tpl-pick)
   Botón plano de una lista "elige uno a la izquierda, edítalo a la
   derecha": constructor de formatos (admin/planning) y plantillas de la
   tarjeta por indicadores (admin/skill-reports). Vivía duplicada en el
   <style> de las dos páginas; una sola definición aquí.
   Sin clases de list-group (arrastraban su propio padding y su borde) y
   sin separadores: cada opción es una PASTILLA redondeada, y la elegida
   se marca con fondo + color de marca.
   ====================================================================== */
.sx-tpl-pick {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  padding: .6rem .75rem;
  margin-bottom: .35rem;
  transition: var(--transition);
}
.sx-tpl-pick:last-child { margin-bottom: 0; }
.sx-tpl-pick:hover { background: var(--sx-body-bg); }
.sx-tpl-pick.active {
  background: var(--sx-body-bg);
  border-color: var(--sx-primary-text);
  color: var(--sx-primary-text);
}
.sx-tpl-pick.active .fw-semibold { color: var(--sx-primary-text); }

/* ======================================================================
   CONSTRUCTOR (bloques y filas de un editor de estructura)
   Lo comparten el constructor de formatos de plan (admin/planning) y el
   editor de tarjetas por indicadores (admin/skill-reports): un bloque por
   sección, filas dentro. Estaba duplicado en el <style> de las dos
   páginas y se estaban separando solo. Una definición, y los dos nombres
   de clase históricos apuntan a ella.
   ponytail: cuando una de las dos pantallas se vuelva a tocar, renombrar
   sus clases a .sx-editor-* y quitar el alias.
   ====================================================================== */
.sx-skr-block,
.sx-tpl-sec {
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-sm);
  padding: .6rem .75rem;
  margin-bottom: .5rem;
  background: var(--sx-card-bg);
}
/* La sección incluida NO se tiñe. El fondo de color con borde de marca era
   propio de esta pantalla y de ninguna otra; el sistema marca estado con
   badge-soft-*, no pintando la caja. Además el dato ya se dice dos veces —
   la sección incluida vive en el fieldset "Secciones de este formato" y lleva
   su casilla marcada — así que el tinte era el tercer aviso de lo mismo.
   El estado va en su badge, como en el resto (ver plan-templates.js). */

.sx-skr-row,
.sx-tpl-item {
  padding: .25rem;
  border-radius: var(--radius-sm);
  margin: 0;
}
.sx-skr-row:hover,
.sx-tpl-item:hover { background: var(--sx-body-bg); }

/* Asa de arrastre (SortableJS). */
.sx-skr-grip,
.sx-tpl-grip { cursor: grab; color: var(--sx-text-muted); }
.sx-skr-grip:active,
.sx-tpl-grip:active { cursor: grabbing; }
.sx-skr-block.sortable-ghost,
.sx-skr-row.sortable-ghost,
.sx-tpl-sec.sortable-ghost,
.sx-tpl-item.sortable-ghost {
  opacity: .5;
  background: var(--sx-primary-light);
  border: 1px dashed var(--sx-primary);
}
.sortable-chosen { box-shadow: var(--shadow-sm); }

/* Variante compacta de .form-icon: con controles .form-control-sm el ícono en
   su posición normal (.9rem + padding 2.5rem) no deja ver ni un dígito.
   OJO CON LA ESPECIFICIDAD: pages.css carga DESPUÉS de este archivo y define
   .form-icon > i / .form-control-icon / el Select2 dentro de .form-icon. Por
   eso cada regla se escribe con las DOS clases (.form-icon.form-icon-sm): con
   una sola, pages.css gana por orden y el ícono se queda en .9rem encima del
   texto, que es justo lo que se veía. */
.form-icon.form-icon-sm > i { left: 0.6rem; font-size: 0.85rem; }
.form-icon.form-icon-sm .form-control-icon,
.form-icon.form-icon-sm .form-select { padding-left: 1.85rem; }
.form-icon.form-icon-sm .select2-container--default .select2-selection--single .select2-selection__rendered,
.form-icon.form-icon-sm .select2-container--default .select2-selection--multiple {
  padding-left: 1.85rem !important;
}

/* Campo numérico corto dentro de una celda (p. ej. "encuentros por semana" del
   constructor de horarios): un contador de 0 a 15 no necesita el ancho entero
   de la columna, y estirado parece que admite cualquier cosa. */
.sx-input-narrow { max-width: 6.5rem; }

/* ===========================================================================
   INFORME DE PROGRESO — barras de avance (portal-progress.js)
   ===========================================================================
   El informe era una tabla de números: para saber cómo iba el estudiante había
   que leer clase por clase y sumar a ojo. Estas barras dan la lectura de un
   vistazo — verde va bien, ámbar hay que mirarlo, rojo hace falta ayuda — con
   los cortes de siempre (70 es el mínimo de aprobación).

   El color NUNCA va solo: al lado siempre hay el porcentaje en texto, porque
   quien no distingue verde de rojo tiene que poder leer el informe igual.
   =========================================================================== */
.sx-pp-bar {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--sx-border-color);
  overflow: hidden;
}

.sx-pp-bar__fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  transition: width 0.25s ease;
}

.sx-pp-bar__fill--success   { background: var(--sx-green); }
/* Una banda por letra (A/B/C/D/F), igual que los badges del informe: sin la de
   info y la de primary, un 85 y un 65 pintaban de amarillo los dos. */
.sx-pp-bar__fill--info      { background: var(--sx-sky); }
.sx-pp-bar__fill--warning   { background: var(--sx-yellow); }
.sx-pp-bar__fill--primary   { background: var(--sx-primary); }
.sx-pp-bar__fill--danger    { background: var(--sx-red); }
.sx-pp-bar__fill--secondary { background: var(--sx-text-muted); }

/* Promedio general, en grande: es el primer dato que busca la familia. */
.sx-pp-overall {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
}

.sx-pp-overall--success   { color: var(--sx-green-dark); }
.sx-pp-overall--warning   { color: var(--sx-yellow-dark); }
.sx-pp-overall--danger    { color: var(--sx-red-dark); }
.sx-pp-overall--secondary { color: var(--sx-text-muted); }

/* Trabajo aún sin calificar: se atenúa para que no compita con lo que ya tiene
   nota, pero NO se esconde — que esté pendiente es parte de la foto. */
.sx-pp-pending td { opacity: .65; }

/* Periodo HIJO en la lista de publicación del boletín (una semana dentro de su
   semestre): sangría + la línea del árbol, para que se lea como parte del
   semestre de arriba y no como un periodo suelto más. Tokens, así que el modo
   oscuro sale solo. */
.sx-term-child {
  margin-left: 1.75rem;
  position: relative;
}
.sx-term-child::before {
  content: '';
  position: absolute;
  left: -1rem;
  top: 50%;
  width: .75rem;
  height: 2px;
  background: var(--sx-border-color);
}
/* El tramo vertical baja desde el renglón anterior hasta el centro de este; en
   el último hijo se corta ahí mismo, que es lo que dibuja la esquina. */
.sx-term-child::after {
  content: '';
  position: absolute;
  left: -1rem;
  top: -.35rem;
  bottom: 50%;
  width: 2px;
  background: var(--sx-border-color);
}

/* ===========================================================================
   IMPRESIÓN (genérico)

   Este bloque lo cargan LAS DOS capas — /app y el portal de familia —, a
   diferencia de layout.css, que solo lo carga /app. Aquí va lo que no depende
   del armazón: la marca `.no-print` y el calendario.

   `.no-print` es la marca para cualquier control que se mira pero no se
   imprime (botones de imprimir, filtros, enlaces). Antes la definía a mano
   cada página suelta, con lo que cada una podía olvidarse.
   =========================================================================== */
@media print {
  .no-print { display: none !important; }

  /* CALENDARIO. Los eventos llevan su color de materia como estilo en línea
     (calendar.js, eventDidMount), y TODOS los navegadores tiran los fondos al
     imprimir salvo que se les diga lo contrario: sin esta regla el calendario
     sale en blanco y negro y no se distingue una clase de otra — que es justo
     para lo que se imprime. */
  .fc, .fc * {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }

  /* Los botones de navegación (prev/next/hoy/vistas) no pintan nada en papel;
     el título del mes sí, que es lo que dice de qué calendario es la hoja. */
  .fc .fc-toolbar-chunk:first-child,
  .fc .fc-toolbar-chunk:last-child { display: none !important; }
  .fc .fc-toolbar-title { font-size: 1.1rem; }

  /* Una semana no se parte entre dos hojas. */
  .fc .fc-daygrid-body tr { break-inside: avoid; page-break-inside: avoid; }

  /* En pantalla la celda tiene 140px fijos (pages.css) para que el mes se vea
     aireado; en papel eso empuja el mes a una segunda hoja. */
  .fc .fc-scrollgrid td { height: auto !important; }

  /* Armazón del PORTAL de familia (el de /app lo esconde layout.css): barra de
     navegación pegajosa y pie no pertenecen a la hoja. */
  .sx-portal-body > .navbar,
  .sx-portal-body > footer { display: none !important; }
  .sx-portal-main { padding: 0 !important; }
  .sx-portal-card { border: 0 !important; box-shadow: none !important; padding: 0 !important; }
}

/* Matriz de avisos (app/notices): una línea marca dónde empieza cada aviso.
   El nombre va en una celda con rowspan sobre sus filas de roles, así que sin
   esta línea dos avisos seguidos se leen como un solo bloque.
   El :first-child evita la raya suelta encima de la primera fila, que quedaría
   pegada al encabezado. */
.sx-att-table > tbody > tr.sx-ev-first > td { border-top: 2px solid var(--sx-border-color); }
.sx-att-table > tbody > tr.sx-ev-first:first-child > td { border-top: 0; }
/* Con rowspan, el hover de fila pinta media celda del nombre y se ve sucio. */
.sx-att-table > tbody > tr:hover > td[rowspan] { background: transparent; }

/* ===========================================================================
   Horario semanal (sx-schedule.js) — la rejilla que ven el estudiante, su
   tutor y el personal. Una sola hoja de estilos porque es un solo renderer.
   =========================================================================== */

/* LA REJILLA ES UN EJE DE TIEMPO. Cabecera y cuerpo son dos CSS grid con las
   MISMAS columnas (la hora + un día o un grupo por columna, todas iguales);
   el cuerpo tiene UNA FILA POR MINUTO (--sx-sched-min de alto, la pone el JS)
   y cada bloque ocupa de la fila de su inicio a la de su fin. Así una clase de
   90 minutos mide casi el doble que una de 50 y una que no encaja en la campana
   se ve donde cae. Con una tabla de filas por tramo las dos medían igual. */
.sx-sched-tl {
  --sx-sched-min: 2px;
  --sx-sched-cols: 5;
  --sx-sched-hour-w: 92px;
}
.sx-sched-tl-head,
.sx-sched-tl-body {
  display: grid;
  grid-template-columns: var(--sx-sched-hour-w) repeat(var(--sx-sched-cols), minmax(0, 1fr));
}
.sx-sched-tl-th {
  background: var(--sx-body-bg);
  color: var(--sx-text-muted);
  font-size: .72rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 700;
  text-align: center;
  padding: .75rem .5rem;
  border-bottom: 1px solid var(--sx-border-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sx-sched-tl-th:first-child { text-align: right; padding-right: .75rem; }
/* Aire arriba y abajo: la etiqueta de la primera hora va ENCIMA de su línea y
   necesita sitio; la última (3:00 pm) quedaba pegada al borde de la tarjeta. */
.sx-sched-tl-body {
  position: relative;
  padding-top: 2.25rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--sx-border-color);
}
/* El fondo de un día: rayado en diagonal, que es lo que en la leyenda
   significa "bloque libre". Lo que ninguna clase tapa está libre; en gris liso
   se confundía con una celda que no cargó. */
.sx-sched-tl-col {
  /* Liso, como el resto de la app: el fondo de página y el borde de siempre.
     Lo que ningún bloque tapa está libre; el rayado de antes chocaba con el
     resto de las tarjetas. */
  border-left: 1px solid var(--sx-border-color);
  background: var(--sx-body-bg);
}
/* La línea de la hora en punto cruza la semana entera; es la referencia con
   la que se lee la altura de cada bloque. No estorba al ratón. */
.sx-sched-tl-line {
  grid-column: 1 / -1;
  align-self: start;
  height: 0;
  border-top: 1px dashed var(--sx-border-color);
  pointer-events: none;
  z-index: 1;
}
.sx-sched-tl-line--end { align-self: end; }
/* La media hora: la misma línea, más tenue. */
.sx-sched-tl-line--half { opacity: .55; }
.sx-sched-tl-hour {
  grid-column: 1;
  align-self: start;
  /* ENCIMA de su línea, no centrada sobre ella: la hora marca dónde empieza. */
  transform: translateY(calc(-100% - 2px));
  height: 1.1rem;
  line-height: 1.1rem;
  padding-right: .75rem;
  text-align: right;
  font-size: .78rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--sx-text-muted);
  background: var(--sx-card-bg);
  white-space: nowrap;
}
.sx-sched-tl-hour--end { align-self: end; }

/* Los bloques dentro del eje: sin la altura fija de tarjeta, la altura es la
   de sus filas (sus minutos). El margen deja ver la línea de la hora entre dos
   clases pegadas. */
.sx-sched-tl .sx-sched-block,
.sx-sched-tl .sx-sched-break,
.sx-sched-tl .sx-sched-free {
  height: auto;
  min-height: 0;
  /* Aire entre bloques pegados: 3px arriba y abajo suman 6 entre dos clases
     seguidas, suficiente para que se lean como dos y no como una. */
  margin: 3px 6px;
  position: relative;
  z-index: 2;
}
/* El primer bloque del eje se despega de la línea de arriba y el último de la
   de abajo (el JS los marca): los demás se quedan con su margen normal. */
.sx-sched-tl .sx-sched-block--first,
.sx-sched-tl .sx-sched-free--first { margin-top: 10px; }
.sx-sched-tl .sx-sched-block--last,
.sx-sched-tl .sx-sched-free--last { margin-bottom: 10px; }
/* Sin franja de color ni sombra: la tarjeta es el tinte de la materia y el
   texto en su tinta, como los badges suaves del resto de la app. */
.sx-sched-tl .sx-sched-block { padding: .5rem .7rem; }
.sx-sched-tl .sx-sched-break { padding: .35rem .6rem; }
.sx-sched-tl .sx-sched-block > div { line-height: 18px; }
.sx-sched-tl .sx-sched-block > .fw-bold { font-size: .86rem; }
.sx-sched-tl .sx-sched-free { z-index: 1; }

.sx-sched-block {
  border-radius: var(--radius-sm, .5rem);
  padding: .55rem .65rem;
  overflow: hidden;
}
/* Con las columnas de ancho fijo, un salón largo ("Laboratorio de
   Computadoras") se partiría en dos líneas y desbordaría la tarjeta. Cada línea
   se corta con puntos suspensivos; el texto completo va en el title. */
.sx-sched-block > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* EXCEPCIÓN: el nombre de la clase. Es el dato que se busca en la celda, así que
   se parte en varias líneas y se lee entero en vez de cortarse con puntos
   suspensivos. `anywhere` para que un nombre sin espacios tampoco desborde. Las
   líneas secundarias (salón, hora) siguen en una sola línea. */
.sx-sched-block > .sx-sched-block__title {
  white-space: normal;
  overflow-wrap: anywhere;
  text-overflow: clip;
}
/* Maestro y salón: el gris `muted` sobre el tinte de la materia se quedaba en
   4.2-4.5, justo por debajo de AA. El gris de cuerpo pasa holgado en los dos
   modos y sigue leyéndose como línea secundaria frente al nombre en negrita. */
.sx-sched-block .text-muted { color: var(--sx-text-body); }
.sx-sched-block > div > span { overflow: hidden; text-overflow: ellipsis; }
/* La banda del receso NO es una tarjeta de clase: mismo eje, otro tono. */
.sx-sched-break {
  background: var(--sx-yellow-light);
  color: var(--sx-yellow-dark, #8a6d1f);
  border-radius: var(--radius-sm, .5rem);
  padding: .5rem;
  overflow: hidden;
  white-space: nowrap;
}
/* El hueco de campana en el CONSTRUCTOR: un tramo donde todavía no hay clase,
   y donde se puede soltar una. Punteado, para distinguirlo del fondo libre. */
.sx-sched-free {
  border-radius: var(--radius-sm, .5rem);
  border: 1px dashed var(--sx-border-color);
  background: var(--sx-card-bg);
  opacity: .85;
}

/* Arrastrar y soltar en el CONSTRUCTOR de horarios (schedule-builder.js). El
   bloque arrastrable se anuncia con el cursor, y la celda destino se resalta
   mientras el bloque está encima: sin eso no se sabe dónde va a caer. */
.sx-sched-block[draggable="true"] { cursor: grab; }
.sx-sched-block[draggable="true"]:active { cursor: grabbing; }
.sx-sched-cell { cursor: pointer; }
.sx-sched-cell--drop.sx-sched-block,
.sx-sched-cell--drop.sx-sched-free {
  outline: 2px dashed var(--sx-primary);
  outline-offset: -2px;
}
/* El bloque de clase lleva su color de materia en el `style`, que gana a
   cualquier regla: solo el hueco libre se puede teñir. */
.sx-sched-cell--drop.sx-sched-free { background: var(--sx-primary-light); opacity: 1; }
/* Un choque tiene que verse aunque otro bloque le caiga encima. */
.sx-sched-tl .sx-sched-block--conflict { z-index: 3; }

/* La leyenda ("Clases") a dos columnas. */
.sx-sched-legend {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .5rem .75rem;
}

.sx-sched-dot {
  width: 12px; height: 12px;
  border-radius: 3px;
  display: inline-block;
  flex: 0 0 auto;
}
.sx-sched-dot--free {
  background: transparent;
  border: 1px dashed var(--sx-border-color);
}

.sx-sched-ev-icon {
  width: 34px; height: 34px;
  border-radius: var(--radius-sm, .5rem);
  background: var(--sx-primary-light);
  color: var(--sx-primary-text);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}

/* En móvil la rejilla no cabe: se deja desplazar en horizontal con la columna
   de la hora fija, que es lo único que da contexto al mirar el jueves. */
@media (max-width: 767.98px) {
  .sx-sched-tl { min-width: 720px; }
  .sx-sched-tl-hour, .sx-sched-tl-th:first-child { position: sticky; left: 0; z-index: 4; }
}

/* ---------------------------------------------------------------------------
 * Acceso rápido (tarjeta lateral de las páginas de /app)
 *
 * Ficha por página hermana: franja de color a la izquierda, icono en disco
 * tintado y fondo suave. Antes era una pila de .list-group-item con borde, que
 * se confundía con las tablas y listados del contenido.
 *
 * El tono lo pone la POSICIÓN (--t0..--t4), no el nombre ni la ruta: cinco
 * familias que rotan, así que por muchos ítems que traiga el grupo nunca hay dos
 * fichas seguidas del mismo color — que es lo que pasaría con un hash. Se deja
 * fuera la familia roja a propósito: en un menú leería como "peligro".
 * --------------------------------------------------------------------------- */
.sx-quick { display: grid; gap: .45rem; }
.sx-quick__item {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .7rem;
  border: 0;
  border-left: 3px solid var(--sx-border-color);
  border-radius: var(--radius-sm);
  background: var(--sx-body-bg);
  color: var(--sx-text-body);
  font-size: .82rem;
  line-height: 1.25;
  text-decoration: none;
  transition: var(--transition-fast);
}
.sx-quick__item:hover { color: var(--sx-text-heading); }
.sx-quick__item.active { font-weight: 700; }
.sx-quick__icon {
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 auto;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--sx-card-bg);
  font-size: .9rem;
  line-height: 1;
}
/* La ficha de la página actual se tiñe entera con SU familia, y el disco se
   invierte (fondo de tarjeta) para no perderse dentro del tinte. */
.sx-quick__item--t0 { border-left-color: var(--sx-primary-text); }
.sx-quick__item--t0 .sx-quick__icon { color: var(--sx-primary-text); background: var(--sx-primary-light); }
.sx-quick__item--t0:hover,
.sx-quick__item--t0.active { background: var(--sx-primary-light); color: var(--sx-primary-text); }
.sx-quick__item--t1 { border-left-color: var(--sx-sky-text); }
.sx-quick__item--t1 .sx-quick__icon { color: var(--sx-sky-text); background: var(--sx-sky-light); }
.sx-quick__item--t1:hover,
.sx-quick__item--t1.active { background: var(--sx-sky-light); color: var(--sx-sky-text); }
.sx-quick__item--t2 { border-left-color: var(--sx-green-text); }
.sx-quick__item--t2 .sx-quick__icon { color: var(--sx-green-text); background: var(--sx-green-light); }
.sx-quick__item--t2:hover,
.sx-quick__item--t2.active { background: var(--sx-green-light); color: var(--sx-green-text); }
.sx-quick__item--t3 { border-left-color: var(--sx-purple-text); }
.sx-quick__item--t3 .sx-quick__icon { color: var(--sx-purple-text); background: var(--sx-purple-light); }
.sx-quick__item--t3:hover,
.sx-quick__item--t3.active { background: var(--sx-purple-light); color: var(--sx-purple-text); }
.sx-quick__item--t4 { border-left-color: var(--sx-yellow-text); }
.sx-quick__item--t4 .sx-quick__icon { color: var(--sx-yellow-text); background: var(--sx-yellow-light); }
.sx-quick__item--t4:hover,
.sx-quick__item--t4.active { background: var(--sx-yellow-light); color: var(--sx-yellow-text); }
.sx-quick__item:hover .sx-quick__icon,
.sx-quick__item.active .sx-quick__icon { background: var(--sx-card-bg); }

/* ---------------------------------------------------------------------------
 * Celdas compuestas de tabla (app/quizzes, app/lesson-plans)
 *
 * Cuando una tabla llega a seis u ocho columnas planas ninguna destaca. El
 * dato principal se queda solo en su celda y el contexto (sección, docente,
 * año) baja a una sublínea gris; las cifras viajan juntas como badges con su
 * unidad. Genérico a propósito: cualquier listado puede reusarlo.
 * --------------------------------------------------------------------------- */
.sx-cell-sub {
  font-size: .74rem;
  color: var(--sx-text-muted);
  margin-top: .1rem;
  display: flex;
  align-items: center;
  gap: .3rem;
}
.sx-cell-sub > i { font-size: .8rem; }
.sx-cell-chips { display: inline-flex; flex-wrap: wrap; gap: .25rem; }

/* Dónde cae HOY dentro de la ventana de disponibilidad. Lo decide el servidor
   (su reloj es el bueno) y aquí solo se le pone color por estado. */
.sx-cell-when { font-size: .74rem; margin-top: .1rem; font-weight: 600; }
.sx-cell-when--open   { color: var(--sx-green-text); }
.sx-cell-when--soon   { color: var(--sx-sky-text); }
.sx-cell-when--closed { color: var(--sx-text-muted); font-weight: 400; }

/* La asistencia por clase de app/my-attendance es una tabla .sx-att-table; lo
   suyo propio (fecha teñida, tira de días, barra del %) vive en my-attendance.css. */

/* El calendario usa el par tinte+tinta (badge-soft), NO relleno sólido con
   texto blanco: a 12.5px en negrita, blanco sobre el verde/amarillo base no
   llega a AA (2.2-2.9 de contraste, medido) — es el mismo motivo por el que
   la tira de "últimos días" usa el tono base como decoración SIN texto
   encima, y aquí sí lleva el número del día encima. */
.sx-att-cal__nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: .6rem; }
.sx-att-cal__nav-btn { border: 1px solid var(--sx-border-color); background: var(--sx-body-bg); color: var(--sx-text-body); }
.sx-att-cal__nav-btn:disabled { opacity: .4; }
.sx-att-cal__month { font-weight: 700; font-size: .88rem; color: var(--sx-text-heading); text-transform: capitalize; }
.sx-att-cal__dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .25rem;
  font-size: .7rem;
  color: var(--sx-text-muted);
  text-align: center;
  margin-bottom: .25rem;
}
.sx-att-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .25rem; }
.sx-att-cal__cell {
  text-align: center;
  font-size: .78rem;
  padding: .4rem 0;
  border-radius: var(--radius-sm);
  color: var(--sx-text-muted);
}
.sx-att-cal__cell--present { background: var(--sx-success-light); color: var(--sx-success-text); font-weight: 700; }
.sx-att-cal__cell--late    { background: var(--sx-warning-light); color: var(--sx-warning-text); font-weight: 700; }
.sx-att-cal__cell--absent  { background: var(--sx-danger-light);  color: var(--sx-danger-text);  font-weight: 700; }
.sx-att-cal__cell--today   { box-shadow: inset 0 0 0 2px var(--sx-primary-text); }
.sx-att-strip__legend {
  display: flex;
  flex-wrap: wrap;
  gap: .9rem;
  margin-top: .8rem;
  font-size: .74rem;
  color: var(--sx-text-muted);
}
.sx-att-strip__legend span { display: inline-flex; align-items: center; gap: .3rem; }
.sx-att-strip__key { width: .65rem; height: .65rem; border-radius: 3px; display: inline-block; }
.sx-att-strip__key--present { background: var(--sx-success); }
.sx-att-strip__key--late    { background: var(--sx-warning); }
.sx-att-strip__key--absent  { background: var(--sx-danger); }

/* ============================================================
   Ficha del estudiante dentro de un modal (.sx-student-helper)
   ------------------------------------------------------------
   Vivía en el <style> de rsh.php y ya la pedían otros modales
   (override de nota final), así que sube al sistema tal cual.
   ============================================================ */
/* Ficha del estudiante: no es un aviso, así que no usa .alert (regla: dentro
   de un modal solo van avisos en línea .sx-note). Caja neutra propia. */
/* La tarjeta del estudiante en los modales: esquinas de tarjeta (--radius),
   no el rectángulo a escuadra que dejaba .sx-note (que no trae radio) ni el
   .rounded de Bootstrap (4px), que se quedaba corto al lado del modal. */
.sx-student-helper {
  background: var(--sx-body-bg);
  border-color: var(--sx-border-color) !important;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  gap: .85rem;
  padding: .85rem 1rem;
}

/* El avatar es el .sx-avatar--initials del sistema (círculo + anillo); aquí
   solo se agranda, porque en el helper del modal va a 46px y no a 34px.
   Antes era un clon con su propia forma, su propio anillo y su propia paleta. */
.sx-student-helper__avatar {
  width: 46px;
  height: 46px;
  min-width: 46px;
  font-size: .92rem;
  font-weight: 800;
  letter-spacing: .04em;
}

.sx-student-helper__eyebrow {
  display: block;
  margin-bottom: .15rem;
  color: var(--sx-text-muted);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

.sx-student-helper__name {
  display: block;
  color: var(--sx-text-body);
  font-size: .98rem;
  font-weight: 700;
  line-height: 1.25;
}

/* ============================================================
   "Mostrar N" de las listas en tarjetas (.sx-page-size)
   ------------------------------------------------------------
   Mismo aspecto que el select de longitud de las DataTables
   (pages.css ~353), pero sin el ancestro .dataTables_length:
   en estas páginas no hay tabla, así que se ancla al <select>.

   Estaba COPIADO en cinco hojas (attendance, announcements,
   assignments, rubrics, rubric-grading), cada copia clavada a
   un id distinto — por eso las páginas nuevas salían con el
   select descuadrado: no era que les faltara Select2, era que
   nadie había copiado el bloque otra vez.
   ============================================================ */
select.sx-page-size + .select2-container { width: auto !important; min-width: 4.5rem !important; }
select.sx-page-size + .select2-container .select2-selection--single { position: relative; }
select.sx-page-size + .select2-container .select2-selection__rendered {
  flex: 1 1 auto !important;
  text-align: center !important;
  padding-left: 0.75rem !important;
  padding-right: 1.5rem !important;
}
select.sx-page-size + .select2-container .select2-selection__arrow {
  position: absolute !important;
  top: 0 !important;
  bottom: 0 !important;
  right: 0.35rem !important;
  height: auto !important;
  width: 1rem !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* ============================================================
   Ficha de persona en una celda de tabla (.sx-person)
   ------------------------------------------------------------
   Avatar + nombre, y debajo los identificadores en gris pequeño
   (número, correo, teléfono…). Nació en students.css y la piden
   igual Maestros, Encargados y Empleados: cada uno tenía esos
   datos en COLUMNAS propias y, con ocho columnas, la fila se
   leía en zigzag.

   El servidor ya escapa lo que va dentro.
   ============================================================ */
.sx-person { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.sx-person__txt { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.sx-person__name {
  font-family: var(--font-display); font-weight: 800; font-size: 0.92rem;
  color: var(--sx-text-heading); line-height: 1.2;
}
.sx-person__sub { display: flex; gap: 0.75rem; flex-wrap: wrap; font-size: 0.72rem; color: var(--sx-text-muted); }
.sx-person__i { display: inline-flex; align-items: center; gap: 0.25rem; min-width: 0; }
.sx-person__mail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 18rem; }
/* Un dato que falta no se deja en blanco: se dice, porque es lo que hay que
   arreglar (y suele ser una de las tarjetas KPI de arriba). */
.sx-person__none { color: var(--sx-warning-text); }
/* Variante secundaria: la persona RELACIONADA de la fila (el estudiante de un
   encargado), que no puede pesar lo mismo que el sujeto de la fila. */
/* min-width TAMBIÉN: .sx-avatar trae `min-width: 34px`, así que bajar solo
   width/height no encogía nada y el estudiante salía del mismo tamaño que el
   encargado. */
.sx-person--sub .sx-avatar {
  width: 26px; height: 26px; min-width: 26px;
  font-size: 0.6rem; border-width: 1px;
}
.sx-person--sub .sx-person__name { font-size: 0.82rem; }

/* Dato de columna: la cifra o la etiqueta en grande con el color de su familia.
   Son las columnas que se recorren en vertical para ubicar una fila. */
.sx-fact b { font-family: var(--font-display); font-weight: 800; font-size: 0.95rem; }
.sx-fact--grade b { color: var(--sx-primary-text); }
.sx-fact--group b { color: var(--sx-info-text); }
.sx-fact--count b { color: var(--sx-purple-text); }

/* ---------------------------------------------------------------------------
 * Utilidades heredadas de Tailwind que las vistas usan desde siempre y que NO
 * estaban definidas en ninguna hoja: `flex-1` (Bootstrap la llama `flex-fill`)
 * y `min-w-0`. Eran no-ops silenciosos en ~46 vistas — sobre todo dentro de las
 * tarjetas KPI, donde el bloque de la cifra debía quedarse con el ancho
 * sobrante. Se definen UNA vez aquí en vez de reescribir las 46 vistas: así el
 * markup que ya existe empieza a hacer lo que dice.
 * --------------------------------------------------------------------------- */
.flex-1 { flex: 1 1 0%; }
/* Un hijo de flex no baja de su contenido salvo que se le diga: es lo que
   permite que un nombre largo se recorte con puntos suspensivos en vez de
   empujar la fila. */
.min-w-0 { min-width: 0; }

/* Vive AQUÍ y no en layout.css porque el shell legal (privacy / terms) carga
   components.css pero no layout.css, y también quiere el botón. */
/* Volver arriba: botón flotante abajo a la derecha, aparece al bajar.
   z-index 1035: sobre la topbar (1030) y bajo el aviso de cookies (1040), los
   modales (1050) y los toasts (1090) — nunca tapa algo que haya que leer o
   cerrar. Oculto también con visibility para que no reciba el foco del
   tabulador mientras está invisible. */
.sx-to-top {
  position: fixed;
  right: 1.25rem;
  bottom: 1.5rem;
  z-index: 1035;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-circle);
  background: var(--sx-card-bg);
  color: var(--sx-primary-text);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transition: var(--transition);
}
.sx-to-top.is-on { opacity: 1; visibility: visible; }
.sx-to-top:hover { background: var(--sx-primary-solid); color: #fff; }
/* Con el aviso de cookies abierto, súbelo para no montarse encima en móvil. */
.sx-cookie-banner:not([hidden]) ~ .sx-to-top { bottom: 6rem; }

/* ---------------------------------------------------------------------------
   Tareas programadas (admin/cron)
   La línea del crontab y la salida de una corrida son texto para COPIAR: en
   una sola línea con corte de palabra se vuelven ilegibles, y sin tope de alto
   una salida larga empuja el resto de la tabla fuera de la pantalla.
--------------------------------------------------------------------------- */
.sx-cron-line {
  background: var(--sx-neutral);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius-sm);
  color: var(--sx-text-body);
  overflow-x: auto;
  white-space: nowrap;
}

/* El selector de tarea del historial: ancho fijo para que el segmentado de al
   lado no baile cada vez que se elige una tarea de nombre más largo. */
.sx-run-job { min-width: 13rem; }

.sx-cron-output {
  background: var(--sx-neutral);
  border-radius: var(--radius-sm);
  color: var(--sx-text-muted);
  font-size: .78rem;
  max-height: 9rem;
  overflow: auto;
  padding: .5rem .625rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---------------------------------------------------------------------------
   Selector visual (.sx-pick) — elegir por DIBUJO, no por nombre.
   Nace en el editor de galerías: "mampostería" o "polaroid" no dicen cómo va a
   quedar, y un desplegable obliga a probar y guardar para averiguarlo. Cada
   opción lleva una miniatura hecha con divs (nada de imágenes que mantener) que
   enseña la forma de verdad.
--------------------------------------------------------------------------- */
.sx-pick {
  display: grid;
  gap: .75rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.sx-pick__op {
  background: var(--sx-card-bg);
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: .4rem;
  padding: .75rem;
  text-align: left;
  transition: var(--transition);
}
.sx-pick__op:hover { border-color: var(--sx-primary-text); }
.sx-pick__op.on {
  border-color: var(--sx-primary-text);
  box-shadow: 0 0 0 2px var(--sx-primary-light);
}
.sx-pick__nom { color: var(--sx-text-heading); font-size: .85rem; font-weight: 700; }
.sx-pick__hint { color: var(--sx-text-muted); font-size: .72rem; line-height: 1.35; }

/* La miniatura: un rectángulo con "fotos" dentro. Cada disposición coloca las
   mismas cuatro piezas de otra manera, que es exactamente la diferencia que
   hay que enseñar. */
.sx-pick__art {
  background: var(--sx-body-bg);
  border-radius: var(--radius-sm);
  display: grid;
  gap: 3px;
  height: 3.75rem;
  padding: 5px;
}
.sx-pick__art i { background: var(--sx-primary); border-radius: 2px; display: block; opacity: .55; }

.sx-pick__art--grid     { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 1fr); }
.sx-pick__art--grid i:nth-child(4) { grid-column: span 3; }
.sx-pick__art--masonry  { grid-template-columns: repeat(3, 1fr); grid-template-rows: 1fr 1.6fr; }
.sx-pick__art--masonry i:nth-child(1) { grid-row: span 2; }
.sx-pick__art--masonry i:nth-child(4) { grid-column: span 2; }
.sx-pick__art--mosaic   { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 1fr); }
.sx-pick__art--mosaic i:nth-child(1) { grid-column: span 2; grid-row: span 2; }
.sx-pick__art--carousel { grid-template-columns: repeat(4, 1fr); grid-template-rows: 1fr; overflow: hidden; }
.sx-pick__art--carousel i:nth-child(4) { opacity: .25; }

/* Los diseños enseñan UNA foto: lo que cambia es su marco, no su colocación. */
.sx-pick__art--d-card     { place-items: stretch; }
.sx-pick__art--d-card i   { border-radius: 6px; box-shadow: 0 2px 6px rgba(0, 0, 0, .18); }
.sx-pick__art--d-polaroid { background: var(--sx-card-bg); padding: 6px 6px 16px; box-shadow: inset 0 0 0 1px var(--sx-border-color); }
.sx-pick__art--d-polaroid i { border-radius: 1px; }
.sx-pick__art--d-overlay  { position: relative; }
.sx-pick__art--d-overlay::after {
  background: linear-gradient(to top, rgba(0, 0, 0, .55), transparent);
  border-radius: 0 0 2px 2px;
  bottom: 5px; content: ""; height: 40%; left: 5px; position: absolute; right: 5px;
}
.sx-pick__art--d-frame i  { box-shadow: 0 0 0 2px var(--sx-primary-text); }
.sx-pick__art--d-plain i  { border-radius: 0; }

/* Ubicación del banner flotante (editor de banners). La miniatura ES la
   pantalla y la pieza ES el banner: con doce opciones, lo único que cambia
   entre ellas es dónde cae la pieza, así que se dibuja con una rejilla 3x3 y
   una sola celda ocupada. Las barras ocupan una fila entera y la pantalla
   completa, las nueve celdas. */
.sx-pick__art--ub {
  box-shadow: inset 0 0 0 1px var(--sx-border-color);
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
}
.sx-pick__art--ub i { opacity: .8; }
.sx-pick__art--ub-top-left i      { grid-area: 1 / 1; }
.sx-pick__art--ub-top-center i    { grid-area: 1 / 2; }
.sx-pick__art--ub-top-right i     { grid-area: 1 / 3; }
.sx-pick__art--ub-middle-left i   { grid-area: 2 / 1; }
.sx-pick__art--ub-center i        { grid-area: 2 / 2; }
.sx-pick__art--ub-middle-right i  { grid-area: 2 / 3; }
.sx-pick__art--ub-bottom-left i   { grid-area: 3 / 1; }
.sx-pick__art--ub-bottom-center i { grid-area: 3 / 2; }
.sx-pick__art--ub-bottom-right i  { grid-area: 3 / 3; }
.sx-pick__art--ub-bar-top i       { grid-area: 1 / 1 / 2 / 4; }
.sx-pick__art--ub-bar-bottom i    { grid-area: 3 / 1 / 4 / 4; }
.sx-pick__art--ub-fullscreen i    { grid-area: 1 / 1 / 4 / 4; }

/* Un <input type=color> no sabe estar vacío: siempre devuelve un color. Cuando
   el banner NO lleva color propio (manda el tono del theme), su selector se
   marca apagado para que se vea que no está eligiendo nada. */
.sx-color-off { filter: grayscale(1); opacity: .45; }

/* Miniatura de la galería en el listado. */
.sx-gal-thumb {
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
  height: 2.5rem;
  object-fit: cover;
  width: 3.5rem;
}
.sx-gal-thumb--vacia {
  align-items: center;
  background: var(--sx-body-bg);
  box-shadow: inset 0 0 0 1px var(--sx-border-color);
  color: var(--sx-text-muted);
  display: flex;
  justify-content: center;
}

/* ---------------------------------------------------------------------------
   Encabezado de tarjeta que pliega (.sx-card-toggle).
   El encabezado ENTERO es el botón, no solo la flecha: una diana de 40x40 px en
   la esquina es el clic que se falla, y el título ya dice qué se abre. La flecha
   va a la derecha y gira, que es lo que dice si está abierto o cerrado.
--------------------------------------------------------------------------- */
/* El encabezado sigue siendo un <h2> (así el lector de pantalla puede saltar de
   sección en sección) y el botón va DENTRO, ocupándolo entero: un <h2> dentro de
   un <button> no es HTML válido. Mismo reparto que los acordeones de Bootstrap.
   El relleno se muda al botón, o la zona pulsable no llegaría al borde. */
.card-header.sx-card-toggle-h { padding: 0; }
.sx-card-toggle {
  align-items: center;
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  display: flex;
  gap: .75rem;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  text-align: left;
  width: 100%;
}
.sx-card-toggle:hover .sx-card-toggle__chev { color: var(--sx-primary-text); }
.sx-card-toggle:focus-visible {
  outline: 2px solid var(--sx-primary-text);
  outline-offset: -2px;
}
.sx-card-toggle__chev {
  color: var(--sx-text-muted);
  flex: 0 0 auto;
  transition: var(--transition);
}
/* Bootstrap pone .collapsed en el disparador mientras el panel está cerrado. */
.sx-card-toggle.collapsed .sx-card-toggle__chev { transform: rotate(-90deg); }

/* ==========================================================================
   Constructor de formularios (admin/site-form-edit)
   Paleta de tipos, lienzo con los campos y su tarjeta. El arrastre es nativo
   del navegador: no hay librería, solo estas clases y los eventos.
   ========================================================================== */
.sx-fb-palette { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
.sx-fb-tipo {
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  padding: .7rem .4rem; border: 1px dashed var(--sx-border-color); border-radius: var(--radius);
  background: var(--sx-card-bg); color: var(--sx-text-body); font-size: .78rem;
  line-height: 1.25; text-align: center; cursor: grab;
}
.sx-fb-tipo:hover { border-color: var(--sx-primary-text); color: var(--sx-primary-text); border-style: solid; }
.sx-fb-tipo:active { cursor: grabbing; }
.sx-fb-tipo i { font-size: 1.15rem; color: var(--sx-primary-text); }

.sx-fb-canvas {
  /* Alto suficiente para poder SOLTAR algo cuando está vacío, y ni un píxel
     más: en cuanto hay campos, el lienzo crece con ellos. Una altura fija
     dejaba un hueco muerto debajo del último campo. */
  display: grid; gap: .5rem; min-height: 8rem; align-content: start; padding: .6rem;
  border: 1px dashed var(--sx-border-color); border-radius: var(--radius);
  background: var(--sx-body-bg);
}
/* Mientras se arrastra por encima: el borde dice dónde se va a soltar. */
.sx-fb-canvas--drop { border-color: var(--sx-primary-text); border-style: solid; }
.sx-fb-empty { margin: auto; color: var(--sx-text-muted); font-size: .9rem; text-align: center; }

.sx-fb-campo {
  display: flex; align-items: center; gap: .75rem; padding: .6rem .75rem;
  background: var(--sx-card-bg); border: 1px solid var(--sx-border-color);
  border-left: 3px solid var(--sx-border-color); border-radius: var(--radius);
  cursor: grab;
}
.sx-fb-campo:hover { border-color: var(--sx-text-muted); }
.sx-fb-campo.on { border-left-color: var(--sx-primary-text); background: var(--sx-primary-light); }
.sx-fb-campo:focus-visible { outline: 2px solid var(--sx-primary-text); outline-offset: 2px; }
.sx-fb-campo__ico {
  flex: 0 0 2rem; height: 2rem; display: grid; place-items: center;
  background: var(--sx-body-bg); border-radius: var(--radius-sm); color: var(--sx-primary-text);
}
.sx-fb-campo__txt { flex: 1 1 auto; min-width: 0; display: grid; gap: .15rem; }
.sx-fb-campo__lbl { font-weight: 600; color: var(--sx-text-heading); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-fb-campo__meta { font-size: .78rem; color: var(--sx-text-muted); }
.sx-fb-campo__marcas { display: flex; gap: .25rem; flex-wrap: wrap; }
.sx-fb-campo__acc { flex: 0 0 auto; display: flex; gap: .25rem; }

/* ==========================================================================
   Constructor de formularios de matrícula (admin/enrollment-form-edit)
   Reusa la paleta y la tarjeta de campo de arriba (.sx-fb-*). Añade la lista
   de pasos, el bloque de sección (que agrupa campos) y la vista previa.
   ========================================================================== */
.sx-efb-group { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--sx-text-muted); margin: .9rem 0 .4rem; }
.sx-efb-group:first-child { margin-top: 0; }
.sx-efb-palette--off { opacity: .45; pointer-events: none; }

.sx-efb-steps { display: grid; gap: .4rem; }
.sx-efb-step {
  display: flex; align-items: center; gap: .6rem; width: 100%; padding: .5rem .6rem;
  background: var(--sx-card-bg); border: 1px solid var(--sx-border-color);
  border-left: 3px solid var(--sx-border-color); border-radius: var(--radius); text-align: left;
  color: var(--sx-text-body);
}
.sx-efb-step:hover { border-color: var(--sx-text-muted); }
.sx-efb-step.on { border-left-color: var(--sx-primary-text); background: var(--sx-primary-light); }
/* Arrastrar para ordenar: el asa, la tarjeta que se mueve y la raya de dónde cae. */
.sx-efb-step[draggable="true"] { cursor: grab; }
.sx-efb-step__grip { flex: 0 0 auto; color: var(--sx-text-muted); margin-right: -.25rem; }
.sx-efb-step.is-dragging { opacity: .45; }
.sx-efb-step.is-drop-before { box-shadow: inset 0 3px 0 var(--sx-primary-text); }
.sx-efb-step.is-drop-after { box-shadow: inset 0 -3px 0 var(--sx-primary-text); }
/* Un campo o una sección soltados SOBRE otro paso: el paso entero se marca. */
.sx-efb-step.is-drop-into { border-color: var(--sx-primary-text); border-style: dashed; background: var(--sx-primary-light); }
.sx-efb-sec__head[draggable="true"] { cursor: grab; }
.sx-efb-sec.is-dragging { opacity: .45; }
.sx-efb-sec.is-drop-before { box-shadow: 0 -3px 0 var(--sx-primary-text); }
.sx-efb-sec.is-drop-after { box-shadow: 0 3px 0 var(--sx-primary-text); }
.sx-efb-step__n { flex: 0 0 1.6rem; height: 1.6rem; display: grid; place-items: center; border-radius: 50%;
  background: var(--sx-body-bg); font-size: .78rem; font-weight: 700; color: var(--sx-primary-text); }
.sx-efb-step__txt { flex: 1 1 auto; min-width: 0; display: grid; }
.sx-efb-step__lbl { font-weight: 600; color: var(--sx-text-heading); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-efb-step__meta { font-size: .75rem; color: var(--sx-text-muted); }

.sx-efb-sec { border: 1px solid var(--sx-border-color); border-radius: var(--radius); background: var(--sx-card-bg); }
.sx-efb-sec + .sx-efb-sec { margin-top: .75rem; }
.sx-efb-sec.on { border-color: var(--sx-primary-text); }
.sx-efb-sec__head { display: flex; align-items: center; gap: .5rem; padding: .6rem .75rem;
  border-bottom: 1px solid var(--sx-border-color); cursor: pointer; }
.sx-efb-sec__title { flex: 1 1 auto; min-width: 0; font-weight: 700; color: var(--sx-text-heading); }
.sx-efb-sec__body { padding: .6rem; }
.sx-efb-sec__body .sx-fb-canvas { min-height: 4.5rem; }

.sx-efb-errors { display: grid; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.sx-efb-errors li { display: flex; gap: .5rem; font-size: .85rem; line-height: 1.4; }
.sx-efb-errors li i { color: var(--sx-warning-text); flex: 0 0 auto; }
.sx-efb-sys { display: grid; gap: .3rem; margin: 0; padding: 0; list-style: none; font-size: .85rem; }
.sx-efb-sys li { display: flex; gap: .5rem; align-items: center; }

.sx-efb-opt { display: grid; grid-template-columns: 1fr 1fr auto; gap: .35rem; margin-bottom: .35rem; }
/* Ancho exacto (col-N): barrita que muestra cuánto de la fila ocupa. */
.sx-efb-wbar { display: inline-block; width: 1.4rem; height: .4rem; border-radius: 99px; background: color-mix(in srgb, currentColor 22%, transparent); overflow: hidden; }
.sx-efb-wbar > span { display: block; height: 100%; background: currentColor; border-radius: 99px; }

/* ==========================================================================
   Ficha en panel lateral (admin/site-media)
   El cuerpo crece y los botones + la nota quedan siempre abajo, así el sitio
   de "guardar" no se mueve según lo largo que sea el archivo elegido.
   ========================================================================== */
.sx-ficha { display: flex; flex-direction: column; }
.sx-ficha__cuerpo { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.sx-ficha__cuerpo > form { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.sx-ficha__acciones { margin-top: auto; padding-top: .75rem; border-top: 1px solid var(--sx-border-color); }
.sx-ficha__pie { margin-top: .75rem; margin-bottom: 0; }

/* El panel se enfoca solo al abrirse y el navegador le pinta el anillo de foco
   alrededor de TODO el recuadro. Es un contenedor, no un control: los mandos
   de dentro conservan su anillo. */
.offcanvas:focus-visible, .modal:focus-visible { outline: none; }
/* Paneles laterales de TODA la app: 515px (el estándar de Bootstrap, 400px,
   apretaba los textos ES/EN y las opciones). En el celular, pantalla completa. */
.offcanvas-end, .offcanvas-start { --bs-offcanvas-width: min(515px, 100vw); }

/* Cabecera de un bloque del constructor: el botón del acordeón y, a su derecha,
   el de ajustes de sección. El botón del acordeón trae fondo propio y esquinas
   del acordeón; al meterlo en una fila hay que devolverle el ancho para que la
   flecha siga cayendo al final y no en medio de la línea. */
/* Menús «Añadir bloque» / «Insertar patrón» del editor de páginas: con 26
   tipos la lista pasa del alto de la pantalla y lo de abajo quedaba fuera de
   alcance. Tope de alto y scroll dentro del menú. */
#blockMenu, #patternMenu, .js-nest-menu { max-height: min(60vh, 30rem); overflow-y: auto; overscroll-behavior: contain; }
.sx-blk-head .accordion-button { flex: 1 1 auto; min-width: 0; }
.sx-blk-head { background: var(--sx-card-bg); }
/* Con el bloque ABIERTO, el tinte tiene que cubrir la fila ENTERA. Lo pinta el
   botón del acordeón, que solo ocupa una parte: el de ajustes y la flecha se
   quedaban con el fondo de tarjeta y el color salía cortado a media fila.
   Se tiñe el contenedor y el botón se deja transparente para no pintar dos
   veces el mismo color. */
.sx-blk-head:has(.accordion-button:not(.collapsed)) { background: var(--sx-primary-light); }
.sx-blk-head:has(.accordion-button:not(.collapsed)) .accordion-button { background: transparent; }
/* Sobre el tinte, la flecha y el botón de ajustes se leen con la tinta de la
   marca; el gris de reposo se apagaba encima del color. */
.sx-blk-head:has(.accordion-button:not(.collapsed)) .js-blk-chev { color: var(--sx-primary-text); }
.sx-blk-head:has(.accordion-button:not(.collapsed)) .js-block-style {
  background: transparent;
  border-color: rgba(var(--sx-primary-rgb), .35);
  color: var(--sx-primary-text);
}
.sx-blk-head .js-block-style { border-radius: var(--radius-sm, .5rem); }
/* La flecha del acordeón se pinta aparte para que quede la ÚLTIMA de la fila,
   detrás del icono de ajustes; la suya (::after) se apaga. Gira con el estado
   del propio botón, así que no hace falta que el JS la toque. */
.sx-blk-head .accordion-button::after { display: none; }
/* El tamaño es explícito: la cabecera del acordeón es un <h2>, así que un
   icono suelto ahí dentro heredaba el cuerpo del titular y salía enorme. */
.sx-blk-head .js-blk-chev { font-size: .8rem; line-height: 1; color: var(--sx-text-muted); transition: var(--transition); }
.sx-blk-head .accordion-button:not(.collapsed) ~ .js-blk-chev { transform: rotate(-180deg); }


/* ======================================================
   TICKETS — detalle (app/ticket-detail, admin/ticket-detail)
   ======================================================
   Sale de la maqueta apps-ticket-details.html. Vive aquí y no en un <style>
   de la página porque son DOS pantallas (la de la escuela y la de MRE) que
   pintan el mismo hilo con el mismo módulo JS.

   NO reusa .sx-chat-bubble: el de salida del chat es relleno macizo con texto
   blanco, y aquí el propio va TEÑIDO, con avatar y línea de autoría encima.
   Son dos componentes distintos que se parecen. */
.sx-tk-msg { display: flex; gap: .75rem; margin-bottom: 1.25rem; }
/* El mensaje propio a la derecha: en un hilo de dos partes, el lado dice de un
   vistazo quién habla sin tener que leer el nombre. */
.sx-tk-msg--own { flex-direction: row-reverse; }
.sx-tk-msg .sx-avatar { flex-shrink: 0; }

.sx-tk-msg__bubble {
  max-width: 75%;
  padding: .85rem 1rem;
  border-radius: var(--radius);
  background: var(--sx-body-bg);
  border: 1px solid var(--sx-border-color);
}
.sx-tk-msg--own .sx-tk-msg__bubble {
  background: color-mix(in srgb, var(--sx-primary) 12%, transparent);
  border-color: color-mix(in srgb, var(--sx-primary) 22%, transparent);
}
/* La nota interna se ve distinta A PROPÓSITO: quien atiende tiene que saber de
   un golpe qué NO está leyendo la otra persona. */
.sx-tk-msg__bubble--internal {
  background: var(--sx-warning-light);
  border-color: color-mix(in srgb, var(--sx-warning) 40%, transparent);
}
.sx-tk-msg__meta { font-size: .73rem; color: var(--sx-text-muted); margin-bottom: .2rem; }
.sx-tk-msg__meta strong { color: var(--sx-text-heading); }
.sx-tk-msg__text { font-size: .85rem; color: var(--sx-text-body); margin: 0; line-height: 1.5; }

/* Adjunto dentro de la burbuja. */
.sx-tk-file {
  display: inline-flex; align-items: center; gap: .5rem;
  border: 1px solid var(--sx-border-color); border-radius: var(--radius-sm);
  padding: .4rem .7rem; font-size: .78rem; margin-top: .5rem;
  background: var(--sx-card-bg); color: var(--sx-text-body); text-decoration: none;
}
.sx-tk-file:hover { border-color: var(--sx-primary); color: var(--sx-text-heading); }
.sx-tk-file > i { color: var(--sx-primary-text); }

/* Ficha lateral: etiqueta a la izquierda, dato a la derecha. */
.sx-tk-info {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; padding: .55rem 0;
  border-bottom: 1px solid var(--sx-border-color); font-size: .83rem;
}
.sx-tk-info:last-child { border-bottom: 0; }
.sx-tk-info__label { color: var(--sx-text-muted); }
.sx-tk-info__value { font-weight: 600; color: var(--sx-text-heading); text-align: right; }

/* Línea de tiempo de la actividad. */
.sx-tk-act { position: relative; padding-left: 26px; padding-bottom: 1.1rem; }
.sx-tk-act:last-child { padding-bottom: 0; }
.sx-tk-act::before {
  content: ''; position: absolute; left: 7px; top: 18px; bottom: -2px;
  width: 2px; background: var(--sx-border-color);
}
.sx-tk-act:last-child::before { display: none; }
.sx-tk-act__dot {
  position: absolute; left: 0; top: 3px; width: 16px; height: 16px;
  border-radius: var(--radius-circle); background: var(--sx-primary);
  border: 3px solid var(--sx-card-bg); box-shadow: 0 0 0 1px var(--sx-border-color);
}
.sx-tk-act__txt { font-size: .83rem; color: var(--sx-text-body); }
.sx-tk-act__txt strong { color: var(--sx-text-heading); }
.sx-tk-act__when { font-size: .75rem; color: var(--sx-text-muted); }

/* Miniatura de la rejilla de adjuntos. */
.sx-tk-thumb {
  width: 100%; aspect-ratio: 4/3;
  border-radius: var(--radius-sm); background: var(--sx-body-bg);
  border: 1px solid var(--sx-border-color);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.4rem; color: var(--sx-text-muted);
  overflow: hidden; text-decoration: none;
}
.sx-tk-thumb:hover { border-color: var(--sx-primary); }
.sx-tk-thumb img { width: 100%; height: 100%; object-fit: cover; }
.sx-tk-thumb__name { font-size: .75rem; color: var(--sx-text-muted); word-break: break-all; }

/* ---------------------------------------------------------------------------
   .sx-doc — la HOJA: un documento (factura, recibo) enseñado dentro de una
   página de la app, con el mismo diseño que tendrá en papel.

   Solo pone el marco —ancho de carta, fondo blanco, borde y sombra—; lo de
   dentro lo pinta el CSS del propio documento, que llega acotado a este
   contenedor (FinancePdf::scopeCss). Por eso aquí no hay tipografías ni
   colores de texto: los pondría por duplicado y se separarían del papel.

   El fondo es blanco fijo y no var(--sx-card-bg): es una hoja de papel, y en
   modo oscuro una hoja gris no se lee como una hoja.
   --------------------------------------------------------------------------- */
.sx-doc {
  max-width: 8.5in;
  margin: 0 auto;
  padding: 0.5in;
  background: #fff;
  border: 1px solid var(--sx-border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
}
@media (max-width: 767.98px) {
  .sx-doc { padding: 1rem; }
}

/* Leyenda del calendario que además APAGA: cada fila es un <button>.
   Los estilos van sobre `button.cal-legend-item` y no sobre la clase a secas
   porque la leyenda de Mis Cursos usa <div> con la misma clase — allí no se
   apaga nada, y un reset de botón sobre un div no le hace falta a nadie.
   Apagada = translúcida. No tachada: el tachado se lee como "borrado", y esto
   se vuelve a encender con otro clic. */
button.cal-legend-item {
  background: none;
  border: 0;
  padding: .1rem 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: opacity var(--transition);
}
button.cal-legend-item:hover { opacity: .75; }
button.cal-legend-item.is-off { opacity: .38; }
button.cal-legend-item:focus-visible {
  outline: 2px solid var(--sx-primary);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* ======================================================
   SALUD DEL SISTEMA · registro de errores PHP (admin/health)
   ====================================================== */
/* El tope de ancho va en un DIV dentro de la celda, no en el <td>: la tabla es
   de layout automático y ahí max-width sobre la celda no lo respeta nadie. Sin
   tope, el mensaje de una excepción se lleva todo el ancho y deja la fecha de
   "Última vez" partida en tres renglones. Lo que no cabe se corta con puntos
   suspensivos (el texto entero está en el modal de detalle) y el title deja
   leerlo por encima sin abrir nada. */
.sx-err-problem { max-width: 46ch; }
.sx-err-problem > .sx-att-clname,
.sx-err-problem > .small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
