/* =========================================================================
   SISTEMA DE DISEÑO — App Académica
   Base visual: glassmorphism + degradados + pills + badges + switches.
   13 paletas x 3 modos (claro/color/oscuro profundo) controladas por atributos en <html>:
     <html data-palette="azul" data-mode="dark">
   ========================================================================= */

:root {
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --font-display: 'Sora', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Inter', 'Segoe UI', system-ui, sans-serif;

  --shadow-glass: 0 20px 40px rgba(0, 0, 0, 0.35);
  --shadow-float: 0 8px 20px rgba(0, 0, 0, 0.4);

  --transition-fast: 0.18s ease;
  --transition-med: 0.28s ease;

  /* Fallback por si el atributo data-palette aún no se aplicó en el
     primer pintado; cada paleta lo sobreescribe con su propio valor
     "aplanado" (ver bloques [data-palette=...] más abajo). */
  --bg-header-solido: #101114;
}

/* Punto 6 (2026-08-23): en modo claro la sombra negra plana ("rgba(0,0,0,x)")
   choca contra el fondo claro aprobado — se ve como una mancha gris sucia en
   vez de dar profundidad. Se tiñe con --accent-1 de la paleta activa (mismo
   patrón color-mix ya usado en el resto de este archivo, ver línea 645) en
   vez de negro puro: primero se mezcla el acento con negro para tener una
   base oscura con tinte de color (--shadow-tinte-claro), y esa base se
   diluye con transparencia para mantener la MISMA opacidad final que tenía
   la sombra original (35%/40%/28%/30% según el caso) — solo cambia el color
   de base, la fuerza del efecto de profundidad queda igual. No toca modo
   oscuro (ahí el negro plano sí funciona bien contra fondos oscuros, y es
   el look aprobado). */
[data-mode="light"] {
  --shadow-tinte-claro: color-mix(in srgb, var(--accent-1) 25%, black 75%);
  --shadow-glass: 0 20px 40px color-mix(in srgb, var(--shadow-tinte-claro) 35%, transparent),
    0 1px 3px color-mix(in srgb, var(--shadow-tinte-claro) 20%, transparent);
  --shadow-float: 0 8px 20px color-mix(in srgb, var(--shadow-tinte-claro) 40%, transparent);

  /* Punto 3 (2026-09-12, REVERTIDO — intento 1): se había probado oscurecer
     --bg-card/--bg-panel ~15% hacia gris para separarlos del canvas (ver
     historial). Feedback directo: el gris se ve mal, el blanco elegido se
     quiere mantener tal cual. Se descarta esa idea por completo (no quedan
     --bg-card-jerarquia/--bg-panel-jerarquia — .glass-card/.glass-panel
     vuelven a usar var(--bg-card)/var(--bg-panel) directo, sin capa
     intermedia).

     Intento 2 (el que queda activo): NO tocar el color de fondo — delimitar
     con borde + sombra un poco más marcados, nada más. --border-glass ya
     existe por paleta pero a un alfa muy bajo (0.08-0.12) que en blanco casi
     puro no se nota. Se define una variable NUEVA acá (--border-glass-fuerte)
     en vez de reasignar --border-glass en términos de sí mismo — eso sería
     un ciclo inválido en CSS (cualquier declaración de una custom property
     que dependa, aunque sea indirectamente, de sí misma en la cascada del
     MISMO elemento, computa a "inválido"; no es como una variable de Sass
     que puede extender su propio valor anterior). Se usa un tono neutro fijo
     (no derivado de cada paleta) a propósito: tiene que verse igual de bien
     con cualquiera de las 13 paletas claras, y ya hay precedente de esto
     mismo en el archivo (--border-glass en varias paletas usa el mismo
     rgba(15,23,42,...) sin importar el acento de la paleta). El shadow-glass
     de arriba también suma una sombra CHICA y pegada al borde (además de la
     grande y difusa que ya tenía) para que el límite de la tarjeta se lea
     incluso donde el borde solo no alcanza (esquinas, tarjetas una al lado
     de la otra). */
  --border-glass-fuerte: rgba(15, 23, 42, 0.16);
}
[data-mode="light"] .glass-card,
[data-mode="light"] .glass-panel {
  border-color: var(--border-glass-fuerte);
}
/* Mismo fix que arriba, pero para los valores reducidos de Modo Rendimiento
   (ver bloque [data-rendimiento="reducido"] más abajo) — sin esto, apenas se
   activa el modo por defecto (rendimiento) la sombra clara volvía a salir
   negra plana porque esa regla pisa --shadow-glass/--shadow-float con sus
   propios rgba(0,0,0,...). */
[data-mode="light"][data-rendimiento="reducido"] {
  --shadow-glass: 0 2px 6px color-mix(in srgb, var(--shadow-tinte-claro) 28%, transparent);
  --shadow-float: 0 2px 6px color-mix(in srgb, var(--shadow-tinte-claro) 30%, transparent);
}

/* ---- Estructura base (independiente de paleta) ---- */
* {
  box-sizing: border-box;
  /* Quita el highlight azul nativo que Chrome/Safari en móvil pintan sobre
     toda la hitbox al tocar cualquier elemento clickeable (botones, links,
     pills, tarjetas, etc). Es un efecto del navegador, no del sistema de
     diseño — por eso aparecía en casi todo sin que hubiera una regla
     nuestra que lo causara. */
  -webkit-tap-highlight-color: transparent;
}

/* ---- Letra 10% más chica solo en teléfono (pedido explícito) ----
   16px * 0.9 = 14.4px. Todo el sistema usa rem para tipografía (ver
   comentarios en horario.js sobre esto), así que bajar el font-size base
   de <html> solo en este breakpoint escala el texto de toda la app un
   10% únicamente en pantallas de teléfono, sin tocar cada regla suelta ni
   afectar tablet/escritorio. */
@media (max-width: 480px) {
  html { font-size: 14.4px; }
}

html[data-text-size="pequeno"] { font-size: 14px !important; }
html[data-text-size="mediano"] { font-size: 16px !important; }
html[data-text-size="grande"] { font-size: 18px !important; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  font-family: var(--font-body);
  background: var(--bg-canvas);
  color: var(--text-primary);
  transition: background var(--transition-med), color var(--transition-med);
  /* v8.3 (prioridad máxima): sin esto, el gesto nativo de "deslizar hacia
     abajo para refrescar" de los navegadores móviles (Chrome/Safari en
     Android/iOS) puede disparar una RECARGA COMPLETA de la página real,
     compitiendo con nuestro gesto personalizado de sincronizar — eso
     además forzaba a que toda la app (login incluido) se reinicializara
     de cero en cada deslizón, lo cual explicaba tanto que el gesto no
     funcionara bien en teléfono como que pareciera "abrirse Google" de la
     nada. overscroll-behavior-y: contain apaga el gesto nativo del
     navegador para que solo mande el nuestro. */
  overscroll-behavior-y: contain;
}

body {
  background-image:
    radial-gradient(circle at 15% 10%, var(--accent-glow-1) 0%, transparent 45%),
    radial-gradient(circle at 85% 0%, var(--accent-glow-2) 0%, transparent 40%),
    radial-gradient(circle at 50% 100%, var(--accent-glow-1) 0%, transparent 55%);
  background-attachment: fixed;
  min-height: 100vh;
  overflow-wrap: anywhere;
}

h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 700; margin: 0 0 8px; }
p { margin: 0 0 12px; color: var(--text-secondary); }
a { color: var(--accent-1); }

/* ---- Tarjeta de vidrio (glassmorphism) ---- */
.glass-card {
  background: var(--bg-card);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: var(--shadow-glass);
  padding: 24px;
}

.glass-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-md);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* ---- Botón primario con degradado ---- */
.btn {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  padding: 12px 22px;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), opacity var(--transition-fast);
  /* Pedido explícito: en teléfono, mantener presionado un botón (long-press,
     ej. el indicador de sync) no debe activar la selección/lupa de texto
     nativa del navegador — solo debe disparar la acción del botón. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.btn:active { transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

.btn-primary {
  background: var(--gradient-accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-float);
}
.btn-primary:hover { box-shadow: 0 10px 26px var(--accent-glow-1); }

.btn-secondary {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border: 1px solid var(--border-glass);
}
.btn-secondary:hover { background: rgba(255, 255, 255, 0.1); }

.btn-danger {
  background: transparent;
  color: var(--color-danger);
  border: 1px solid var(--color-danger);
}

.btn-block { width: 100%; }

/* Ronda de ajustes visuales — punto 3: botón de engranaje del header de
   Agenda (#btn-agenda-ajustes), consolidado a partir de los 3 botones
   anteriores (Agregar / engranaje-a-Ajustes-global / Filtros).

   Ronda de ajustes visuales #2 — pedido explícito (revertido respecto al
   punto 3 original): SIN fondo/sombra — solo el emoji del engranaje, como
   estaba antes de esa ronda. Se mantiene la clase propia (en vez de volver
   a .btn-icono-fantasma) por si more adelante necesita un tratamiento
   propio, pero visualmente hoy es igual de "fantasma": sin caja, mismo
   tamaño de tap-target (36×36) para no perder el área táctil. */
.agenda-btn-ajustes {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: none;
  box-shadow: none;
  color: var(--text-secondary);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.agenda-btn-ajustes:hover { background: rgba(255, 255, 255, 0.08); }
[data-mode="light"] .agenda-btn-ajustes:hover { background: rgba(0, 0, 0, 0.06); }
.agenda-btn-ajustes:active { transform: scale(0.94); }
.agenda-btn-ajustes:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* Idea "varios semestres a la vez" — tarjetas seleccionables del modal
   #modal-agenda-semestres (reemplaza al popover de lista simple que había
   antes, ver .agenda-semestre-popover-item, ya retirado): cada tarjeta
   actúa como botón toggle — puede haber VARIAS marcadas a la vez. Mismo
   lenguaje visual que .glass-panel de base, con el estado "seleccionada"
   resaltado con el color de acento (mismo criterio que .pill-item.active),
   más un check circular a la izquierda que se rellena al activarse. */
.agenda-semestre-tarjeta {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: var(--bg-panel);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  color: var(--text-primary);
  font-family: var(--font-body);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.agenda-semestre-tarjeta:hover { border-color: var(--accent-1-40, var(--accent-1)); }
.agenda-semestre-tarjeta:active { transform: scale(0.99); }
.agenda-semestre-tarjeta:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.agenda-semestre-tarjeta-check {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--border-glass);
  color: transparent;
  font-size: 0.72rem;
  font-weight: 700;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.agenda-semestre-tarjeta.active {
  border-color: var(--accent-1);
  background: var(--accent-1-10);
}
.agenda-semestre-tarjeta.active .agenda-semestre-tarjeta-check {
  background: var(--accent-1);
  border-color: var(--accent-1);
  color: var(--on-accent);
}

/* ---- v2.8.9: botón de texto "discreto" — mismo texto que un .btn normal
   pero sin caja ni fondo propio, para acciones secundarias que no deberían
   competir visualmente con el CTA principal de la fila (ej. "Editar
   registro" al lado de un badge de monto). Mismo espíritu que
   .btn-icono-fantasma pero con texto en vez de un solo glyph. ---- */
.btn-discreto {
  background: none;
  border: none;
  padding: 6px 10px;
  color: var(--accent-2);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast), color var(--transition-fast);
  white-space: nowrap;
}
.btn-discreto:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-primary); }
[data-mode="light"] .btn-discreto:hover { background: rgba(0, 0, 0, 0.06); }
.btn-discreto:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* Variante para acciones destructivas (ej. "Eliminar" en las tarjetas del
   Asistente) — mismo tratamiento discreto, pero en rojo para diferenciarse
   de una acción como "Editar". */
.btn-discreto-peligro { color: var(--color-danger); }
.btn-discreto-peligro:hover { background: rgba(220, 38, 38, 0.12); color: var(--color-danger); }

/* ---- Grupo de botones "pill" (selección exclusiva) ---- */
.pill-group {
  display: flex;
  /* Bug 3 (v8): antes usaba flex-wrap para pasar los pills que no cabían a
     una segunda línea (ej. los 4 pills de Estado en pantallas angostas),
     pero eso se ve mal y el prompt pide explícitamente que NUNCA ocupen más
     de una línea. Ahora nunca envuelve: si no caben todos, el propio grupo
     se vuelve scrolleable horizontalmente (deslizable al tacto de forma
     nativa) y, cuando además desborda, se puede envolver con flechitas via
     envolverConFlechasScroll() — ver plan.js. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none; /* Firefox: oculta la barra mientras sigue siendo scrolleable */
  -webkit-overflow-scrolling: touch;
  gap: 6px;
  background: rgba(0, 0, 0, 0.18);
  padding: 4px;
  border-radius: var(--radius-pill);
}
.pill-group::-webkit-scrollbar { display: none; } /* Chrome/Safari/Edge */
[data-mode="light"] .pill-group { background: rgba(0, 0, 0, 0.05); }

/* Bug 5: el selector de "Plan activo" en Configuración normalmente tiene
   1-2 pills nada más (a diferencia de los pill-group de ajustes, que
   siempre tienen varias opciones fijas) — sin este centrado explícito, un
   solo pill queda pegado a la izquierda dentro de todo el ancho de la
   tarjeta, viéndose "torcido". */
#selector-plan .pill-group,
#selector-plan-secundario .pill-group { justify-content: center; }

.pill-item {
  /* v1.9.6: `flex: 0 0 auto` (v7.1) arreglaba el corte de texto pero rompía
     el reparto equitativo pedido (los pills volvían a quedar más angostos
     que el ancho disponible). `flex: 1 1 0` + `min-width: 0` reparte el
     ancho por igual entre todos los pills del grupo Y permite que, si aun
     así el texto no cabe, trunque prolijamente con "…" en vez de
     desbordarse o encogerse de forma desigual (ver overflow/text-overflow
     abajo) — resuelve los dos problemas a la vez sin reintroducir el bug
     original de "Solo la primera[m]" cortado a lo bruto. */
  flex: 1 1 0;
  min-width: 0;
  /* v1.9.9: se probaron 2 approaches para el corte vertical con zoom:
     (a) overflow-x:hidden + overflow-y:visible -> por spec de CSS, cuando un
         eje no es "visible" el otro se fuerza a "auto" -> metía scrollbar
         por botón.
     (b) engordar el padding/line-height para que el redondeo nunca alcance
         la letra -> funciona pero el botón se ve gordo/raro (feedback:
         "muy gordo se ve raro").
     Fix real: dejar el .pill-item genérico como estaba (compacto, sin
     engordar nada) y en su lugar sacar el overflow:hidden puntualmente en
     el selector Tarea/Examen/Evento vía `.pill-group-fijo` más abajo — ese
     grupo tiene 3 palabras cortas y fijas que NUNCA necesitan truncarse con
     "…", así que sin overflow:hidden ahí no hay nada que el zoom pueda
     recortar, y el botón queda con su alto mínimo natural. El resto de los
     pill-group del sistema (los que sí truncan texto largo/dinámico)
     siguen usando este overflow:hidden normal, sin cambios. */
  overflow: hidden;
  text-overflow: ellipsis;
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  transition: all var(--transition-fast);
  white-space: nowrap;
}
/* La mayoría de los pill-item del proyecto tienen el texto directo dentro
   del <button> (sin span interno) — la regla de arriba ya los cubre. Esta
   regla adicional es para los pocos casos que sí envuelven el texto en un
   span (ej. `.texto`), para que el truncado funcione igual ahí. */
.pill-item span, .pill-item .texto {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pill-item.active {
  background: var(--gradient-accent-alt, var(--gradient-accent));
  color: var(--on-accent);
  box-shadow: var(--shadow-float);
}

/* v1.9.10: `.pill-group-fijo .pill-item { overflow:visible }` (arriba, junto
   a la definición base de .pill-item) no alcanzaba solo — el corte en
   realidad venía del CONTENEDOR .pill-group, que tiene overflow-x:auto
   (para scrollear pills que no entran). Por spec de CSS, si un eje no es
   "visible" el otro NUNCA puede quedar realmente visible: el navegador
   fuerza el overflow-y del contenedor a "auto" también, y ese es el que
   seguía recortando con zoom >90%. Este bloque tiene que ir DESPUÉS de la
   definición base de `.pill-group` (arriba) — misma especificidad
   (1 clase c/u), así que gana quien esté último en el archivo; puesto
   antes, `.pill-group` lo pisaba y el fix no hacía nada en absoluto.
   Como este grupo es fijo (3 palabras cortas que siempre entran, nunca
   necesita scroll horizontal), se le saca el overflow-x:auto por completo. */
.pill-group-fijo {
  overflow: visible;
}
/* Especificidad 2 clases (le gana a `.pill-item` normal sin importar el
   orden en el archivo) — este es el que realmente evita cualquier truncado
   con "…" en los 3 botones de adentro. */
.pill-group-fijo .pill-item {
  overflow: visible;
  text-overflow: unset;
}

/* ---- Pill switch binario (punto 4, ronda visual 2026-09-12): variante de
   .pill-group-fijo para el caso "switch de exactamente 2 opciones" (Modo:
   Oscuro/Claro, Calidad: Optimizado/Fancy — ver construirPillSwitchBinario
   en ui/componentes.js). Dos pedidos juntos acá:

   1. Ancho completo de la tarjeta: .pill-group ya reparte sus pill-item con
      flex:1 (ver arriba), así que solo hace falta que el CONTENEDOR mismo
      ocupe el 100% en vez de encogerse al contenido — por defecto un div en
      flujo normal ya lo hace, pero se deja explícito acá para que no
      dependa de qué layout tenga la fila que lo envuelve en cada lugar
      donde se use.

   2. Transición suave tipo "thumb deslizante" en vez del salto brusco de
      fondo entre botones (feedback: "se ve muy brusco"). En vez de que cada
      .pill-item prenda/apague su propio fondo con la transición genérica de
      0.18s que ya trae .pill-item (pensada para pills sueltos, no para este
      caso), acá hay un único thumb (.pill-switch-thumb) que se desliza de
      un lado al otro con transform — se ve y se siente como un switch
      real, no como 2 botones independientes cambiando de color a la vez.
      Los .pill-item de acá quedan con fondo transparente incluso en
      .active (el color lo da el thumb de abajo), así que no compite/parpadea
      contra el fondo del thumb. */
.pill-switch-binario {
  position: relative;
  width: 100%;
  gap: 0;
}
.pill-switch-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  /* Ver el cálculo en el comentario de arriba: con padding fijo de 4px en
     .pill-group y exactamente 2 opciones de ancho igual, "calc(50% - 4px)"
     da el ancho exacto de CUALQUIERA de los 2 botones sin importar el
     ancho real de la tarjeta — y por eso mover el thumb "su propio 100%"
     (translateX(100%), relativo a SU PROPIO ancho, no al del contenedor)
     lo deja pegado exacto contra el botón de la derecha. */
  width: calc(50% - 4px);
  border-radius: var(--radius-pill);
  background: var(--gradient-accent-alt, var(--gradient-accent));
  box-shadow: var(--shadow-float);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 0;
  pointer-events: none;
}
.pill-switch-thumb.pill-switch-thumb--derecha {
  transform: translateX(100%);
}
.pill-switch-binario[data-opciones="3"] .pill-switch-thumb {
  width: calc((100% - 8px) / 3);
}
.pill-switch-binario[data-opciones="3"] .pill-switch-thumb.pill-switch-thumb--derecha {
  transform: translateX(100%);
}
.pill-switch-binario[data-opciones="3"] .pill-switch-thumb.pill-switch-thumb--tercera {
  transform: translateX(200%);
}
.pill-switch-binario .pill-item {
  position: relative;
  z-index: 1;
  /* El color de fondo lo da el thumb, no el propio botón — si se dejara
     el fondo normal de .pill-item.active acá, se vería doble (el thumb
     deslizándose Y un segundo fondo prendiéndose/apagándose de golpe
     encima), exactamente el "brusco" que se quiere evitar. La transición
     de color de texto (heredada de .pill-item) sigue andando normal. */
  background: transparent !important;
  box-shadow: none !important;
  transition: color var(--transition-fast);
}

/* Fila "Modo" / "Calidad" + su pill switch de ancho completo, apilados
   (caption arriba, switch abajo) — reemplaza la fila anterior en línea de
   "etiqueta ..... switch chico a la derecha" (ver montarPillSwitch en
   config-ajustes.js, que reconstruye la fila entera con esta estructura). */
.fila-pill-switch {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  width: 100%;
}
.fila-pill-switch .fila-pill-switch-titulo {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-primary);
}

/* ---- v2.8.9: grid de 2 columnas para grupos de opciones tipo switch con
   etiquetas que no caben cómodas en una sola fila horizontal sin cortarse
   (ej. Frecuencia de un gasto recurrente: Semanal/Quincenal/Mensual/Anual/
   Personalizado). Sigue siendo un switch de selección única — mismos
   .pill-item adentro, mismo estado .active — solo cambia el LAYOUT: en vez
   de una fila que trunca o scrollea, es una grilla de 2 columnas (2 arriba,
   2 abajo, y así sucesivamente) donde cada botón crece en alto lo que haga
   falta para mostrar el texto completo, nunca se recorta. ---- */
.pill-grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  background: rgba(0, 0, 0, 0.18);
  padding: 4px;
  border-radius: var(--radius-md);
}
[data-mode="light"] .pill-grid-2 { background: rgba(0, 0, 0, 0.05); }
.pill-grid-2 .pill-item {
  flex: unset;
  min-width: 0;
  overflow: visible;
  text-overflow: unset;
  white-space: normal;
  text-align: center;
  min-height: 38px;
}

/* Cronograma (horario-modal.js) — grid de semanas (2026-08-14): antes
   reutilizaba .pill-group (fila scrolleable, nunca envuelve) y con más de
   ~10 semanas la mayoría quedaba fuera de vista sin aviso, y el highlight
   de la semana seleccionada podía quedar oculto tras el scroll. Con CSS
   Grid + auto-fill, el navegador calcula UNA sola vez cuántas columnas
   caben según el ancho disponible y las repite en cada fila — así todas
   las filas quedan con la misma cantidad de botones (la última fila, si
   queda incompleta, simplemente no llena todas las columnas, nunca
   desareja el resto) y NUNCA se recorta ni hace falta scrollear. */
.pill-grid-semanas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
  gap: 6px;
  background: rgba(0, 0, 0, 0.18);
  padding: 4px;
  border-radius: var(--radius-md);
  margin-top: 10px;
}
[data-mode="light"] .pill-grid-semanas { background: rgba(0, 0, 0, 0.05); }
.pill-grid-semanas .pill-item {
  flex: unset;
  min-width: 0;
  text-align: center;
  padding: 8px 4px;
}

/* Punto 4 (v1.9.6): separador "·" entre "Bloques ▲▼" y "Materias ▲▼" dentro
   de la misma pill combinada — no es un pill-item, así que no participa del
   reparto flex:1 1 0 de arriba (ancho fijo, solo el glyph). */
.pill-separador {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  color: var(--text-muted);
  opacity: 0.6;
  padding: 0 2px;
  font-weight: 700;
}

/* Bug 3 (v8) / reutilizado por Ajuste 3 (mapa): envoltorio genérico para
   cualquier fila scrolleable horizontal con flechitas "‹ ›" de solo símbolo
   (sin fondo de botón), como pide el prompt "igual que en Planes de
   Estudio" — reutiliza el mismo estilo visual que .flecha-plan. Las
   flechas se ocultan solas (clase .oculto) cuando no hace falta desplazar,
   o cuando ya se llegó al extremo correspondiente. */
.scroll-con-flechas {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.scroll-con-flechas > .pill-group,
.scroll-con-flechas > .scroll-contenido {
  flex: 1 1 auto;
  min-width: 0;
}
.flecha-scroll { flex-shrink: 0; }



/* ---- Badges semánticos ---- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  font-weight: 700;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
}
.badge-success { background: rgba(16, 185, 129, 0.15); border-color: #10b981; color: #34d399; }
.badge-warning { background: rgba(245, 158, 11, 0.15); border-color: #f59e0b; color: #fbbf24; }
.badge-danger  { background: rgba(239, 68, 68, 0.15);  border-color: #ef4444; color: #f87171; }
.badge-neutral { background: rgba(148, 163, 184, 0.15); border-color: #94a3b8; color: #cbd5e1; }
/* Rediseño núcleo Agenda — punto 4: colores FIJOS (no ligados a la paleta
   de acento del usuario, mismo criterio que badge-success/warning/danger de
   arriba) para Evento (morado) y Tarea completada (azul) — los otros 3
   estados (tarea pendiente=amarillo, examen=rojo, evento feriado=verde) ya
   los cubren badge-warning/badge-danger/badge-success de arriba, sin
   necesidad de clases nuevas. */
.badge-purple  { background: rgba(168, 85, 247, 0.15); border-color: #a855f7; color: #c084fc; }
.badge-info    { background: rgba(59, 130, 246, 0.15); border-color: #3b82f6; color: #60a5fa; }
.badge-accent  {
  /* FIX v3: fondo pastel sólido y autocontenido (mezclarHex accent1+blanco
     en tema.js) SOLO bajo paleta personalizada — no depende de qué haya
     detrás, por eso funciona en cualquier fondo. Texto y borde son los de
     siempre, sin condición, igual en las 14 paletas. */
  background: var(--badge-bg-solid, var(--accent-1-10));
  border-color: var(--accent-1);
  color: var(--accent-2);
}

/* ---- Bug 4 (v7.1): las 4 badges de arriba usaban colores de texto pastel
   pensados solo para fondo oscuro (contraste ~9-12:1 en dark, pero apenas
   1.4-2.6:1 en claro — badge-neutral era casi invisible). Como esto no
   depende de paleta (son colores fijos), se corrige una sola vez para
   modo claro con tonos más oscuros y saturados del mismo color base,
   verificados por encima de 4.5:1 contra su propio fondo translúcido. */
[data-mode="light"] .badge-success { color: #047857; }
[data-mode="light"] .badge-warning { color: #92400e; }
[data-mode="light"] .badge-danger  { color: #991b1b; }
[data-mode="light"] .badge-neutral { color: #334155; }
[data-mode="light"] .badge-purple  { color: #7e22ce; }
[data-mode="light"] .badge-info    { color: #1d4ed8; }

/* ---- Switch (toggle on/off) ---- */
.switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track {
  position: absolute; inset: 0; cursor: pointer;
  background: rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-pill);
  transition: background var(--transition-fast);
}
.switch .thumb {
  position: absolute; height: 20px; width: 20px; left: 3px; top: 3px;
  background: #fff; border-radius: 50%;
  transition: transform var(--transition-fast);
  box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.switch input:checked + .track { background: var(--gradient-accent-alt2, var(--gradient-accent)); }
.switch input:checked + .track .thumb { transform: translateX(20px); }
.switch input:focus-visible + .track { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* ---- Switch de tema: mismo estilo que el switch base, sin iconos ---- */
.switch-tema { width: 46px; height: 26px; }
.switch-tema .thumb { width: 20px; height: 20px; top: 3px; left: 3px; }
.switch-tema input:checked + .track .thumb { transform: translateX(20px); }

/* Ronda de ajustes visuales #6 — foco 100% en que "Es feriado" no se
   corte NUNCA (ver comentario largo en index.html): se elimina toda
   animación de tamaño para esta fila. Ya no hay reglas acá — el toggle es
   un simple display:none <-> flex vía la clase global .oculto, que no
   puede "cortarse" a media transición porque no hay transición. */

/* ---- Checkbox custom ---- */
.checkbox { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.checkbox input { display: none; }
.checkbox .box {
  width: 22px; height: 22px; border-radius: 7px;
  border: 1.5px solid var(--border-glass);
  display: flex; align-items: center; justify-content: center;
  transition: all var(--transition-fast);
  background: rgba(0,0,0,0.15);
}
.checkbox input:checked + .box {
  background: var(--gradient-accent);
  border-color: transparent;
}
.checkbox input:checked + .box::after {
  content: "✓"; color: #fff; font-size: 14px; font-weight: 700;
}

/* Utilidad genérica: ocultar cualquier elemento sin sacarlo del DOM ni
   reacomodar layout de hermanos. Bug 2026-08-08: antes solo existían
   versiones de .oculto ya acopladas a un padre puntual (.perfil-popover.oculto,
   .overlay-cargando.oculto, .ppz-degradado-*.oculto) — cualquier código nuevo
   que usara classList.toggle("oculto", ...) sobre OTRO elemento (como el
   bloque "Pasás raspando con" en config-ajustes.js) no ocultaba nada, porque
   ninguna regla lo cubría. Esta versión genérica cierra ese hueco sin pisar
   las anteriores (mismo valor, sin conflicto de especificidad).

   Corrección 2026-08-17: sí había conflicto — .row (más abajo en este
   archivo) define display:flex con la MISMA especificidad, y por orden de
   declaración le ganaba a este display:none cuando un elemento combinaba
   class="row oculto" (ej. #fila-agenda-es-feriado, #info-agenda-fila-
   completada). El elemento quedaba SIEMPRE visible pese al toggle de
   "oculto" en JS. !important a propósito: es una utilidad de "ocultar sí o
   sí", no debe depender del orden en que se declaren otras clases nuevas. */
.oculto { display: none !important; }

/* ---- Inputs ---- */
.form-input, .form-select, .form-textarea {
  width: 100%;
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  padding: 12px 14px;
  font-size: 0.95rem;
  font-family: var(--font-body);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  /* Agenda (inputs type="date"/"time" en el modal de alta/edición): sin
     esto, el ícono nativo del selector de fecha/hora queda pintado según el
     tema del SISTEMA OPERATIVO, no el de la app — en modo oscuro de la app
     con SO en modo claro (o viceversa) el ícono queda casi invisible sobre
     el fondo. .form-select ya resolvía esto para sí mismo; se sube acá para
     que lo hereden TODOS los inputs, date/time incluidos. */
  color-scheme: dark;
}
[data-mode="light"] .form-input,
[data-mode="light"] .form-select,
[data-mode="light"] .form-textarea { background: rgba(0,0,0,0.04); color-scheme: light; }
/* v1.15.12 — el <textarea> nativo trae su propio "resize handle" en la
   esquina inferior derecha que por default permite estirar en las 2
   direcciones; el ancho no tiene sentido acá (el campo ya ocupa el 100%
   del modal) — se deja solo el resize vertical, que sí sirve para hacer
   más alto el cuadro de notas si hace falta. */
.form-textarea { resize: vertical; }

/* v1.15.10: el <select> nativo, aparte de fondo/borde/texto (ya cubiertos
   arriba junto con .form-input), tiene DOS partes que el navegador sigue
   pintando con su propio criterio si no se le dice lo contrario:
   1) la flechita — viene del tema del SO, no de esta hoja de estilos.
   2) la lista desplegable de <option> al hacer click — en Chrome/Edge
      hereda color-scheme (no el CSS del <select>), así que sin esto
      renderiza con fondo blanco fijo aunque el <select> cerrado ya se vea
      oscuro. color-scheme resuelve el popup; el estilo de <option> de abajo
      cubre Firefox, que si respeta ese CSS. */
.form-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  color-scheme: dark;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'><path d='M1 1L7 7L13 1' stroke='%238892b0' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  padding-right: 38px;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.form-select:hover { border-color: var(--accent-1-40, var(--accent-1)); }
[data-mode="light"] .form-select { color-scheme: light; }
.form-select option {
  background: var(--bg-panel);
  color: var(--text-primary);
}

/* v1.15.11 (2026-08-08): dropdown propio para reemplazar al <select>
   nativo en los casos donde SÍ importa que el popup se vea bien de verdad
   (ver "Escala de notas" en config-ajustes.js) — el <select> real queda
   oculto como fuente de valor/evento, esto es 100% la parte visible. */
.select-custom { position: relative; width: 100%; }
.select-custom-boton {
  width: 100%;
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'><path d='M1 1L7 7L13 1' stroke='%238892b0' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  padding-right: 38px;
}
.select-custom-boton:hover { border-color: var(--accent-1-40, var(--accent-1)); }
.select-custom-boton[aria-expanded="true"] {
  border-color: var(--accent-1);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-1) 25%, transparent);
}
.select-custom-lista {
  position: absolute;
  z-index: 9999;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  max-height: 260px;
  overflow-y: auto;
  /* Ronda de ajustes visuales #7: --bg-panel es casi transparente por
     diseño, y este
     dropdown flota sobre contenido detrás (el resto del modal/la lista de
     materias) que se leía mezclado con las opciones. --bg-header-solido
     mantiene el mismo tono por tema pero opaco. */
  background: var(--bg-header-solido);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--border-glass, rgba(255,255,255,0.12));
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-float);
}
.select-custom-opcion {
  padding: 9px 12px;
  border-radius: calc(var(--radius-sm) - 4px);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 0.9rem;
}
.select-custom-opcion:hover { background: rgba(255,255,255,0.08); }
.select-custom-opcion.activa {
  background: var(--accent-1);
  color: var(--on-accent, #ffffff);
  font-weight: 600;
}

.form-input:focus, .form-select:focus, .form-textarea:focus {
  outline: none;
  border-color: var(--accent-1);
  box-shadow: 0 0 0 3px var(--accent-1-20);
}
.form-label { display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-muted); margin-bottom: 6px; }

/* ---- Botón discreto de "quitar/desvincular" (ícono, no btn ancho) ----
   2026-08-09 (pedido explícito, varias veces: "los botones de quitar no
   los hagas tan gordos [...] hazlos como un boton de eliminar [...] pero
   estan gordos y feos"): reemplaza los antiguos <button class="btn
   btn-secondary">Quitar</button> anchos por un ícono chico y discreto,
   usado en comunidad.js (desvincular materia desde Editar profesor) y en
   plan-detalle.js (desvincular profesor desde el Historial). */
.btn-icono-quitar {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.btn-icono-quitar:hover,
.btn-icono-quitar:focus-visible {
  background: color-mix(in srgb, var(--color-danger, #e5484d) 12%, transparent);
  color: var(--color-danger, #e5484d);
  border-color: color-mix(in srgb, var(--color-danger, #e5484d) 35%, transparent);
  outline: none;
}

/* ---- Modal ---- */
.modal-overlay {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
  padding: 16px;
}
/* v7.1: en modo claro, el mismo overlay negro al 65% se filtra a través del
   glass-card translúcido (su backdrop-filter "ve" ese fondo casi negro), así
   que la tarjeta terminaba con un tinte gris apagado en vez de verse fresca
   y luminosa como en modo oscuro. Se usa un overlay más claro y con menos
   opacidad — sigue oscureciendo el fondo lo suficiente para dar foco al
   modal, pero deja que el glass-card se vea blanco/brillante de verdad. */
[data-mode="light"] .modal-overlay {
  background: rgba(30, 41, 59, 0.22);
}
.modal-card {
  /* v5 #2: usa el ancho disponible en vez de quedar angosta "porque sí" */
  width: min(560px, calc(100vw - 32px));
  max-height: 85vh;
  overflow-y: auto;
  position: relative;
  /* Ronda de ajustes visuales #2 — fix "Es feriado" se ve cortado: en
     iOS Safari/WebView, un contenedor con overflow-y:auto no siempre
     recalcula su scrollHeight cuando un hijo cambia de display:none a
     visible en el mismo tick (bug conocido de esos motores con flex +
     overflow) — el contenido nuevo (acá, la fila "Es feriado" al elegir
     tipo "Evento") queda recortado hasta que algo más fuerza un reflow.
     -webkit-overflow-scrolling:touch además de forzar un reflow manual en
     JS (ver actualizarVisibilidadFeriado en agenda-modal.js) cubre el caso
     desde los dos lados. */
  -webkit-overflow-scrolling: touch;
}
.modal-card-ancha { width: min(760px, calc(100vw - 32px)); }

/* ---- Botón de cerrar (X) propio del sistema de diseño, en TODOS los modales ---- */
.modal-x-close {
  position: absolute;
  top: 14px; right: 14px;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border-glass);
  background: rgba(255,255,255,0.06);
  color: var(--text-secondary);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast);
  z-index: 5;
}
.modal-x-close:hover { background: var(--accent-1-10); color: var(--text-primary); }
.modal-x-close:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* Pedido puntual: la X de la tarjeta de info de bloque en Horario, sin el
   fondo/borde circular de .modal-x-close (que sigue igual en el resto de
   modales de la app) — deja solo el glyph. */
.horario-info-x-plana {
  background: none;
  border: none;
}
.horario-info-x-plana:hover { background: none; }

/* ---- Link de solo-texto (sin forma de botón), ej. "Es requisito" ---- */
.link-plano {
  background: none; border: none; padding: 0; margin: 0;
  color: var(--accent-2);
  font-weight: 600;
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
  align-self: flex-start;
}

/* ---- Scrollbars propias de la app, con la estética de la paleta activa ---- */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--accent-1) var(--bg-panel);
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg-panel); border-radius: var(--radius-pill); }
::-webkit-scrollbar-thumb {
  background: var(--gradient-accent-alt, var(--gradient-accent));
  border-radius: var(--radius-pill);
  border: 2px solid var(--bg-panel);
}
::-webkit-scrollbar-thumb:hover { background: var(--gradient-accent); }

/* ---- Paleta swatch selector ----
   Flex en vez de grid: así la última fila (incompleta) queda centrada.
   13 paletas / 7 columnas fijas = 2 líneas exactas (7 arriba, 6 abajo, la
   fila de arriba con más elementos que la de abajo). El número de columnas
   NO cambia con el tamaño de pantalla — lo que se adapta es el tamaño de
   cada cuadro (siempre cuadrado, gracias a aspect-ratio), así nunca hay
   más de 2 líneas. */
.palette-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  max-width: 560px;
  margin: 0 auto;
}
.palette-swatch {
  flex: 0 0 calc((100% - 6 * 10px) / 7); /* 7 por fila, siempre */
  max-width: 64px;
  aspect-ratio: 1;
  border-radius: var(--radius-md);
  cursor: pointer;
  border: 2px solid transparent;
  background-clip: padding-box; /* el borde nunca "come" el degradado de la esquina */
  overflow: hidden; /* el degradado nunca se sale de la esquina redondeada */
  /* v7.1 (Bug 5, sigue roto de antes): el hack anterior con
     -webkit-mask-image (radial-gradient con dos stops en 100%) generaba una
     transición filo-de-navaja justo en las esquinas GEOMÉTRICAS de la caja,
     que el navegador puede rasterizar de forma inconsistente ahí mismo —
     exactamente donde se nota el corte cuadrado. Se reemplaza por la forma
     estándar de forzar que Safari le dé su propia capa compuesta al
     elemento (sin tocar la geometría del borde redondeado): */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  display: flex; align-items: flex-end; justify-content: center;
  padding-bottom: 6px;
  font-size: 0.65rem;
  font-weight: 700;
  text-shadow: 0 1px 3px rgba(0,0,0,0.35);
  transition: transform var(--transition-fast), border-color var(--transition-fast);
}
@media (max-width: 420px) {
  /* Sigue habiendo 7 por fila; solo se reduce el espaciado y el tope de
     tamaño para que quepan cómodas en pantallas angostas. */
  .palette-grid { gap: 6px; }
  .palette-swatch { flex-basis: calc((100% - 6 * 6px) / 7); max-width: 42px; }
  /* Bug 5: un poco más compactos para que quepan más pills por fila antes
     de tener que pasar a la siguiente línea. */
  .pill-item { padding: 7px 12px; font-size: 0.8rem; }
}
.palette-swatch:hover { transform: translateY(-2px) translateZ(0); }
.palette-swatch.selected { border-color: var(--text-primary); }

/* ---- Utilidades de layout ---- */
.stack { display: flex; flex-direction: column; gap: 14px; }
.row { display: flex; align-items: center; gap: 10px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

/* v1.13: chips de Categorías — CSS Grid con reparto equitativo por filas
   (nunca una fila casi vacía: 6→3+3, 11→4+4+3) y posición explícita de
   cada chip (grid-row/grid-column, ver distribuirCategoriasEnGrid en
   plan-categorias.js) para que las filas completas queden alineadas en
   columnas de verdad — el ancho de cada columna es el del chip más ancho,
   así ningún chip se estira. `grid-template-columns` se recalcula en
   cada resize (depende de cuántos caben por fila). `justify-content:center`
   centra la cuadrícula completa dentro de la tarjeta de Categorías. */
.categorias-grid {
  display: grid;
  gap: 8px;
  justify-content: center;
}

/* v1.9.8: "Editar plan" + "Añadir materia" (barra de acciones del Plan de
   Estudios) — el ancho idéntico entre ambos lo fuerza JS
   (igualarAnchoBotones en plan-vista-lista.js) tras medir el layout real;
   esta clase solo define el acomodo en fila. */
.botones-editar-grupo { display: inline-flex; gap: 10px; flex-wrap: wrap; }
.muted { color: var(--text-muted); font-size: 0.85rem; }

/* =========================================================================
   PALETAS — cada una define acentos y fondos para modo oscuro y claro.

   Bug 4 (v7.1, auditoría de contraste): --accent-2 se usa como color de
   TEXTO en varios lugares (badge-accent, "Es requisito", "Historial",
   estrella de plan activo, etc. — ver grep de "color: var(--accent-2)").
   En 7 paletas, el --accent-2 de modo claro era una versión CLARA del
   acento (pensada para lucir bien como highlight, no como texto legible),
   dando contraste tan bajo como 2.21:1 contra el fondo (azucarado) — muy
   por debajo del mínimo WCAG AA de 4.5:1 para texto. Se oscureció
   --accent-2 en esas 7 paletas (rojo, azul, verde, cyan, rosado, amarillo,
   azucarado) manteniendo el mismo matiz, todas verificadas ≥4.5:1.
   ========================================================================= */

/* ---------- BLANCO (neutro/plata) ---------- */
[data-palette="blanco"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #101114; --bg-card: rgba(255,255,255,0.05); --bg-header-solido: rgb(28, 29, 32); --bg-panel: rgba(255,255,255,0.03);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#F1F2F4; --text-secondary:#B7BAC1; --text-muted:#888C94;
  --accent-1:#CBD5E1; --accent-2:#F8FAFC; --gradient-accent: linear-gradient(90deg,#94A3B8,#F1F5F9);
  --accent-glow-1: rgba(203,213,225,0.18); --accent-glow-2: rgba(248,250,252,0.10);
  --accent-1-10: rgba(203,213,225,0.10); --accent-1-20: rgba(203,213,225,0.25); --color-danger:#f87171; --on-accent:#1E293B;
}
[data-palette="blanco"][data-mode="light"] {
  --bg-canvas: #F7F8FA; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(253, 253, 254); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(15,23,42,0.08); --text-primary:#0F172A; --text-secondary:#475569; --text-muted:#7B8697;
  --accent-1:#64748B; --accent-2:#334155; --gradient-accent: linear-gradient(90deg,#94A3B8,#475569);
  --accent-glow-1: rgba(100,116,139,0.10); --accent-glow-2: rgba(51,65,85,0.06);
  --accent-1-10: rgba(100,116,139,0.10); --accent-1-20: rgba(100,116,139,0.25); --color-danger:#dc2626; --on-accent:#1E293B;
}

/* ---------- NEGRO (deep black) ---------- */
[data-palette="negro"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #000000; --bg-card: rgba(255,255,255,0.04); --bg-header-solido: rgb(10, 10, 10); --bg-panel: rgba(255,255,255,0.02);
  --border-glass: rgba(255,255,255,0.08); --text-primary:#FFFFFF; --text-secondary:#A3A3A3; --text-muted:#707070;
  --accent-1:#3F3F46; --accent-2:#71717A; --gradient-accent: linear-gradient(90deg,#18181B,#52525B);
  --accent-glow-1: rgba(63,63,70,0.30); --accent-glow-2: rgba(113,113,122,0.15);
  --accent-1-10: rgba(63,63,70,0.14); --accent-1-20: rgba(63,63,70,0.30); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="negro"][data-mode="light"] {
  --bg-canvas: #FAFAFA; --bg-card: rgba(255,255,255,0.8); --bg-header-solido: rgb(254, 254, 254); --bg-panel: rgba(255,255,255,0.65);
  --border-glass: rgba(0,0,0,0.08); --text-primary:#000000; --text-secondary:#3F3F46; --text-muted:#71717A;
  --accent-1:#18181B; --accent-2:#000000; --gradient-accent: linear-gradient(90deg,#27272A,#000000);
  --accent-glow-1: rgba(0,0,0,0.10); --accent-glow-2: rgba(0,0,0,0.06);
  --accent-1-10: rgba(0,0,0,0.08); --accent-1-20: rgba(0,0,0,0.18); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- ROJO ---------- */
[data-palette="rojo"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #170808; --bg-card: rgba(220,38,38,0.08); --bg-header-solido: rgb(39, 10, 10); --bg-panel: rgba(220,38,38,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#FDEEEE; --text-secondary:#E3AFAF; --text-muted:#AC7D7D;
  --accent-1:#DC2626; --accent-2:#F87171; --gradient-accent: linear-gradient(90deg,#B91C1C,#F87171);
  --accent-glow-1: rgba(220,38,38,0.28); --accent-glow-2: rgba(248,113,113,0.18);
  --accent-1-10: rgba(220,38,38,0.12); --accent-1-20: rgba(220,38,38,0.28); --color-danger:#fb923c; --on-accent:#ffffff;
}
[data-palette="rojo"][data-mode="light"] {
  --bg-canvas: #FEF2F2; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(255, 252, 252); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(185,28,28,0.10); --text-primary:#450A0A; --text-secondary:#7F1D1D; --text-muted:#A66A6A;
  --accent-1:#B91C1C; --accent-2:#991B1B; --gradient-accent: linear-gradient(90deg,#B91C1C,#EF4444);
  --accent-glow-1: rgba(185,28,28,0.12); --accent-glow-2: rgba(239,68,68,0.10);
  --accent-1-10: rgba(185,28,28,0.10); --accent-1-20: rgba(185,28,28,0.22); --color-danger:#c2410c; --on-accent:#ffffff;
}

/* ---------- GRIS ---------- */
[data-palette="gris"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #121316; --bg-card: rgba(255,255,255,0.05); --bg-header-solido: rgb(30, 31, 34); --bg-panel: rgba(255,255,255,0.03);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#F1F1F2; --text-secondary:#B4B5B9; --text-muted:#84858C;
  --accent-1:#6B7280; --accent-2:#9CA3AF; --gradient-accent: linear-gradient(90deg,#4B5563,#9CA3AF);
  --accent-glow-1: rgba(107,114,128,0.20); --accent-glow-2: rgba(156,163,175,0.12);
  --accent-1-10: rgba(107,114,128,0.12); --accent-1-20: rgba(107,114,128,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="gris"][data-mode="light"] {
  --bg-canvas: #F4F4F5; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(252, 252, 252); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(15,23,42,0.08); --text-primary:#18181B; --text-secondary:#52525B; --text-muted:#8B8B93;
  --accent-1:#52525B; --accent-2:#27272A; --gradient-accent: linear-gradient(90deg,#71717A,#3F3F46);
  --accent-glow-1: rgba(82,82,91,0.10); --accent-glow-2: rgba(39,39,42,0.06);
  --accent-1-10: rgba(82,82,91,0.10); --accent-1-20: rgba(82,82,91,0.25); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- AZUL ---------- */
[data-palette="azul"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #0A0E17; --bg-card: rgba(37,99,235,0.07); --bg-header-solido: rgb(12, 20, 38); --bg-panel: rgba(37,99,235,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#F1F5FE; --text-secondary:#AEC0DD; --text-muted:#7C8CA8;
  --accent-1:#2563EB; --accent-2:#38BDF8; --gradient-accent: linear-gradient(90deg,#2563EB,#38BDF8);
  --accent-glow-1: rgba(37,99,235,0.28); --accent-glow-2: rgba(56,189,248,0.18);
  --accent-1-10: rgba(37,99,235,0.12); --accent-1-20: rgba(37,99,235,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="azul"][data-mode="light"] {
  --bg-canvas: #F2F6FE; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(252, 253, 255); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(37,99,235,0.10); --text-primary:#0B1B3B; --text-secondary:#33487A; --text-muted:#7284AC;
  --accent-1:#2563EB; --accent-2:#0369A1; --gradient-accent: linear-gradient(90deg,#2563EB,#0EA5E9);
  --accent-glow-1: rgba(37,99,235,0.12); --accent-glow-2: rgba(14,165,233,0.10);
  --accent-1-10: rgba(37,99,235,0.10); --accent-1-20: rgba(37,99,235,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- VERDE ---------- */
[data-palette="verde"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #08120D; --bg-card: rgba(22,163,74,0.07); --bg-header-solido: rgb(9, 28, 17); --bg-panel: rgba(22,163,74,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#EFFDF4; --text-secondary:#AFD8BE; --text-muted:#7DA48D;
  --accent-1:#16A34A; --accent-2:#4ADE80; --gradient-accent: linear-gradient(90deg,#15803D,#4ADE80);
  --accent-glow-1: rgba(22,163,74,0.28); --accent-glow-2: rgba(74,222,128,0.16);
  --accent-1-10: rgba(22,163,74,0.12); --accent-1-20: rgba(22,163,74,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="verde"][data-mode="light"] {
  --bg-canvas: #F1FAF3; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(252, 254, 252); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(21,128,61,0.10); --text-primary:#052E16; --text-secondary:#2B5D3F; --text-muted:#6C8F79;
  --accent-1:#15803D; --accent-2:#047857; --gradient-accent: linear-gradient(90deg,#15803D,#059669);
  --accent-glow-1: rgba(21,128,61,0.12); --accent-glow-2: rgba(5,150,105,0.10);
  --accent-1-10: rgba(21,128,61,0.10); --accent-1-20: rgba(21,128,61,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- CYAN ---------- */
[data-palette="cyan"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #06141A; --bg-card: rgba(8,145,178,0.08); --bg-header-solido: rgb(6, 30, 38); --bg-panel: rgba(8,145,178,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#EAFCFD; --text-secondary:#A6D9DF; --text-muted:#75A6AD;
  --accent-1:#0891B2; --accent-2:#22D3EE; --gradient-accent: linear-gradient(90deg,#0E7490,#22D3EE);
  --accent-glow-1: rgba(8,145,178,0.28); --accent-glow-2: rgba(34,211,238,0.18);
  --accent-1-10: rgba(8,145,178,0.12); --accent-1-20: rgba(8,145,178,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="cyan"][data-mode="light"] {
  --bg-canvas: #EEFBFD; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(251, 254, 254); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(14,116,144,0.10); --text-primary:#053442; --text-secondary:#2C6270; --text-muted:#6D949C;
  --accent-1:#0E7490; --accent-2:#155E75; --gradient-accent: linear-gradient(90deg,#0E7490,#06B6D4);
  --accent-glow-1: rgba(14,116,144,0.12); --accent-glow-2: rgba(6,182,212,0.10);
  --accent-1-10: rgba(14,116,144,0.10); --accent-1-20: rgba(14,116,144,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- MORADO ---------- */
[data-palette="morado"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #100B1A; --bg-card: rgba(124,58,237,0.08); --bg-header-solido: rgb(25, 15, 43); --bg-panel: rgba(124,58,237,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#F5EEFE; --text-secondary:#CBB3E8; --text-muted:#9A82B8;
  --accent-1:#7C3AED; --accent-2:#C084FC; --gradient-accent: linear-gradient(90deg,#6D28D9,#C084FC);
  --accent-glow-1: rgba(124,58,237,0.28); --accent-glow-2: rgba(192,132,252,0.18);
  --accent-1-10: rgba(124,58,237,0.12); --accent-1-20: rgba(124,58,237,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="morado"][data-mode="light"] {
  --bg-canvas: #F7F2FE; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(253, 252, 255); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(109,40,217,0.10); --text-primary:#2B0A54; --text-secondary:#5B3A8A; --text-muted:#8874A8;
  --accent-1:#6D28D9; --accent-2:#9333EA; --gradient-accent: linear-gradient(90deg,#6D28D9,#A855F7);
  --accent-glow-1: rgba(109,40,217,0.12); --accent-glow-2: rgba(168,85,247,0.10);
  --accent-1-10: rgba(109,40,217,0.10); --accent-1-20: rgba(109,40,217,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- ROSADO ---------- */
[data-palette="rosado"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #170B12; --bg-card: rgba(219,39,119,0.08); --bg-header-solido: rgb(39, 13, 26); --bg-panel: rgba(219,39,119,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#FDF0F6; --text-secondary:#E7B3CE; --text-muted:#B482A0;
  --accent-1:#DB2777; --accent-2:#F472B6; --gradient-accent: linear-gradient(90deg,#BE185D,#F472B6);
  --accent-glow-1: rgba(219,39,119,0.28); --accent-glow-2: rgba(244,114,182,0.18);
  --accent-1-10: rgba(219,39,119,0.12); --accent-1-20: rgba(219,39,119,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="rosado"][data-mode="light"] {
  --bg-canvas: #FDF1F7; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(254, 252, 253); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(190,24,93,0.10); --text-primary:#4A0523; --text-secondary:#87285A; --text-muted:#AD7692;
  --accent-1:#BE185D; --accent-2:#9D174D; --gradient-accent: linear-gradient(90deg,#BE185D,#EC4899);
  --accent-glow-1: rgba(190,24,93,0.12); --accent-glow-2: rgba(236,72,153,0.10);
  --accent-1-10: rgba(190,24,93,0.10); --accent-1-20: rgba(190,24,93,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- ÍNDIGO ---------- */
[data-palette="indigo"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #0B0C1E; --bg-card: rgba(79,70,229,0.08); --bg-header-solido: rgb(16, 17, 46); --bg-panel: rgba(79,70,229,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#EFEEFE; --text-secondary:#BFBBEB; --text-muted:#8B87BE;
  --accent-1:#4F46E5; --accent-2:#818CF8; --gradient-accent: linear-gradient(90deg,#4338CA,#818CF8);
  --accent-glow-1: rgba(79,70,229,0.28); --accent-glow-2: rgba(129,140,248,0.18);
  --accent-1-10: rgba(79,70,229,0.12); --accent-1-20: rgba(79,70,229,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="indigo"][data-mode="light"] {
  --bg-canvas: #F1F1FD; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(252, 252, 254); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(67,56,202,0.10); --text-primary:#1B1650; --text-secondary:#413A8C; --text-muted:#7C77AB;
  --accent-1:#4338CA; --accent-2:#4F46E5; --gradient-accent: linear-gradient(90deg,#4338CA,#6366F1);
  --accent-glow-1: rgba(67,56,202,0.12); --accent-glow-2: rgba(99,102,241,0.10);
  --accent-1-10: rgba(67,56,202,0.10); --accent-1-20: rgba(67,56,202,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- AMARILLO ---------- */
[data-palette="amarillo"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #16130A; --bg-card: rgba(202,138,4,0.08); --bg-header-solido: rgb(36, 29, 10); --bg-panel: rgba(202,138,4,0.04);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#FDF9EC; --text-secondary:#E3D19D; --text-muted:#AC9C6C;
  --accent-1:#CA8A04; --accent-2:#FDE047; --gradient-accent: linear-gradient(90deg,#A16207,#FDE047);
  --accent-glow-1: rgba(202,138,4,0.26); --accent-glow-2: rgba(253,224,71,0.16);
  --accent-1-10: rgba(202,138,4,0.12); --accent-1-20: rgba(202,138,4,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="amarillo"][data-mode="light"] {
  --bg-canvas: #FEFBEB; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(255, 254, 250); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(161,98,7,0.10); --text-primary:#422C05; --text-secondary:#78571A; --text-muted:#A2895C;
  --accent-1:#A16207; --accent-2:#854D0E; --gradient-accent: linear-gradient(90deg,#A16207,#EAB308);
  --accent-glow-1: rgba(161,98,7,0.12); --accent-glow-2: rgba(234,179,8,0.10);
  --accent-1-10: rgba(161,98,7,0.10); --accent-1-20: rgba(161,98,7,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- DORADO ---------- */
[data-palette="dorado"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #170F08; --bg-card: rgba(180,83,9,0.09); --bg-header-solido: rgb(37, 21, 8); --bg-panel: rgba(180,83,9,0.05);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#FDF3E4; --text-secondary:#E3C296; --text-muted:#AF9066;
  --accent-1:#B45309; --accent-2:#FBBF24; --gradient-accent: linear-gradient(90deg,#92400E,#FBBF24);
  --accent-glow-1: rgba(180,83,9,0.28); --accent-glow-2: rgba(251,191,36,0.18);
  --accent-1-10: rgba(180,83,9,0.12); --accent-1-20: rgba(180,83,9,0.28); --color-danger:#f87171; --on-accent:#ffffff;
}
[data-palette="dorado"][data-mode="light"] {
  --bg-canvas: #FDF6EB; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(254, 253, 250); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(146,64,14,0.10); --text-primary:#3A1E04; --text-secondary:#6E4718; --text-muted:#9C7B4F;
  --accent-1:#92400E; --accent-2:#B45309; --gradient-accent: linear-gradient(90deg,#92400E,#D97706);
  --accent-glow-1: rgba(146,64,14,0.12); --accent-glow-2: rgba(217,119,6,0.10);
  --accent-1-10: rgba(146,64,14,0.10); --accent-1-20: rgba(146,64,14,0.22); --color-danger:#dc2626; --on-accent:#ffffff;
}

/* ---------- AZUCARADO ----------
   Los botones/elementos NO mezclan todos los colores en una sola mancha:
   cada uno usa un degradado horizontal de solo 2 tonos, pero se turnan
   distintas parejas de colores (--gradient-accent / -alt / -alt2) para que
   haya variedad de un botón a otro sin que ninguno se vea "sucio". El
   swatch del selector de temas sí conserva el efecto disperso/artístico
   (ver FONDO_PREVIEW_AZUCARADO en app.js), ya que ahí solo es una muestra. */
/* Recoloreado a tonos pastel FRÍOS (rosa → fucsia → violeta → azul → cyan),
   sin verde ni amarillo, para que se vea más suave y menos "bandera". */
[data-palette="azucarado"]:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #100E1C; --bg-card: rgba(255,255,255,0.05); --bg-header-solido: rgb(28, 26, 39); --bg-panel: rgba(255,255,255,0.03);
  --border-glass: rgba(255,255,255,0.10); --text-primary:#F6F1FB; --text-secondary:#CBC0DE; --text-muted:#8F84A8;
  --accent-1:#C599E8; --accent-2:#7DD3DB;
  --gradient-accent: linear-gradient(90deg, #F5A9D0, #B8A6F0);      /* rosa → violeta */
  --gradient-accent-alt: linear-gradient(90deg, #C599E8, #9DC0F5);  /* fucsia → azul */
  --gradient-accent-alt2: linear-gradient(90deg, #9DC0F5, #8FE3EA); /* azul → cyan */
  --accent-glow-1: rgba(184,166,240,0.28); --accent-glow-2: rgba(245,169,208,0.18);
  --accent-1-10: rgba(184,166,240,0.12); --accent-1-20: rgba(184,166,240,0.28); --color-danger:#f87171; --on-accent:#2A1E38;
}
[data-palette="azucarado"][data-mode="light"] {
  --bg-canvas: #FBF7FD; --bg-card: rgba(255,255,255,0.75); --bg-header-solido: rgb(254, 253, 254); --bg-panel: rgba(255,255,255,0.6);
  --border-glass: rgba(184,166,240,0.12); --text-primary:#2A1E38; --text-secondary:#5C4A72; --text-muted:#8D7BA0;
  --accent-1:#A876D9; --accent-2:#1E7A85;
  --gradient-accent: linear-gradient(90deg, #F0A0CC, #B091EA);      /* rosa → violeta */
  --gradient-accent-alt: linear-gradient(90deg, #B091EA, #7FAEEE);  /* violeta → azul */
  --gradient-accent-alt2: linear-gradient(90deg, #7FAEEE, #6ED7DF); /* azul → cyan */
  --accent-glow-1: rgba(168,118,217,0.14); --accent-glow-2: rgba(240,160,204,0.12);
  --accent-1-10: rgba(168,118,217,0.10); --accent-1-20: rgba(168,118,217,0.22); --color-danger:#dc2626; --on-accent:#2A1E38;
}

/* =========================================================================
   LAYOUT DE LA APP (shell + sidebar) — responsivo real
   Nada aquí depende del ancho de pantalla para decidir colores/paleta;
   data-palette y data-mode siempre se leen desde <html> sin importar el
   viewport (ver punto 2 del prompt de ajustes).
   ========================================================================= */

/* Modo oscuro profundo: superficies casi negras con los acentos y degradados
   heredados de la paleta oscura seleccionada. */
html[data-mode="true-dark"][data-palette] {
  color-scheme: dark;
  --bg-canvas: #000000;
  --bg-header-solido: #050505;
  --bg-card: color-mix(in srgb, var(--accent-1) 5%, #090909);
  --bg-panel: color-mix(in srgb, var(--accent-1) 3%, #050505);
  --border-glass: color-mix(in srgb, var(--accent-1) 12%, #242424);
  --text-primary: #F5F5F5;
  --text-secondary: #C4C4C4;
  --text-muted: #929292;
  --shadow-glass: 0 20px 48px rgba(0,0,0,.72), 0 1px 3px rgba(255,255,255,.025);
  --shadow-float: 0 10px 28px rgba(0,0,0,.68);
}
html[data-mode="true-dark"][data-palette] .glass-card,
html[data-mode="true-dark"][data-palette] .glass-panel {
  border-color: color-mix(in srgb, var(--accent-1) 12%, #242424);
}
/* En oscuro optimizado se prioriza el negro puro y la lectura sencilla;
   el modo oscuro normal conserva sus superficies con acento y profundidad. */
html[data-mode="true-dark"][data-rendimiento="reducido"] {
  --bg-canvas:#000; --bg-header-solido:#030303; --bg-card:#080808; --bg-panel:#050505;
  --border-glass:rgba(255,255,255,.055); --shadow-glass:none; --shadow-float:0 8px 20px rgba(0,0,0,.55);
}
html[data-mode="true-dark"][data-rendimiento="reducido"] .glass-card,
html[data-mode="true-dark"][data-rendimiento="reducido"] .glass-panel { border-color:rgba(255,255,255,.055); box-shadow:none; }
#app-shell, #app-shell * { box-sizing: border-box; }

.app-layout {
  display: flex;
  min-height: 100vh;
  width: 100%;
  max-width: 100vw;
  /* Fix (2026-08-26 — "el sidebar se pierde al hacer scroll en PC", pedido
     de Krys): acá había overflow-x:hidden. El problema de fondo: CUALQUIER
     valor de overflow distinto de "visible"/"clip" (incluido "hidden") por
     spec convierte al elemento en un "contenedor de scroll" — y eso es
     exactamente lo que usa position:sticky para decidir contra qué caja
     "engancharse". #app-sidebar terminaba con sticky calculado contra
     .app-layout (que nunca se recorta de verdad, porque su alto crece
     libre con el contenido) en vez de contra el scroll real del documento,
     así que nunca se sentía "pegado": se iba con el resto de la página.
     overflow-x: clip recorta el desborde horizontal exactamente igual que
     hidden, pero por spec NO establece un contenedor de scroll — el
     sidebar vuelve a quedar sticky contra el scroll real de la página.
     (Nota: se descartó mover el overflow a html/body — tocar el overflow
     de la raíz del documento, sobre todo junto con background-attachment:
     fixed que ya tiene <body>, es una combinación conocida por romper el
     scroll nativo en varios navegadores; clip acá mismo evita ese riesgo
     por completo.) */
  overflow-x: clip;
}

/* ---- Asistente IA: bloqueo de scroll del documento + ocultar topbar con
   teclado abierto (ver sincronizarEstadoAsistente en asistente.js). La
   sección de Asistente es un bloque estático de pantalla completa con su
   propio scroll interno (#asistente-chat-scroll) — el documento en sí NUNCA
   debe poder scrollear mientras esté visible. asistente.js agrega/quita
   estas 2 clases solo, en base a visibilidad real (IntersectionObserver) y
   a si el teclado está abierto (comparación de innerHeight vs
   visualViewport.height), así que no hacen falta media queries acá. */
body.asistente-bloqueo-scroll {
  overflow: hidden;
}
body.asistente-teclado-abierto .mobile-topbar {
  display: none !important;
}

/* ---- Barra superior solo-móvil (hamburguesa) ---- */
.mobile-topbar {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border-glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* Acceso directo a Enlaces rápidos en móvil (2026-08-07): botón nuevo a la
   derecha de la barra, mismo símbolo-limpio que la hamburguesa — comparten
   .btn-topbar-icono en vez de cada uno tener su propio estilo, para que
   cualquier ajuste futuro de tamaño/color quede sincronizado entre los dos.
   Sin borde ni fondo: solo el símbolo (pedido explícito, la hamburguesa NO
   debe verse como un botón). */
/* Fix (2026-08-26 — "hay una X default muy fea en el panel de enlaces"):
   este selector estaba scopeado a ".mobile-topbar .btn-topbar-icono", pero
   #btn-cerrar-drawer-enlaces usa la misma clase y vive dentro de
   #drawer-enlaces-movil (no de .mobile-topbar) — nunca recibía este estilo
   y se veía como botón nativo del navegador sin diseñar. Se saca el scope:
   la intención original ya era que CUALQUIER botón con esta clase
   comparta el mismo look, sin importar en qué contenedor viva. */
.btn-topbar-icono {
  background: transparent;
  border: none;
  color: var(--text-primary);
  width: 42px; height: 42px;
  font-size: 1.2rem;
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.btn-topbar-icono:active { opacity: 0.6; }
/* flex:1 para que el título ocupe el espacio del medio y el botón de
   Enlaces (agregado después de <h3> en el HTML) quede pegado al borde
   derecho de la barra, sin necesitar un wrapper extra. */
.mobile-topbar h3 { margin: 0; font-size: 1rem; flex: 1; }

/* ---- Overlay para el drawer móvil ---- */
.sidebar-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 49;
}

/* C.6 (v9): se aplica a <body> mientras el drawer móvil está abierto, para
   que la página de fondo no se pueda desplazar y el drawer se sienta
   realmente anclado a la pantalla (igual que el panel de Enlaces Rápidos). */
body.scroll-bloqueado {
  overflow: hidden;
  overscroll-behavior: contain;
}

/* ---- Sidebar (nav) ---- */
.app-sidebar {
  width: 230px;
  max-width: 230px;
  flex-shrink: 0;
  padding: 20px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: width var(--transition-med), max-width var(--transition-med), transform var(--transition-med);
  height: 100vh;
  position: sticky;
  top: 0;
  overflow: hidden;
  /* FIX (2026-08-23): en escritorio no tenía z-index propio (auto = 0), así
     que cualquier header interno con z-index explícito (ej. el encabezado
     sticky del grid de Horario, z-index:50 — ver horario.js) le ganaba el
     stacking apenas hubiera cualquier overlap, aunque sea de un par de
     píxeles por un desborde de layout. Con un z-index propio más alto que
     cualquier header de sección, el sidebar SIEMPRE queda por encima, pase
     lo que pase adentro del contenido. (En móvil esto no aplica: ahí el
     sidebar es un cajón con su propio z-index:55 dentro del media query de
     abajo, ya mayor a cualquier header — ver ese bloque más abajo.) */
  z-index: 60;
}
/* Un poco menos transparente que el resto de tarjetas de vidrio: se suma
   una capa neutra (oscura en modo oscuro, clara en modo claro) encima del
   fondo translúcido normal, solo para la barra lateral. */
:is([data-mode="dark"], [data-mode="true-dark"]) #app-sidebar {
  background: linear-gradient(rgba(0,0,0,0.16), rgba(0,0,0,0.16)), var(--bg-panel);
}
[data-mode="light"] #app-sidebar {
  background: linear-gradient(rgba(255,255,255,0.16), rgba(255,255,255,0.16)), var(--bg-panel);
}
.app-sidebar .sidebar-scroll {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
}
.app-sidebar .sidebar-titulo { margin-bottom: 18px; white-space: nowrap; overflow: hidden; }
.app-sidebar .sidebar-scroll .row-between { width: 100%; }
.app-sidebar .sidebar-scroll .row-between #indicador-sync { width: 100%; justify-content: center; text-align: center; }
.app-sidebar .btn-nav {
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Bug real reportado (2026-08-22): el alto de cada botón se basaba en el
     espacio disponible del contenedor en vez de en su propio contenido —
     con la lista larga, los botones se veían recortados/aplastados en vez
     de mantener su alto real y dejar que .sidebar-scroll (ya tiene
     overflow-y:auto arriba) scrolleara. Causa exacta: overflow:hidden (de
     la línea de arriba, necesario para el ellipsis del texto) hace que el
     navegador considere que el "tamaño mínimo automático" de este flex-item
     es 0 en vez de basado en su contenido (así lo define la spec de
     flexbox para items con overflow != visible) — eso habilita que
     flex-shrink (1 por defecto) los encoja de más cuando no entran todos.
     flex-shrink:0 los saca de ese reparto: SIEMPRE miden lo que su
     contenido necesita, sin importar cuántos entren; el scroll de
     .sidebar-scroll se encarga del resto. */
  flex-shrink: 0;
}
.app-sidebar .btn-colapsar {
  background: transparent;
  border: 1px solid var(--border-glass);
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  padding: 8px;
  cursor: pointer;
  align-self: flex-end;
  margin-bottom: 8px;
}

/* Sidebar colapsada (solo desktop) */
.app-sidebar.colapsada {
  width: 72px;
  max-width: 72px;
  padding-left: 10px;
  padding-right: 10px;
}
.app-sidebar.colapsada .texto-nav,
.app-sidebar.colapsada .sidebar-titulo span.texto,
.app-sidebar.colapsada #indicador-sync,
.app-sidebar.colapsada .perfil-datos {
  display: none;
}
.app-sidebar.colapsada .btn-nav,
.app-sidebar.colapsada .btn-colapsar { align-self: center; }
.app-sidebar.colapsada .btn-colapsar { transform: rotate(180deg); }

/* ---- Zona de perfil, fija abajo (sin scroll), punto 6 ---- */
.perfil-sidebar {
  position: sticky;
  bottom: 0;
  padding-top: 12px;
  margin-top: 8px;
  border-top: 1px solid var(--border-glass);
  background: transparent;
  flex-shrink: 0;
}
.perfil-fila {
  display: flex;
  align-items: center;
  gap: 10px;
}
.perfil-foto {
  width: 34px; height: 34px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  border: 1px solid var(--border-glass);
  background: var(--bg-panel);
  /* Mismo recorte a prueba de WebKit que .palette-swatch: sin esto, el
     borde translúcido sobre un fondo degradado puede dejar "puntitos"
     claros asomando en las esquinas del cuadro que envuelve el círculo,
     visibles sobre todo en modo oscuro (se pierden en modo claro). */
  overflow: hidden;
  -webkit-mask-image: -webkit-radial-gradient(circle, #fff 100%, #000 100%);
}
.perfil-foto-wrap {
  position: relative;
  width: 34px; height: 34px;
  flex-shrink: 0;
  cursor: pointer;
}
.perfil-foto-wrap .perfil-foto { position: absolute; inset: 0; }
.perfil-foto-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gradient-accent);
  color: var(--on-accent);
  font-weight: 700;
  font-size: 0.85rem;
}
.perfil-nombre {
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}
.app-sidebar.colapsada .perfil-fila { justify-content: center; }

.perfil-popover {
  position: absolute;
  bottom: 100%;
  left: 8px;
  margin-bottom: 8px;
  min-width: 200px;
  padding: 14px;
  z-index: 60;
}
.perfil-popover.oculto { display: none; }

/* ---- Contenido principal ---- */
.app-contenido {
  flex: 1;
  min-width: 0;
  width: 100%;
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: center;
}
.app-contenido > * { max-width: 760px; width: 100%; }
/* Excepción: los modales de Horario (selector de semestre, bloque flotante,
   crear/editar bloque) viven dentro de <main> por cómo arma su DOM
   horario.js — sin esto, heredaban max-width/width de la regla de arriba y
   el overlay (position:fixed) quedaba angosto y pegado a la izquierda en
   vez de cubrir toda la pantalla. */
.app-contenido > .modal-overlay { max-width: none; width: auto; }

/* FIX (2026-08-23): red de contención para el grid de Horario. Antes esta
   caja no tenía NINGUNA regla acá — el ancho/alto/scroll se fijaban
   siempre desde horario.js (overflowX, maxHeight, overflowY inline), lo
   cual deja un hueco: si ese JS no corre a tiempo (ej. la rama "sin
   semestre" de renderizarHorarioInterno hace un return antes de llegar a
   esas líneas) o corre después del primer paint, la grilla ancha de adentro
   (width:max-content) no tiene nada que la recorte y se desborda del
   contenedor — arrastrando consigo su propio encabezado sticky
   (z-index:50) por encima de lo que sea que esté al lado, sidebar
   incluido. overflow-x:auto acá como base asegura que SIEMPRE haya un
   límite, incluso antes de que el JS termine de correr; horario.js sigue
   pudiendo ajustar max-height/overflow-y por encima sin problema. */
#horario-grid-contenedor {
  overflow-x: auto;
  max-width: 100%;
}

/* v5 #2: en pantallas grandes el contenido aprovecha más ancho, centrado
   respecto al espacio libre que deja la barra lateral. */
@media (min-width: 1200px) {
  .app-contenido { align-items: center; }
  .app-contenido > * { max-width: 920px; }
}

/* Fix (2026-08-07): caja fija en flex para el ícono del enlace rápido —
   ver el comentario en config-enlaces.js. font-size:1.3rem queda acá para
   que el emoji se vea al mismo tamaño que antes; para <img> es
   irrelevante (el tamaño real de la imagen ahora se fija en su propio
   estilo inline, ver más abajo).
   Fix (2026-08-08 — "cuando hay más de un emoji se pone uno sobre otro"):
   `width: 24px` fijo convertía este span en un contenedor angosto; el
   texto de varios emojis adentro es un ítem flex anónimo que sigue
   pudiendo hacer salto de línea normal (white-space: normal por
   defecto) — con 2+ emojis (cada uno ronda los 20px+ a este tamaño de
   fuente) no entraban en 24px de ancho y el navegador los partía línea
   por línea, uno debajo del otro. Se cambia a `min-width` (así una sola
   imagen sigue centrada en una caja de al menos 24px) + `white-space:
   nowrap` (así el texto de los emojis nunca envuelve, sin importar
   cuántos haya — quedan todos en una sola línea horizontal, como texto
   normal). */
.enlace-rapido-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  flex-shrink: 0;
  font-size: 1.3rem;
  line-height: 1;
  white-space: nowrap;
}
/* El tamaño de la imagen ahora se fija acá (antes era width/height:100% del
   contenedor, que dependía de que el contenedor tuviera un ancho fijo —
   ya no lo tiene, porque pasó a min-width para poder crecer con emojis). */
.enlace-rapido-icono img {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}

/* ---- v7.1: el nombre del enlace rápido usaba var(--accent-1) heredado del
   <a> global (`a { color: var(--accent-1); }`), y en varias paletas ese
   acento tiene muy poco contraste contra el fondo detrás (ej. paleta negro
   en modo oscuro: acento #3F3F46 sobre fondo #000000 ≈ contraste 2:1, casi
   invisible). Se fija a --text-primary (diseñado para contraste garantizado
   en las 14 paletas) y el acento queda solo como detalle sutil al pasar el
   mouse, nunca como color base del texto. ---- */
.enlace-rapido-nombre {
  color: var(--text-primary);
  transition: color var(--transition-fast);
}
a:hover .enlace-rapido-nombre {
  color: var(--accent-2);
}

/* ---- Enlaces Rápidos laterales: solo con espacio de sobra a la derecha ---- */
.enlaces-lateral {
  display: none;
  width: 260px;
  flex-shrink: 0;
  padding: 20px;
  align-self: flex-start;
  position: sticky;
  top: 20px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}
@media (min-width: 1500px) {
  .enlaces-lateral { display: block; }
}

/* ---- Botón flotante de Enlaces rápidos (900px–1499px) ----
   Pedido de Krys (2026-08-26): en ese rango de escritorio no hay espacio
   para .enlaces-lateral (que recién aparece a partir de 1500px) y el botón
   de la topbar móvil está oculto (solo vive en @media max-width:899.98px),
   así que Enlaces rápidos quedaba sin ningún acceso directo salvo entrar a
   Configuración. Se replica EXACTAMENTE el patrón que ya existe en
   móvil: mismo drawer (#drawer-enlaces-movil), mismo overlay
   (#enlaces-movil-overlay), mismas funciones abrirDrawerEnlacesMovil /
   cerrarDrawerEnlacesMovil (ver componentes.js) — este botón es solo un
   segundo disparador para ese mismo mecanismo, no un componente nuevo. */
.btn-flotante-enlaces {
  display: none;
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: color-mix(in srgb, var(--accent-1) 68%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  outline: 1px solid color-mix(in srgb, var(--text-primary) 16%, transparent);
  color: var(--on-accent);
  font-size: 1.12rem;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-glass);
  z-index: 45;
  padding: 0;
  transition: bottom .42s cubic-bezier(.22,1,.36,1), transform .25s ease, background .25s ease;
}
.btn-flotante-enlaces:hover { box-shadow: 0 10px 26px var(--accent-glow-1); }
.btn-flotante-enlaces:active { transform: scale(0.96); }
body.instalacion-flotante-activa .btn-flotante-enlaces { bottom: 76px; }
@media (min-width: 900px) and (max-width: 1499.98px) {
  .btn-flotante-enlaces { display: flex; }
}

/* =========================================================================
   BREAKPOINT: < 900px → sidebar se convierte en drawer superior
   ========================================================================= */
@media (max-width: 899.98px) {
  .mobile-topbar { display: flex; }

  .app-sidebar {
    position: fixed;
    top: 0; left: 0;
    height: 100vh;
    height: 100dvh; /* C.6 (v9): en móvil real, 100vh puede incluir el
      espacio de la barra de navegador que se oculta/muestra al hacer
      scroll, dejando el fondo del drawer "cortado" o con un salto visual
      al abrir. 100dvh usa la altura visible real en cada momento; se
      declara DESPUÉS de 100vh (nunca antes) para que actúe como mejora
      progresiva y no rompa nada en navegadores que no soportan dvh. */
    width: min(280px, 82vw);
    max-width: min(280px, 82vw);
    /* FIX (punto 4, 2026-08-23): estaba en z-index 50, el MISMO valor que
       usan los headers sticky internos de Horario (ver headerWrap/barra en
       horario.js, y el comentario junto a .horario-materia-lane en este
       archivo). Con z-index empatado, gana quien aparece último en el DOM
       dentro del mismo contexto de apilamiento — y el contenido de
       <main class="app-contenido"> va DESPUÉS de #app-sidebar en el
       markup (ver index.html), así que el header de Horario terminaba
       pintándose ENCIMA del drawer del sidebar al abrirlo en móvil, en vez
       de quedar debajo como corresponde a un drawer de navegación. Subir
       el sidebar a 55 lo deja por encima de cualquier header/barra de
       sección (max. 50 hoy) sin acercarse a los z-index de modales reales
       (80 en adelante), que sí deben seguir tapando todo, sidebar incluido. */
    z-index: 55;
    transform: translateX(-100%);
    box-shadow: var(--shadow-glass);
  }
  .app-sidebar.abierta { transform: translateX(0); }
  .app-sidebar.colapsada { width: min(280px, 82vw); max-width: min(280px, 82vw); padding-left:14px; padding-right:14px; }
  .app-sidebar.colapsada .texto-nav,
  .app-sidebar.colapsada .sidebar-titulo span.texto,
  .app-sidebar.colapsada #indicador-sync,
  .app-sidebar.colapsada .perfil-datos { display: revert; }
  .app-sidebar .btn-colapsar { display: none; } /* el colapsado de escritorio no aplica en móvil */

  .sidebar-overlay.abierta { display: block; }

  .app-contenido { padding: 16px; }
  .app-contenido > * { max-width: 100%; }
}

@media (min-width: 900px) {
  .sidebar-cerrar-movil { display: none; }
}

/* ---- Drawer de Enlaces rápidos en móvil (2026-08-07) ----
   Mismo mecanismo del sidebar principal (fixed + transform + overlay
   propio), pero anclado a la derecha y deslizando en sentido contrario:
   así abrir Enlaces no navega a otra sección ni saca al usuario de donde
   estaba — la pantalla de fondo queda intacta detrás del overlay. */
.drawer-enlaces-movil {
  position: fixed;
  top: 0; right: 0;
  height: 100vh;
  height: 100dvh; /* mismo motivo que .app-sidebar: barra de navegador móvil */
  width: min(320px, 86vw);
  max-width: min(320px, 86vw);
  /* FIX (punto 4, 2026-08-23): mismo empate de z-index (50) que tenía
     #app-sidebar contra los headers sticky de sección — ver el comentario
     junto al z-index de .app-sidebar más arriba. Aunque no había reporte
     puntual de este drawer tapado, es el mismo mecanismo y el mismo riesgo
     si se abre Enlaces rápidos estando en Horario, así que se sube en
     paralelo para no dejar el mismo bug latente en un segundo lugar. */
  z-index: 55;
  padding: 20px 16px;
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform var(--transition-med);
  box-shadow: var(--shadow-glass);
}
.drawer-enlaces-movil.abierta { transform: translateX(0); }

/* ---- Modal "Añadir enlace" (punto 7), responsivo también en 375px ---- */
.modal-card { width: min(440px, calc(100vw - 32px)); }
.modal-card .form-input[type="file"] { padding: 8px; }

/* ---- Aviso de login bloqueado (punto 8) ---- */
.aviso-login-bloqueado {
  font-size: 0.82rem;
  color: var(--color-danger);
  margin-top: 4px;
}

/* ---- 2026-09-24: píldora "Sin conexión" (reemplaza al banner fijo
   .aviso-reconexion de antes — offline no es un error, así que ya no
   interrumpe con una alarma en toda la pantalla). Vive DENTRO de
   .mobile-topbar-centro, arriba del <h3>, mismo criterio de columna
   centrada que ya usa #timer-linea-topbar debajo del título — ver ese
   bloque más abajo. Solo existe en <900px (hereda el display:none de
   .mobile-topbar por defecto); en >=900px el sidebar ya tiene su propio
   aviso discreto en #indicador-sync, sin banner ni píldora. Colores
   tomados de .badge-warning pero MÁS apagados (menos opacidad, sin
   fondo sólido, sin sombra) para leerse como "estado", no como alerta. */
.pill-sin-conexion {
  align-self: center;
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(245, 158, 11, 0.32);
  background: rgba(245, 158, 11, 0.10);
  color: #fbbf24;
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.pill-sin-conexion:hover,
.pill-sin-conexion:focus-visible { background: rgba(245, 158, 11, 0.16); border-color: rgba(245, 158, 11, 0.5); }
.pill-sin-conexion:active { transform: scale(0.97); }
.pill-sin-conexion svg { width: 12px; height: 12px; flex-shrink: 0; }
[data-mode="light"] .pill-sin-conexion { color: #92400e; }

/* ---- Utilidad genérica: nada debe forzar overflow horizontal ---- */
img, svg { max-width: 100%; }

/* ---- Responsive base ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* =========================================================================
   ITERACIÓN 1 — PARTE 2: Vista del Plan de Estudios
   ========================================================================= */

/* ---- Ajuste v4 #3: candado -> indicador de "luz" ----
   Nunca color fijo en hex "porque sí": bloqueada usa var(--text-muted) sin
   glow (apagada); disponible usa el acento de la paleta activa con un glow
   sutil (encendida). Se reutiliza tanto para disponibilidad (no clickeable)
   como para el ícono de "Desbloquea" (clickeable, ver .luz-boton). */
.luz-punto {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}
.luz-punto.disponible {
  background: var(--accent-2, #34d399);
  box-shadow: 0 0 9px var(--accent-2, #34d399), 0 0 3px var(--accent-2, #34d399);
}
.luz-punto.bloqueada {
  background: var(--text-muted);
  opacity: 0.5;
}
/* v5 #4: en modo oscuro la diferencia encendida/apagada no se notaba lo
   suficiente — se oscurece más la apagada y se le quita todo glow. En modo
   claro no se toca (ya se veía bien). */
:is([data-mode="dark"], [data-mode="true-dark"]) .luz-punto.bloqueada {
  background: #3f4552;
  opacity: 1;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.25);
}
.luz-boton {
  border: none;
  background: transparent;
  padding: 4px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---- Tarjeta de materia — encabezado FINAL de 2 líneas (v8 punto 2 / B v9) ----
   Línea 1: Luz · Código · Nombre (con flecha de expandir al final; el
   Código en sí es clickeable y abre el detalle unificado de la materia).
   Línea 2: Estado (izquierda) · Horas (centro) · Créditos (derecha) — igual
   en la tarjeta colapsada, expandida, y en el modal. */
.materia-card {
  padding: 12px 14px;
  position: relative;
  transition: padding-right var(--transition-fast);
}
/* Punto 6 (v1.9.6): con Modo Edición activo se reserva un carril a la
   derecha para el lápiz ✏️, así nunca se monta sobre la flecha ▲▼ de
   expandir (que sigue flotando dentro de .materia-linea1, más a la
   izquierda de ese carril). */
.materia-card.modo-edicion-activa { padding-right: 42px; }
.materia-editar-lapiz {
  display: none;
  position: absolute;
  top: 10px;
  right: 10px;
  width: 26px;
  height: 26px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-1-10);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  transition: background var(--transition-fast);
}
.materia-card.modo-edicion-activa .materia-editar-lapiz { display: flex; }
.materia-editar-lapiz:hover { background: var(--gradient-accent-alt, var(--gradient-accent)); }
.materia-fila-principal {
  display: flex;
  flex-direction: column;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}
/* v1.9.8 (reemplaza el enfoque de floats de v8/v9): la causa raíz del bug de
   la flecha era que .materia-nombre (con overflow:hidden) creaba su propio
   contexto de formato y "gastaba" todo el ancho disponible antes de que el
   navegador supiera que la flecha (float:right, declarada después en el
   DOM) iba a necesitar espacio — la empujaba a una 2da línea. Con flexbox
   real, los 3 hijos (prefijo, nombre, flecha) reservan su espacio desde el
   primer cálculo de layout: la flecha con ancho fijo según su contenido, el
   nombre (flex:1) recibe exactamente lo que sobra. Esto también vuelve
   innecesario el hack de padding-left/text-indent que usaba
   .materia-nombre.completa (ver esa clase, más abajo): al ser el nombre su
   PROPIA columna flex (no texto compartiendo flujo con un float), sus
   líneas 2+ ya se alinean solas bajo la 1ra sin ningún cálculo manual ni
   medido en JS. align-items:flex-start (no center) para que la flecha quede
   anclada arriba, al nivel de la 1ra línea del nombre, sin importar a
   cuántas líneas se expanda éste. */
.materia-linea1 {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
  cursor: pointer;
}
.materia-codigo {
  flex-shrink: 0;
  font-family: var(--font-mono, monospace);
  font-size: 0.82rem;
  color: var(--text-secondary);
  cursor: pointer; /* v8 punto 2 / B (v9): clic abre el detalle unificado */
}
.materia-codigo:hover { text-decoration: underline; }
/* v1.9.8: ya no flota ni tiene un ancho fijo "a mano" (104px/78px según
   breakpoint) — esa era justo la causa del desajuste que describía el punto
   2 del prompt anterior. Ahora es un hijo flex más de .materia-linea1, con
   flex-shrink:0 para que nunca se comprima, y su ancho lo decide el propio
   navegador según su contenido real (luz + código), sin importar el ancho
   de pantalla ni el tamaño de fuente — nunca vuelve a descalzar. */
.materia-prefijo {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
/* v1.9.8: ya no flota — hijo flex de ancho fijo (según su propio contenido)
   dentro de .materia-linea1, lo que evita que empuje el nombre a una 2da
   línea (ver comentario de .materia-linea1). */
.materia-expandir { flex-shrink: 0; opacity: 0.7; }

/* ---- Cronograma (Agenda): tarjeta-selector de materia (2026-08-19) ----
   La tarjeta ES el selector — sin estado seleccionado, es este botón
   vacío; con estado seleccionado, es la tarjeta-resumen real + la
   "pestañita" de abajo (.materia-pestana-cambiar). ---- */
.materia-card-vacia {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 64px;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.materia-card-vacia:hover { background: rgba(255, 255, 255, 0.05); }
[data-mode="light"] .materia-card-vacia:hover { background: rgba(0, 0, 0, 0.03); }

/* Pestañita pegada al borde inferior de la tarjeta-resumen — NO se
   superpone a la tarjeta (nada de position:absolute/negative-margin): va
   en flujo normal, tocando el borde de abajo, así no queda "flotando"
   sobre la tarjeta ni se ve a través de su fondo semitransparente
   (.glass-panel usa blur — cualquier cosa que quede DEBAJO en el mismo
   punto se alcanza a notar difuminada). Es más angosta que la tarjeta
   (corta antes de llegar a las esquinas redondeadas de esta) y sin
   border-top, para que se sienta como una continuación/parte de la
   tarjeta — no un elemento aparte. Es la ÚNICA forma de abrir el selector
   de materia una vez elegida una. */
.materia-pestana-cambiar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(100% - 32px);
  margin: 0 auto;
  height: 20px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-top: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-secondary);
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.materia-pestana-cambiar:hover { background: rgba(255, 255, 255, 0.14); }
[data-mode="light"] .materia-pestana-cambiar { background: rgba(0, 0, 0, 0.04); border-color: rgba(0, 0, 0, 0.08); }
[data-mode="light"] .materia-pestana-cambiar:hover { background: rgba(0, 0, 0, 0.08); }
.materia-nombre {
  font-family: var(--font-display);
  font-weight: 700;
  display: block;
  flex: 1 1 auto;
  min-width: 0; /* imprescindible para que el ellipsis funcione dentro de un flex item */
  /* v1.13: el código (monoespaciado, 0.82rem) queda perfectamente centrado
     con la luz porque ambos viven dentro de .materia-prefijo, que se
     centra internamente (align-items:center). El nombre es una fuente
     distinta (font-display, 700, más grande) y vive FUERA de ese prefijo,
     como hijo directo de .materia-linea1 (align-items:flex-start) — con
     métricas de línea distintas, su primera línea de texto queda un poco
     más abajo que el centro real de la luz/código. `position:relative` con
     un offset vertical no mueve nada horizontalmente ni afecta el layout
     de sus hermanos (a diferencia de un margin-top, que sí lo haría), así
     que es un ajuste puramente visual, parejo en todas las líneas si el
     nombre se expande a varias. */
  position: relative;
  top: -3px;
}
/* Colapsada: trunca con "…" — este es el ÚNICO lugar del diseño de la
   tarjeta donde se usa recorte con puntos suspensivos (v1.9.8); en ningún
   otro elemento nuevo de este rediseño (botones, nombres de requisito) se
   usa "…". Al ser el nombre su propia columna flex con ancho ya resuelto
   de antemano (ver .materia-linea1), el recorte siempre calza justo antes
   de llegar a la flecha, sin importar el ancho de pantalla. */
.materia-nombre.truncada {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* v1.9.8: expandida, nombre completo con wrap normal a varias líneas — ya
   NO necesita padding-left/text-indent "de indentación colgante" (ese hack
   solo hacía falta cuando el prefijo compartía el mismo flujo de texto vía
   float). Ahora el nombre es su propia columna flex, separada del prefijo,
   así que sus líneas 2+ ya nacen alineadas bajo la 1ra de forma nativa. */
.materia-nombre.completa {
  white-space: normal;
}

/* Línea 2: 3 zonas — Estado a la izquierda, Horas al centro (ocupa el
   espacio flexible restante y se centra dentro de él), Créditos a la
   derecha. Si Horas desborda su ancho, hace wrap a 2 líneas mantenidas
   centradas (tanto horizontal como verticalmente respecto a Estado/
   Créditos) gracias a align-items:center en la fila y text-align:center
   en el propio span. */
.materia-linea2 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.materia-linea2-horas {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* Semestres (tarjeta de materia matriculada) — línea 2 ANCHA necesita 3
   columnas simétricas (Estado / Universidad / Créditos+flecha), a
   diferencia del .materia-linea2 base (flex, usado también por Plan de
   Estudios en plan-detalle.js, que NO debe tocarse). El display:grid vive
   en esta clase — NUNCA como estilo inline — justo para que el
   @media(max-width:480px) de abajo pueda apagarla en teléfono sin pelear
   contra la prioridad de un inline (ver FIX 2026-08-09 en
   construirTarjetaMateriaMatriculada). */
.materia-linea2-semestre-grid { display: grid; }

.materia-cuerpo { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-glass); }
.materia-req-linea { text-align: left; color: var(--text-muted); font-size: 0.82rem; margin: 0; }

/* ---- Vista angosta de la tarjeta de materia (2026-08-09, pedido
   explícito, SOLO en teléfono) ----
   Ocultas por defecto: si por lo que sea el CSS de abajo (@media) no
   llegara a aplicar, el resultado sigue siendo la vista ancha de toda la
   vida (.materia-linea1/.materia-linea2, sin cambios), nunca un layout
   roto ni duplicado a la vista. Ver construirTarjetaMateriaMatriculada en
   semestres-tarjetas.js — arma AMBOS juegos de nodos siempre (nunca
   reutiliza un mismo nodo en dos contenedores), y es este CSS el que
   decide cuál se ve. */
.materia-linea1-angosta,
.materia-linea2-angosta,
.materia-linea3-angosta {
  display: none;
}

@media (max-width: 480px) {
  /* Vista ancha (2 líneas) se apaga, la angosta (3 líneas) toma su lugar —
     PERO solo dentro de tarjetas que efectivamente construyeron el juego de
     nodos angosto (Semestres / construirTarjetaMateriaMatriculada). FIX
     2026-08-10: el selector original apagaba .materia-linea1/.materia-linea2
     por nombre de clase nomás, sin importar el contenedor — como
     .materia-fila-principal/.materia-linea1/.materia-linea2 son las MISMAS
     clases que usa Plan de Estudios (plan-vista-lista-tarjetas.js, que
     NUNCA arma los nodos -angosta), esas tarjetas quedaban con su línea 1 y
     línea 2 ocultas y nada que las reemplazara: tarjeta vacía en pantallas
     angostas. El :has() acota el apagado a fila-principal que sí tiene un
     descendiente -angosta (Semestres); en Plan de Estudios, al no existir
     ese descendiente, la condición no matchea y la vista ancha de siempre
     queda visible — que es justo el fallback que el comentario de arriba
     ("Ocultas por defecto...") ya decía que debía pasar si el @media no
     aplicara. */
  .materia-fila-principal:has(.materia-linea1-angosta) > .materia-linea1:not(.materia-linea1-angosta),
  .materia-fila-principal:has(.materia-linea2-angosta) > .materia-linea2:not(.materia-linea2-angosta) {
    display: none;
  }

  /* Línea 1 angosta: Código, Materia — mismo comportamiento/tipografía que
     la línea 1 ancha (reutiliza .materia-linea1 como clase base), solo sin
     Nota ni 👤. */
  .materia-linea1-angosta { display: flex; }

  /* Líneas 2 y 3 angostas: mismo patrón de 3 columnas simétricas
     (1fr/auto/1fr) que ya usa el resto de la app para "anclado
     izquierda/centrado/anclado derecha" sin importar cuánto midan los
     elementos de los extremos. */
  .materia-linea2-angosta,
  .materia-linea3-angosta {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
  }
}

/* ---- v1.9.8: cuerpo de la tarjeta de lista, exclusivo de este diseño (el
   modal NO usa esto — sigue con su propio layout de 1 columna, ver
   construirCuerpoDetalleModal en plan-detalle.js). Columna 1 (Requisitos/
   Correquisitos) flexible; columna 2 (Categoría + 2 botones) de ancho fijo
   según su propio contenido (`auto`) y anclada arriba a la derecha — nunca
   cambia de tamaño ni posición sin importar cuánto contenido tenga la
   columna 1, incluso en pantallas angostas (nunca se apilan). */
.materia-cuerpo-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px;
  align-items: start;
}
.materia-cuerpo-requisitos { min-width: 0; }
.materia-cuerpo-acciones {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  flex-shrink: 0;
}
/* "Es requisito"/"Historial": nunca se truncan con "…" (a diferencia del
   nombre de la materia) — en vez de eso, display:inline-grid con
   justify-items:stretch hace que ambos botones midan exactamente el ancho
   del más largo entre los dos, sin JS: la única columna implícita del grid
   se dimensiona al contenido más ancho (el propio inline-grid se encoge a
   ese mismo ancho), y "stretch" hace que el más corto ocupe ese ancho
   completo también. No tienen por qué medir igual que el badge de
   Categoría (ese sigue su propio ancho natural) — solo un tamaño similar,
   por eso comparten padding/font-size parecidos pero no idénticos al badge. */
.materia-acciones-botones {
  display: inline-grid;
  justify-items: stretch;
  gap: 6px;
}
.materia-acciones-botones .btn {
  padding: 7px 14px;
  font-size: 0.8rem;
  white-space: nowrap;
  text-align: center;
}

/* B (v9)/v8 punto 2: "Bloque X · Código", texto plano — exclusivo del modal
   desde v1.11 (ver .requisito-linea1, abajo). v1.11 lo subió 50% (0.75rem →
   1.125em). v1.12: se sintió muy grande, se le baja 25% sobre ESE valor
   (1.125em × 0.75 = 0.84375em) — sigue en `em` (no rem fijo) para quedar
   proporcional al tamaño del nombre de la materia. */
.materia-meta-linea {
  font-size: 0.84375em;
  color: var(--text-muted);
  margin: 0;
}
/* Badge de Categoría — se omite del DOM por completo (ver
   construirLineaCategoriaMateria) si la materia no tiene ninguna. Ya no es
   una fila propia de ancho completo (era .materia-categoria-linea con
   justify-content:flex-end): desde v1.11 es un hijo más dentro de
   .requisito-linea1, así que solo ocupa su ancho natural. */
.materia-categoria-linea {
  display: flex;
  justify-content: flex-end;
}

/* v1.11 — SOLO MODAL: Línea 1, Bloque·Código (izquierda) + badge de
   Categoría (derecha), en la MISMA fila y centrados verticalmente entre
   sí (antes salían en 2 líneas separadas por bug). Va antes del título en
   el DOM — ver #requisito-linea1 en index.html y construirLinea1Materia. */
.requisito-linea1 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* v1.11: #requisito-contenedor-final agrupa la Línea 3 (Estado/Horas/
   Créditos) y el cuerpo (Requisitos/Correquisitos + botones finales), pero
   no es un .stack — sus 2 hijos quedaban sin espacio propio entre ellos.
   Se fija aquí, de forma EXPLÍCITA (no heredando el `gap` genérico de
   `.stack`, que se comparte con otras vistas y podría cambiar por una
   razón ajena a esto), el mismo valor que ya separa el título (Línea 2) de
   este contenedor vía el `gap: 14px` de `.modal-card.stack` — así el
   espacio Línea 3 → Requisitos queda exactamente igual al espacio
   Nombre → Línea 3, tal como pide el diseño. */
#requisito-contenedor-final {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Fila final del bloque de detalle: "Es requisito" · "Historial" · (solo
   en el modal) "Cerrar" — siempre agrupados juntos, nunca dispersos. */
.detalle-botones-finales {
  justify-content: flex-end;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 4px;
}

@media (max-width: 480px) {
  /* Parte 2.1 (pedido explícito): en teléfono, "Es requisito"/"Historial"/
     "Cerrar" deben verse prolijos, repartir entre los 2 o 3 (según si es
     modal o tarjeta expandida) todo el ancho disponible, y NUNCA irse a una
     segunda línea — mismo patrón ya usado para la fila Cancelar/Eliminar/
     Guardar del modal de profesor en Comunidad (ver comunidad.js,
     abrirModalAltaProfesor: flex-wrap:nowrap + flex:1 en cada botón).
     flex-wrap:nowrap acá pisa el wrap de la regla base de arriba. */
  .detalle-botones-finales {
    flex-wrap: nowrap;
    gap: 8px;
  }
  .detalle-botones-finales .btn {
    flex: 1;
    min-width: 0;
    padding: 10px 6px;
    font-size: 0.82rem;
    text-align: center;
  }
}

/* ---- Fila de requisito/correquisito de 2 columnas (v8 punto 2) ----
   1) Código-Nombre (flexible, es el link en sí, trunca con "…")
   2) Créditos, alineados estrictamente a la derecha de la fila. */
.requisito-fila {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
  font-size: 0.82rem;
}
.requisito-col-nombre {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-secondary);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.requisito-col-nombre:hover { text-decoration: underline; }
/* v1.9.8: en la tarjeta de lista (no en el modal) el nombre del requisito
   NUNCA se corta con "…" — el recorte con puntos suspensivos queda
   reservado exclusivamente para el nombre de la materia en el encabezado.
   Aquí, si no cabe ni una palabra del nombre, plan-detalle.js reemplaza el
   texto por solo el código (ver programarAjusteAnchoRequisito); si cabe al
   menos una palabra, se deja el texto normal y este text-overflow:clip
   simplemente lo recorta de forma limpia, sin puntos, si aun así desborda. */
.requisito-fila-tarjeta .requisito-col-nombre { text-overflow: clip; }
.requisito-col-creditos {
  flex-shrink: 0;
  text-align: right;
  color: var(--text-muted);
}
.requisito-divisor-o {
  text-align: center;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-style: italic;
  margin: 0;
}

@media (max-width: 640px) {
  .materia-codigo { font-size: 0.72rem; }
}

@media (max-width: 480px) {
  /* v1.9.8: en pantallas muy angostas la columna de acciones se mantiene
     siempre a la derecha (nunca se apila) — solo se reduce un poco su
     tamaño para dejarle más aire a la columna de Requisitos. */
  .materia-cuerpo-grid { gap: 10px; }
  .materia-acciones-botones .btn { font-size: 0.76rem; padding: 6px 10px; }
}

.requisito-fila-link {
  background: none; border: none; padding: 0;
  color: var(--accent-2);
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
  white-space: nowrap;
}

/* ---- Bloques como tarjetas colapsables independientes ---- */
.bloque-card { margin-bottom: 4px; }
.bloque-encabezado {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
}
.bloque-encabezado h3 { margin: 0; }

/* ---- Chips de código clickeables en requisitos/correquisitos ---- */
.chip-codigo {
  color: var(--accent-2);
  text-decoration: underline;
  cursor: pointer;
}

/* ---- Franja superior de color de categoría en el modal de requisito ----
   v1.12: el espacio visible hasta Línea 1 (Bloque·Código/Categoría) es la
   suma de este margin-bottom + el `gap:14px` de `.modal-card.stack` (franja
   y Línea 1 son ambos hijos flex directos de .modal-card) — antes 16+14=30px,
   pedido reducirlo a la mitad (15px), así que el margin-bottom baja a 1px
   (1+14=15). Exclusiva de este modal (.franja-categoria no se usa en
   ningún otro), así que no afecta a nada más. */
.franja-categoria { height: 8px; border-radius: var(--radius-sm) var(--radius-sm) 0 0; margin: -24px -24px 1px; }

/* ---- Modal de asignación de materias a categoría: más ancho (sección 7.2) ---- */
#modal-categoria-materias .modal-card { width: min(640px, calc(100vw - 32px)); }

/* ---- Modal de detalle de materia: siempre al frente ----
   2026-08-09 (pedido explícito): todos los .modal-overlay comparten
   z-index:100 por defecto, así que el orden de apilamiento depende del
   orden en el DOM — poco confiable cuando este modal se abre desde DENTRO
   de otro (ej. al tocar el nombre de una materia vinculada dentro del
   modal de Editar profesor, en Comunidad). Se fuerza explícitamente por
   encima de cualquier otro overlay de la app (el más alto usado en JS es
   310, ver comunidad.js). */
#modal-requisito { z-index: 320; }

/* ---- Modal de Historial: se abre DESDE DENTRO de #modal-requisito (el
   botón "Historial" vive en la tarjeta de detalle de materia, que no se
   cierra al abrirlo) ----
   2026-08-09 (bug reportado: "historial se abre DETRAS de la tarjeta de
   materia"): #modal-historial no tenía z-index propio, así que caía en el
   100 por defecto de .modal-overlay — muy por debajo del 320 forzado
   arriba para #modal-requisito. Se sube por encima de ese 320 (mismo
   criterio: el z-index más alto usado en JS es 315, ver
   abrirPopoverProfesoresMateria en semestres-tarjetas.js). */
#modal-historial { z-index: 325; }

/* ---- Créditos/estado en tarjeta de materia: más compactos en móvil ---- */
.materia-centro .badge, .materia-derecha .badge { font-size: 0.72rem; padding: 3px 9px; }

/* ---- Estadísticas colapsables con donuts (v5 #3) ---- */
.estadisticas-encabezado {
  display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; user-select: none;
}
.estadisticas-cuerpo {
  display: flex; flex-wrap: wrap; gap: 28px; justify-content: center;
  margin-top: 14px;
}
.donut-bloque { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.donut-bloque svg { display: block; }
.donut-etiqueta { font-size: 0.85rem; color: var(--text-secondary); text-align: center; font-weight: 600; }
.donut-subetiqueta { font-size: 0.75rem; color: var(--text-muted); text-align: center; }

/* ---- Toast breve (feedback "copiado") ---- */
/* FIX (2026-08-23, reportado en celular: "se cortan horrible, quedan
   altísimos"): antes no tenía ancho propio — se achicaba al contenido
   dentro de una "pill" (border-radius:999px) con solo un max-width tope,
   así que cualquier texto un poco largo se cortaba contra ese tope sin
   nada de margen para respirar. Ahora usa EXACTAMENTE los mismos tramos de
   ancho que las tarjetas (.app-contenido > *, más abajo en este archivo):
   en escritorio 760px (920px desde 1200px), en móvil el 100% del ancho
   disponible menos 16px de cada lado — nunca más angosto que eso. Sigue
   centrado a TODA la pantalla (left:50% + translateX(-50%), no al carril
   de contenido) porque un elemento position:fixed no convive con el
   sidebar de todas formas. border-radius pasa de --radius-pill a
   --radius-lg: una píldora completa a este ancho se ve rarísima; el radio
   de tarjeta es justamente el pedido ("del tamaño de las tarjetas"). */
.toast-app {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  width: calc(100% - 32px);
  max-width: 760px;
  text-align: center;
  background: var(--gradient-accent); color: var(--on-accent);
  padding: 10px 20px; border-radius: var(--radius-lg);
  font-weight: 600; font-size: 0.88rem;
  box-shadow: var(--shadow-float);
  z-index: 300;
  animation: toast-in-out 2.4s ease forwards;
}
@keyframes toast-in-out {
  0% { opacity: 0; transform: translate(-50%, 10px); }
  12%, 82% { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, 10px); }
}

/* ---- Toast persistente con acción (ej. aviso de actualización de la PWA) ----
   Mismo lenguaje visual que .toast-app (mismo ancho igualado a las
   tarjetas, mismo radio de esquina, misma esquina/centrado), pero SIN la
   animación de autodesaparición: queda fija hasta que el usuario aprieta
   el botón de acción o la cierra con ✕. */
.toast-app-accion {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px;
  width: calc(100% - 32px);
  max-width: 760px;
  background: var(--bg-header-solido); color: var(--text-primary);
  border: 1px solid var(--border-glass);
  padding: 12px 14px 12px 20px; border-radius: var(--radius-lg);
  font-weight: 600; font-size: 0.88rem;
  box-shadow: var(--shadow-glass);
  z-index: 300;
  animation: toast-accion-in var(--transition-med) ease;
}
.toast-app-accion span { flex: 1; min-width: 0; }
.toast-app-accion .btn { flex-shrink: 0; padding: 7px 16px; font-size: 0.82rem; }
.toast-app-accion-cerrar {
  flex-shrink: 0; background: none; border: none; cursor: pointer;
  color: var(--text-muted); font-size: 0.9rem; line-height: 1; padding: 6px;
}
.toast-app-accion-cerrar:hover { color: var(--text-primary); }
@keyframes toast-accion-in {
  0% { opacity: 0; transform: translate(-50%, 10px); }
  100% { opacity: 1; transform: translate(-50%, 0); }
}
/* Ya NO fuerza flex-wrap acá abajo: con el ancho de tarjeta que ahora
   tiene siempre (arriba), el texto largo envuelve solo dentro de su
   propio <span> (flex:1 dos reglas arriba) sin necesitar que el botón se
   caiga a una fila aparte — apilar texto+botón en una "pill" angosta era
   justamente lo que producía la forma alta y cortada. Solo se ajusta el
   margen inferior en pantallas chicas. */
@media (max-width: 480px) {
  .toast-app-accion { bottom: 16px; }
}
@media (min-width: 1200px) {
  .toast-app, .toast-app-accion { max-width: 920px; }
}

/* ---- Punto 6 (v1.9.6): badge fijo "Modo edición" ---- */
.badge-modo-edicion-fijo {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background: var(--gradient-accent);
  color: var(--on-accent);
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: 0.85rem;
  box-shadow: var(--shadow-float);
  z-index: 80;
  animation: badge-modo-edicion-in var(--transition-fast) ease;
}
@keyframes badge-modo-edicion-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---- Tarjetas arrastrables en "Gestionar plan" (v5 #1.4) ---- */
.plan-gestion-fila {
  cursor: grab;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}
.plan-gestion-fila.arrastrando { opacity: 0.4; }
.plan-gestion-fila.sobre-drop { box-shadow: 0 0 0 2px var(--accent-2) inset; }
.plan-gestion-estrella { color: var(--accent-2); font-size: 1.1rem; flex-shrink: 0; }

/* ---- Carrusel de planes (v5 #1.1): flechas grandes/gruesas SIN fondo de
   botón — solo el glyph, para que se lea interactivo sin ocupar espacio. ---- */
.carrusel-planes { display: flex; align-items: center; gap: 10px; }
.carrusel-planes button { flex-shrink: 0; }

.encabezado-plan-titulo { display: flex; flex-direction: column; gap: 2px; }
.encabezado-plan-linea1 { display: flex; align-items: center; gap: 8px; }
.flecha-plan {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0 2px;
  color: var(--accent-2);
  font-size: 1.6rem;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  width: 22px;
  text-align: center;
}
.flecha-plan:hover { color: var(--text-primary); }
.flecha-plan:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; border-radius: 4px; }
/* Línea 2 (Universidad · Código) queda indentada exactamente bajo la
   primera letra del nombre de la carrera de la línea 1: mismo ancho que
   una flecha (22px) + el gap (8px) entre flecha y título. */
.encabezado-plan-linea2 { margin: 0; padding-left: 30px; }
.encabezado-plan-linea2.sin-flechas { padding-left: 0; }

/* ---- Bloques colapsables independientes ---- */

/* ---- Secciones de Ajustes: contraídas por defecto (pedido explícito) ----
   Cada <section class="glass-card stack ajuste-seccion colapsada"> arranca
   colapsada; config-ajustes.js le saca la clase .colapsada al tocar la
   cabecera. La cabecera es un <button> (accesible por teclado, foco nativo)
   resetado a look de texto plano. El cuerpo pierde su display en colapsado
   en vez de animar altura: estas secciones tienen contenido muy variable
   (desde 1 pill-group hasta listas largas de enlaces), así que animar
   max-height requeriría medir cada una en JS; display:none es instantáneo
   y evita el "salto" de una transición de altura mal calculada. */
.ajuste-seccion-cabecera {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
}
.ajuste-seccion-cabecera h2 {
  margin: 0;
}

/* ---- Tamaño de letra estándar para encabezados de sección (pedido
   explícito, 15px base +30%) — se usa acá y en cualquier otro encabezado
   que deba quedar "igual a los de Ajustes": Semana X (Horario), Historial
   académico / Semestre actual / Semestres pasados (Semestres), Comunidad,
   Resumen / Gastos generales de la U / Buscar beneficios, y Estadísticas /
   Vista / Categorías (Plan de Estudios). Centralizado en una sola clase en
   vez de repetir el font-size suelto en cada lugar, para que sea un único
   punto de ajuste si el tamaño vuelve a cambiar. ---- */
.texto-encabezado-seccion {
  font-size: 19.5px;
  font-weight: 600;
}
.ajuste-seccion-chevron {
  flex-shrink: 0;
  margin-left: 12px;
  font-size: 1rem;
  color: var(--text-muted);
  transition: transform var(--transition-fast);
}
/* Expandida (sin .colapsada): chevron apunta hacia arriba (▾ rotado 180°) */
.ajuste-seccion:not(.colapsada) .ajuste-seccion-chevron { transform: rotate(180deg); }
.ajuste-seccion-cuerpo {
  display: flex;
  flex-direction: column;
  gap: 14px; /* mismo gap que .stack, para no perder el espaciado que tenían
                estos elementos cuando eran hijos directos de la sección */
}
.ajuste-seccion.colapsada .ajuste-seccion-cuerpo { display: none; }

/* ===================== Créditos (dentro de Ajustes) =====================
   Tarjeta colapsable, mismo patrón .ajuste-seccion (cabecera-botón +
   chevron, colapsada por default) que el resto de Ajustes — ubicada justo
   después de "Datos". Estilo "créditos de película" dentro del cuerpo:
   todo centrado, la etiqueta chica/discreta y el nombre grande con más
   peso — mismo lenguaje visual que el resto de Ajustes (.glass-card,
   .texto-encabezado-seccion, .muted, .btn-secondary). */
.tarjeta-creditos {
  text-align: center;
}
.tarjeta-creditos .texto-encabezado-seccion {
  margin: 0 0 4px;
}
.creditos-bloque + .creditos-bloque {
  margin-top: 18px;
}
.creditos-etiqueta {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 4px;
}
.creditos-nombre {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text-primary);
}
.creditos-beta-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.creditos-separador {
  border: none;
  border-top: 1px solid var(--border-glass);
  margin: 20px 0;
}
.creditos-feedback-texto {
  font-size: 0.82rem;
  margin: 0 0 20px;
}

/* ===================== Resumen ===================== */

/* "Semana N de M" — dato chico arriba de todo, no es un bloque con card
   propia (ver resumen.js), solo texto discreto centrado. */
.resumen-semana-chip {
  text-align: center;
  font-size: 0.9rem;
  font-weight: 600;
}

/* Cada sección (Clases de hoy / Tareas de hoy / etc.) es un .glass-card
   normal — este selector solo ajusta el espaciado interno entre el título
   y la lista de items para que no herede el gap:14px genérico de .stack
   (demasiado suelto para una lista de items chicos). */
.resumen-bloque {
  gap: 10px;
}

/* Mensaje de "día tranquilo" cuando todas las secciones están vacías. */
.resumen-vacio {
  text-align: center;
  padding: 32px 16px;
  font-size: 0.95rem;
}

/* ===================== Agenda ===================== */

/* Tarjetita "Mostrar clases" (colapsada por default) — mismo patrón visual
   de cabecera-clickeable-con-chevron que .ajuste-seccion-cabecera, pero en
   tamaño más chico/casual (vive DENTRO de la tarjeta de un día, no es un
   encabezado de sección de nivel superior) y con su propio contenedor con
   borde sutil para distinguirla del resto de la tarjeta del día. */
.agenda-tarjeta-clases {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
[data-mode="light"] .agenda-tarjeta-clases { background: rgba(0, 0, 0, 0.02); }
.agenda-tarjeta-clases-cabecera {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
}
.agenda-tarjeta-clases-cabecera .ajuste-seccion-chevron { transition: transform var(--transition-fast); }
.agenda-tarjeta-clases-cuerpo { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }

/* Filas de clase (dentro de "Mostrar clases") y de evento/tarea/examen
   (lista principal del día) comparten el mismo lenguaje visual: pill
   clickeable de ancho completo, fondo sutil que se resalta al hover. */
.agenda-fila-clase,
.agenda-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: rgba(255, 255, 255, 0.04);
  border: none;
  border-radius: 8px;
  padding: 7px 10px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  text-align: left;
  transition: background var(--transition-fast);
}
[data-mode="light"] .agenda-fila-clase,
[data-mode="light"] .agenda-item { background: rgba(0, 0, 0, 0.04); }

/* Pedido nuevo: chip de un adjunto (formulario y tarjeta de info) — mismo
   tono de fondo que .agenda-item para que se sienta parte de la misma
   familia visual, pero sin cursor de "toda la fila es un botón" (el link/
   quitar de adentro son los interactivos, no el chip entero). */
.agenda-adjunto-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 8px;
  padding: 7px 10px;
  color: inherit;
  transition: background var(--transition-fast);
}
.agenda-adjunto-chip:hover { background: rgba(255, 255, 255, 0.08); }
[data-mode="light"] .agenda-adjunto-chip { background: rgba(0, 0, 0, 0.04); }
[data-mode="light"] .agenda-adjunto-chip:hover { background: rgba(0, 0, 0, 0.08); }
.agenda-fila-clase:hover,
.agenda-item:hover { background: rgba(255, 255, 255, 0.08); }
[data-mode="light"] .agenda-fila-clase:hover,
[data-mode="light"] .agenda-item:hover { background: rgba(0, 0, 0, 0.07); }

/* Rediseño núcleo Agenda — punto 5: checkbox circular de "completada",
   vacío/relleno. Vive a la izquierda del ítem de tarea (adentro de
   .agenda-item, antes del badge) — botón propio (no <input type="checkbox">)
   para poder frenar la propagación del click y no abrir la tarjeta de
   info al togglear, mismo motivo por el que los ítems de agenda ya son
   <button> en vez de <div> con listener aparte. */
.agenda-check-completada {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid #3b82f6;
  background: transparent;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  transition: background var(--transition-fast), transform var(--transition-fast);
}
.agenda-check-completada:hover { transform: scale(1.08); }
.agenda-check-completada.marcada { background: #3b82f6; }
/* Pedido nuevo: "aumentar el hitbox de la bolita, no se debe ver, el
   usuario no lo notará". El círculo visual no cambia (mismo 20x20 de
   arriba); el área táctil real crece con un ::before invisible más grande
   que el botón, centrado sobre él — técnica estándar para agrandar hitboxes
   sin tocar el tamaño visual. */
.agenda-check-completada { position: relative; }
.agenda-check-completada::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  transform: translate(-50%, -50%);
}

/* Ronda de ajustes visuales #5 — punto D: encabezado separador "Semana N"
   entre grupos de días en el modo "Todo" (ver construirEncabezadoSemanaTodo
   en agenda.js). Línea horizontal a los costados (mismo patrón que un
   divisor de fecha en un chat) para que se note como corte, sin pesar
   tanto como una tarjeta más. */
.agenda-todo-encabezado-semana {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  padding: 0 2px;
}
.agenda-todo-encabezado-semana::before,
.agenda-todo-encabezado-semana::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-glass);
}

/* Punto 6: indicador de tarea vencida — deliberadamente DISTINTO del rojo
   sólido de Examen (badge-danger): borde punteado en vez de sólido, para
   que a simple vista "vencida" no se confunda con "es un examen". */
.agenda-badge-vencida {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.68rem;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px dashed #ef4444;
  color: #f87171;
  background: transparent;
}
[data-mode="light"] .agenda-badge-vencida { color: #b91c1c; }

/* Ronda de ajustes visuales — punto 1: badge de tipo (Tarea/Examen/Evento)
   de la columna derecha de la tarjeta, del mismo tamaño chico que
   .agenda-badge-vencida (arriba) — el `.badge` genérico (design-system.css,
   ~línea 314) es más grande y quedaba desproporcionado apilado con
   "Vencida" en 2 líneas cortas. Se sobreescribe puntualmente acá en vez de
   tocar `.badge` global, que se comparte con el resto de la app. */
.agenda-badge-tipo {
  font-size: 0.68rem;
  padding: 1px 7px;
}

/* Punto 6: timer de cuenta regresiva para tareas que vencen HOY — texto
   chico, mismo tono ámbar que "tarea pendiente" (badge-warning) para que se
   lea como "urgencia dentro de lo pendiente", no como un estado nuevo. */
.agenda-timer-vence-hoy {
  font-size: 0.7rem;
  font-weight: 600;
  color: #fbbf24;
  flex-shrink: 0;
  white-space: nowrap;
}
[data-mode="light"] .agenda-timer-vence-hoy { color: #92400e; }

/* Rediseño núcleo Agenda — punto 8: toggle "‹ N días anteriores" que agrupa
   los días ya pasados de la semana visible. Mismo lenguaje visual liviano
   que el resto de cabeceras clickeables de la app (fondo sutil, borde
   glass) — la flecha rota 180° al expandir, mismo truco que
   .ajuste-seccion-chevron. */
.agenda-colapso-pasados {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  cursor: pointer;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  text-align: left;
  transition: background var(--transition-fast);
}
.agenda-colapso-pasados:hover { background: rgba(255, 255, 255, 0.07); }
[data-mode="light"] .agenda-colapso-pasados { background: rgba(0, 0, 0, 0.02); }
[data-mode="light"] .agenda-colapso-pasados:hover { background: rgba(0, 0, 0, 0.05); }
.agenda-colapso-pasados-flecha {
  font-size: 1.1rem;
  line-height: 1;
  transition: transform var(--transition-fast);
}

/* Feature "filtro por estado" (Clase/Completado/Pendiente/Examen/Evento/
   Feriado) — 6 badges bajo el encabezado de semana, en Semanal y Todo (ver
   construirBarraFiltroEstadosAgenda en agenda.js). El ancho igual entre
   todos ya lo resuelve JS (min-width calculado del más largo); acá solo el
   color. Reutiliza el mismo color que ya identifica a cada tipo en el resto
   de Agenda (mismos valores que .badge-warning/.badge-danger/.badge-purple/
   .badge-success/.badge-info de arriba) para que el filtro se lea como
   extensión natural de esos colores. "Clase" es el único sin color propio
   todavía (no es un `tipo` real de EventoAgenda) — se le asigna un rosa
   medio a pedido explícito ("ni muy claro ni muy oscuro, muy claro
   dificulta la vista"). En reposo/inactivo: contorno gris tenue sin
   relleno; activo: mismo patrón que los badges de arriba (relleno
   translúcido + borde + texto en el color propio). */
.agenda-filtro-estados {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.agenda-filtro-colapsable { margin: 0 0 10px; }
.agenda-filtro-colapsable > summary { cursor: pointer; color: var(--text-secondary); font-size: .84rem; padding: 7px 2px; }
.agenda-filtro-colapsable[open] > summary { margin-bottom: 6px; }
.agenda-vista-entrante { animation: agendaVistaEntrada .28s cubic-bezier(.2,.75,.25,1) both; }
@keyframes agendaVistaEntrada { from { opacity: .5; transform: translateX(14px); } to { opacity: 1; transform: translateX(0); } }
.agenda-color-picker {
  position: relative; display: inline-grid; place-items: center; flex: 0 0 30px;
  width: 30px; height: 30px; border: 0; border-radius: 50%;
  background: var(--color-selector-agenda, #8b5cf6); overflow: hidden; cursor: pointer;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-selector-agenda, #8b5cf6) 26%, transparent);
}
.agenda-color-picker input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; opacity: 0;
  border: 0; cursor: pointer; appearance: none; -webkit-appearance: none;
}
.agenda-tipo-config-fila { align-items: center; }
.agenda-toggle-activo { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; user-select: none; }
.agenda-toggle-activo input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.agenda-toggle-activo-pista { position: relative; width: 38px; height: 22px; border-radius: 999px; background: var(--bg-panel); border: 1px solid var(--border-glass); transition: background .2s ease; }
.agenda-toggle-activo-pista::after { content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: var(--text-muted); transition: transform .2s ease, background .2s ease; }
.agenda-toggle-activo input:checked + .agenda-toggle-activo-pista { background: color-mix(in srgb, var(--accent-1) 32%, var(--bg-panel)); border-color: var(--accent-1); }
.agenda-toggle-activo input:checked + .agenda-toggle-activo-pista::after { transform: translateX(16px); background: var(--accent-2); }
.agenda-toggle-activo input:focus-visible + .agenda-toggle-activo-pista { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.agenda-selector-comportamiento { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.agenda-selector-comportamiento button { display: inline-flex; align-items: center; gap: 7px; min-height: 40px; padding: 7px 12px; border: 1px solid var(--border-glass); border-radius: var(--radius-pill); color: var(--text-secondary); background: var(--bg-panel); cursor: pointer; }
.agenda-selector-comportamiento button::before { content: ""; width: 11px; height: 11px; flex: 0 0 11px; border-radius: 50%; background: var(--tag-color); }
.agenda-selector-comportamiento [data-base="tarea"] { --tag-color: #eab308; }
.agenda-selector-comportamiento [data-base="examen"] { --tag-color: #ef4444; }
.agenda-selector-comportamiento [data-base="evento"] { --tag-color: #06b6d4; }
.agenda-selector-comportamiento button.active { color: var(--text-primary); border-color: color-mix(in srgb, var(--tag-color) 70%, transparent); background: color-mix(in srgb, var(--tag-color) 13%, var(--bg-panel)); }
.icono-app-usuario { display:inline-grid; place-items:center; width:1.5em; height:1.5em; flex:0 0 1.5em; border-radius:50%; background:var(--accent-1-10); font-size:.82em; }
.onboarding-overlay { z-index: 200000; padding: 16px; }
.onboarding-panel { width:min(640px, 100%); max-height:min(88dvh, 820px); overflow-y:auto; padding:clamp(20px, 4vw, 34px); gap:18px; }
.onboarding-panel h2 { margin:0; font-size:clamp(1.35rem, 4vw, 2rem); }
.onboarding-progreso { color:var(--accent-2); font-size:.82rem; font-weight:700; letter-spacing:.08em; }
.onboarding-contenido { gap:14px; }
.onboarding-acciones { gap:10px; flex-wrap:wrap; }
.onboarding-acciones > * { flex:1 1 150px; }
.onboarding-modos { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); }
.onboarding-modos .btn { white-space:normal; min-width:0; }
.onboarding-colores,.onboarding-iconos { display:flex; gap:12px; flex-wrap:wrap; align-items:center; }
.onboarding-color { width:36px; height:36px; border:3px solid transparent; border-radius:50%; background:var(--onboarding-color); cursor:pointer; }
.onboarding-color[aria-pressed="true"] { border-color:var(--text-primary); box-shadow:0 0 0 3px color-mix(in srgb,var(--onboarding-color) 35%,transparent); }
.onboarding-icono { width:44px; height:44px; border:1px solid var(--border-glass); border-radius:14px; background:var(--bg-panel); font-size:1.3rem; cursor:pointer; }
.onboarding-icono[aria-pressed="true"] { border-color:var(--accent-1); background:var(--accent-1-10); }
.onboarding-pasos { margin:0; padding-left:1.4rem; line-height:1.65; }
.onboarding-pasos li { animation:agendaVistaEntrada .3s ease both; }
.onboarding-fragmento { display:flex; flex-direction:column; gap:6px; padding:14px 16px; border:1px solid var(--border-glass); border-left:3px solid var(--accent-1); border-radius:14px; background:var(--bg-card); animation:agendaVistaEntrada .36s cubic-bezier(.2,.75,.25,1) both; }
.onboarding-fragmento > span { color:var(--text-secondary); font-size:.85rem; line-height:1.45; overflow-wrap:anywhere; }
.btn-instalar-app-flotante { position:fixed; z-index:1200; right:18px; bottom:18px; display:flex; align-items:center; justify-content:center; width:52px; height:52px; padding:0; overflow:hidden; border:1px solid color-mix(in srgb,var(--accent-1) 45%,transparent); border-radius:999px; color:var(--on-accent); background:var(--gradient-accent); box-shadow:var(--shadow-float); cursor:pointer; transition:width .52s cubic-bezier(.22,1,.36,1), opacity .38s ease, transform .52s cubic-bezier(.22,1,.36,1), box-shadow .35s ease; will-change:width,transform; }
.btn-instalar-app-flotante:hover { transform:translateY(-2px); box-shadow:var(--shadow-float),0 0 0 5px color-mix(in srgb,var(--accent-1) 12%,transparent); }
.btn-instalar-app-flotante.instalar-expandido { width:176px; }
.btn-instalar-app-flotante.instalar-ocultando { opacity:0; transform:translateY(8px) scale(.92); pointer-events:none; }
.btn-instalar-app-flotante.oculto { display:none; }
.instalar-app-icono { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); display:grid; place-items:center; width:28px; height:28px; font-size:1.55rem; line-height:1; flex:0 0 auto; transition:left .52s cubic-bezier(.22,1,.36,1),transform .52s cubic-bezier(.22,1,.36,1); }
.instalar-expandido .instalar-app-icono { left:18px; transform:translateY(-50%); }
.instalar-app-texto { margin-left:25px; white-space:nowrap; font-size:.98rem; font-weight:750; letter-spacing:.01em; opacity:0; transform:translateX(5px); transition:opacity .28s ease .08s,transform .42s cubic-bezier(.22,1,.36,1) .04s; }
.instalar-expandido .instalar-app-texto { opacity:1; transform:translateX(0); }
.ajuste-subtitulo { font-size:.82rem; line-height:1.45; margin:2px 0 6px; }
@media (prefers-reduced-motion:reduce) { .btn-instalar-app-flotante,.instalar-app-icono,.instalar-app-texto { transition-duration:.01ms !important; animation-duration:.01ms !important; } }
@media (max-width:600px) { .btn-instalar-app-flotante { right:14px; bottom:calc(14px + env(safe-area-inset-bottom)); } .onboarding-panel { max-height:calc(100dvh - 28px); } }

/* Inicio y bienvenida como pantallas completas, no diálogos flotantes. */
#pantalla-login.login-pantalla:not(.oculto) {
  position:fixed; inset:0; z-index:100000; display:flex; width:100%; height:100%; min-height:100vh; min-height:100dvh;
  align-items:stretch; justify-content:stretch; overflow:auto; padding:0;
  background:radial-gradient(ellipse at 50% 0%, var(--accent-1-10), transparent 58%), var(--bg-app);
}
.login-pagina { display:grid; place-items:center; width:100%; min-height:100vh; min-height:100dvh; padding:clamp(24px,6vw,72px); }
.login-contenido { width:min(100%, 520px); margin:auto; text-align:center; gap:clamp(12px,2.4vh,20px); }
.login-contenido > p { max-width:44ch; margin-inline:auto; }
.login-contenido > h1 { flex-wrap:wrap; }
.ajuste-idioma-app { display:grid; gap:5px; max-width:360px; }
#selector-idioma-app { width:100%; }
.onboarding-overlay {
  position:fixed; inset:0; z-index:200000; display:flex; align-items:stretch; justify-content:stretch;
  width:100%; height:100%; min-height:100vh; min-height:100dvh; overflow:auto; overscroll-behavior:contain;
  padding:0; background:var(--bg-app); backdrop-filter:none;
}
.onboarding-panel {
  display:flex; flex-direction:column; justify-content:center; gap:clamp(18px,3vh,32px);
  width:100%; max-width:none; min-height:100vh; min-height:100dvh; max-height:none; margin:0 auto;
  overflow-y:auto; padding:clamp(24px,6vw,72px); border:0; border-radius:0; box-shadow:none;
  background:transparent;
}
.onboarding-panel > * { width:min(100%, 900px); margin-inline:auto; }
.onboarding-panel h2 { font-size:clamp(1.6rem,5vw,3rem); }
@media (max-width:600px) {
  .login-pagina { padding:24px; }
  .login-contenido { gap:14px; }
  .onboarding-panel { min-height:100vh; min-height:100dvh; max-height:none; padding:calc(24px + env(safe-area-inset-top)) 20px calc(24px + env(safe-area-inset-bottom)); }
}

/* Bienvenida: navegación lateral y muestra real de la demo en escritorio;
   en móvil se apilan sin reducir el texto ni recortar acciones. */
.onboarding-panel { justify-content:flex-start; gap:20px; padding:clamp(16px,3vw,40px); }
.onboarding-top,.onboarding-acciones { width:100%; max-width:1500px; margin-inline:auto; }
.onboarding-top { display:flex; align-items:center; justify-content:space-between; min-height:44px; }
.onboarding-cerrar { width:42px; height:42px; border:0; border-radius:50%; color:var(--text-primary); background:var(--bg-panel); font-size:1.6rem; cursor:pointer; }
.onboarding-layout { width:100%; max-width:1500px; margin:auto; display:grid; grid-template-columns:minmax(280px,.8fr) minmax(0,1.2fr); gap:clamp(20px,4vw,64px); align-items:stretch; min-height:0; }
.onboarding-panel > .onboarding-top,.onboarding-panel > .onboarding-layout,.onboarding-panel > .onboarding-acciones { width:100%; max-width:1500px; }
.onboarding-copy { display:flex; flex-direction:column; justify-content:center; gap:18px; min-width:0; }
.onboarding-copy h1 { margin:0; font-size:clamp(1.8rem,4vw,3.25rem); line-height:1.1; overflow-wrap:anywhere; }
.onboarding-lead { font-size:clamp(1rem,1.35vw,1.2rem); line-height:1.65; }
.onboarding-callout { padding:14px 16px; border-left:3px solid var(--accent-1); border-radius:12px; background:var(--bg-panel); line-height:1.55; }
.onboarding-preview-wrap { display:flex; flex-direction:column; min-height:min(68vh,740px); overflow:hidden; border:1px solid color-mix(in srgb,var(--accent-1) 14%,transparent); border-radius:18px; background:var(--bg-panel); }
.onboarding-preview-caption { padding:10px 14px; color:var(--text-muted); font-size:.78rem; }
.onboarding-preview { flex:1; width:100%; min-height:480px; border:0; pointer-events:none; }
.onboarding-paletas { display:grid; grid-template-columns:repeat(auto-fill,minmax(42px,1fr)); gap:10px; max-width:520px; }
.onboarding-color { width:40px; height:40px; border:3px solid transparent; border-radius:50%; cursor:pointer; }
.onboarding-color[aria-pressed="true"] { outline:2px solid var(--text-primary); outline-offset:3px; }
.onboarding-modos { display:flex; flex-wrap:wrap; gap:8px; }
.onboarding-logos { display:flex; flex-wrap:wrap; gap:10px; }
.onboarding-logo { display:grid; place-items:center; width:58px; height:58px; border:1px solid var(--border-glass); border-radius:14px; background:var(--bg-panel); cursor:pointer; }
.onboarding-logo[aria-pressed="true"] { outline:2px solid var(--accent-1); outline-offset:2px; }
.onboarding-logo img { width:38px; height:38px; object-fit:contain; }
.onboarding-acciones { justify-content:flex-end; flex-wrap:wrap; padding-bottom:env(safe-area-inset-bottom); }
.onboarding-dependencias { margin:0; padding-left:1.4em; line-height:1.8; }
.onboarding-con-tour .onboarding-preview-wrap { min-height:68vh; }
.guia-plan-flotante { position:fixed; z-index:200010; right:18px; bottom:18px; width:min(440px,calc(100vw - 36px)); padding:18px; border:1px solid color-mix(in srgb,var(--accent-1) 22%,transparent); border-radius:16px; color:var(--text-primary); background:var(--bg-header-solido); box-shadow:var(--shadow-float); }
.guia-plan-flotante p { line-height:1.55; }
.guia-plan-resaltado { position:relative; z-index:200011!important; outline:3px solid var(--accent-1)!important; outline-offset:5px!important; box-shadow:0 0 0 9999px rgba(0,0,0,.45)!important; }
.onboarding-logo img { object-fit:contain; }
.onboarding-logos button[data-logo-app] img { width:40px; height:40px; object-fit:contain; }
#selector-logo-app .onboarding-logos { display:flex; gap:10px; }
.agenda-barra-seleccion { z-index:10050; bottom:calc(14px + env(safe-area-inset-bottom)); }
.agenda-btn-eliminar-seleccion:disabled { opacity:.45; cursor:not-allowed; }
@media (min-width:601px) { .onboarding-panel:not(.onboarding-con-tour) .onboarding-layout { max-width:900px; grid-template-columns:1fr; } }
@media (max-width:760px) {
  .onboarding-layout { grid-template-columns:minmax(0,1fr); gap:16px; }
  .onboarding-copy { padding-top:4px; }
  .onboarding-preview-wrap { min-height:46vh; max-height:56vh; }
  .onboarding-preview { min-height:380px; }
  .onboarding-acciones { justify-content:stretch; }
  .onboarding-acciones > * { flex:1 1 140px; }
}

/* Cambios de apariencia suaves sin transicionar tamaños ni geometría. */
html[data-rendimiento="normal"] #app-shell .glass-card,
html[data-rendimiento="normal"] #app-shell .glass-panel,
html[data-rendimiento="normal"] #app-sidebar,
html[data-rendimiento="normal"] #app-shell .btn-nav,
html[data-rendimiento="normal"] #app-shell .btn { transition:background-color .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease; }
@media (prefers-reduced-motion:reduce) { html #app-shell *,html #app-sidebar { transition-duration:0s!important; } }
.agenda-filtro-estado-btn {
  font-size: 0.68rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(148, 163, 184, 0.3);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.agenda-filtro-color { width: 11px; height: 11px; flex: 0 0 11px; border-radius: 50%; border: 0 !important; box-shadow: none !important; }
.agenda-filtro-estado-btn:hover { border-color: rgba(148, 163, 184, 0.55); }
.agenda-filtro-estado-completado.active { background: rgba(59, 130, 246, 0.15); border-color: #3b82f6; color: #60a5fa; }
.agenda-filtro-estado-pendiente.active  { background: rgba(245, 158, 11, 0.15); border-color: #f59e0b; color: #fbbf24; }
.agenda-filtro-estado-examen.active     { background: rgba(239, 68, 68, 0.15);  border-color: #ef4444; color: #f87171; }
.agenda-filtro-estado-evento.active     { background: rgba(168, 85, 247, 0.15); border-color: #a855f7; color: #c084fc; }
.agenda-filtro-estado-feriado.active    { background: rgba(16, 185, 129, 0.15); border-color: #10b981; color: #34d399; }
.agenda-filtro-estado-clase.active      { background: rgba(219, 39, 119, 0.18); border-color: #db2777; color: #f472b6; }
[data-mode="light"] .agenda-filtro-estado-completado.active { color: #1d4ed8; }
[data-mode="light"] .agenda-filtro-estado-pendiente.active  { color: #92400e; }
[data-mode="light"] .agenda-filtro-estado-examen.active     { color: #991b1b; }
[data-mode="light"] .agenda-filtro-estado-evento.active     { color: #7e22ce; }
[data-mode="light"] .agenda-filtro-estado-feriado.active    { color: #047857; }
[data-mode="light"] .agenda-filtro-estado-clase.active      { color: #9d174d; }

/* ---- Estado "Perdido" de una tarea (2026-09-21, ajustado) ----
   Tercer estado explícito junto a completada (ver `perdida` en
   crearEventoAgenda, schema.js). GRIS (#6b7280) a propósito, distinto del
   rojo de "Examen" (#ef4444), y en estilo "outline" como el resto de
   badges/filtros (relleno translúcido + borde + texto en el color propio),
   NO sólido. La raya del tachado se pinta inline desde JS (#9ca3af,
   COLOR_PERDIDA_RAYA en agenda-utils.js); estos hex deben coincidir con
   COLOR_PERDIDA_BORDE / COLOR_PERDIDA_RAYA de ese archivo. */
.badge-perdida { background: rgba(107, 114, 128, 0.18); border-color: #6b7280; color: #d1d5db; }
.agenda-check-completada.perdida { background: rgba(107, 114, 128, 0.18); border-color: #6b7280; }
.agenda-check-completada.perdida::after {
  content: "✕";
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: #d1d5db; font-size: 11px; font-weight: 700; line-height: 1;
}
.agenda-filtro-estado-perdida.active { background: rgba(107, 114, 128, 0.18); border-color: #6b7280; color: #d1d5db; }
[data-mode="light"] .badge-perdida { color: #4b5563; }
[data-mode="light"] .agenda-check-completada.perdida::after { color: #4b5563; }
[data-mode="light"] .agenda-filtro-estado-perdida.active { color: #4b5563; }

/* Ventanita "Completado / Perdido" (agenda-modal.js, se arma por JS al tocar
   el círculo de una tarea vencida). Botones en estilo outline (relleno
   translúcido + borde + texto de color), igual que los badges y el filtro. */
.agenda-selector-estado-card { max-width: 340px; }
.agenda-selector-estado-acciones { display: flex; gap: 10px; margin-top: 4px; }
.agenda-selector-estado-btn {
  flex: 1; padding: 12px 10px; border-radius: 12px;
  border: 1px solid transparent; font-weight: 700; font-size: 0.95rem;
  cursor: pointer; color: var(--text-muted);
  transition: transform var(--transition-fast), filter var(--transition-fast);
}
.agenda-selector-estado-btn:hover { filter: brightness(1.15); }
.agenda-selector-estado-btn:active { transform: scale(0.98); }
.agenda-selector-estado-btn.completado { background: rgba(59, 130, 246, 0.15); border-color: #3b82f6; color: #60a5fa; }
.agenda-selector-estado-btn.perdido { background: rgba(107, 114, 128, 0.18); border-color: #6b7280; color: #d1d5db; }
[data-mode="light"] .agenda-selector-estado-btn.completado { color: #1d4ed8; }
[data-mode="light"] .agenda-selector-estado-btn.perdido { color: #4b5563; }

/* Feature "modo selección" (mantener presionado sobre una tarjeta de Agenda,
   cualquier tipo, activa selección múltiple para borrar varias a la vez) —
   pedido explícito: "se resaltarán como con una luz en el borde en lugar de
   usar checkbox". Halo de luz azul en vez de checkbox propio (ver
   .agenda-item-seleccionado en construirItemEvento, agenda.js). */
.agenda-item-seleccionado {
  box-shadow: 0 0 0 2px #3b82f6, 0 0 12px 1px rgba(59, 130, 246, 0.55);
}

/* Barra flotante inferior de selección múltiple (contador + Cancelar +
   Eliminar) — mismo lenguaje visual que .toast-app-accion de arriba (mismo
   ancho igualado a las tarjetas, mismo radio, mismo centrado), pero sin la
   animación de auto-desaparición: se muestra/oculta a mano según
   estado.agendaModoSeleccion (ver sincronizarBarraSeleccionAgenda en
   agenda.js). */
.agenda-barra-seleccion {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px;
  width: calc(100% - 32px);
  max-width: 760px;
  background: var(--bg-header-solido); color: var(--text-primary);
  border: 1px solid var(--border-glass);
  padding: 10px 10px 10px 18px; border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  z-index: 300;
}
.agenda-seleccion-contador {
  font-weight: 600;
  font-size: 0.85rem;
  white-space: nowrap;
}
.agenda-btn-eliminar-seleccion {
  flex-shrink: 0;
  background: rgba(239, 68, 68, 0.15);
  border: 1px solid #ef4444;
  color: #f87171;
  font-weight: 700;
  font-size: 0.82rem;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast);
}
.agenda-btn-eliminar-seleccion:hover { background: rgba(239, 68, 68, 0.25); }
[data-mode="light"] .agenda-btn-eliminar-seleccion { color: #991b1b; }

/* ---- Vista Calendario (mensual/semanal) — grid de 7 columnas ---- */
.agenda-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.agenda-cal-fila-cabecera { margin-bottom: 2px; }
.agenda-cal-cabecera-dia {
  text-align: center;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  padding: 2px 0;
}
.agenda-cal-celda {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 6px 4px;
  min-height: 52px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
[data-mode="light"] .agenda-cal-celda { background: rgba(0, 0, 0, 0.03); }
.agenda-cal-celda:hover { background: rgba(255, 255, 255, 0.09); }
[data-mode="light"] .agenda-cal-celda:hover { background: rgba(0, 0, 0, 0.06); }
.agenda-cal-celda-hoy { border-color: var(--accent-1); }
/* Ajustes vista Calendario — punto 4: celda del día actualmente desplegado
   debajo del grid — mismo tratamiento (borde + fondo tenue) que usa
   .agenda-semestre-tarjeta.active para "esto está seleccionado ahora
   mismo". Puede convivir con .agenda-cal-celda-hoy (hoy Y seleccionada a la
   vez): el fondo extra sigue distinguiéndola aunque el borde ya coincida. */
.agenda-cal-celda-seleccionada { border-color: var(--accent-1); background: var(--accent-1-10); }
.agenda-cal-celda-fuera-mes { opacity: 0.35; }
.agenda-cal-numero { font-size: 0.8rem; font-weight: 600; }
.agenda-cal-indicadores { display: flex; align-items: center; gap: 3px; margin-top: 4px; flex-wrap: wrap; justify-content: center; }
.agenda-cal-punto { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
/* Ajustes vista Calendario — punto 3 (rediseño): "+N" de puntos que no
   entraron en el tope visible de la celda (ver MAX_PUNTOS_CALENDARIO_
   VISIBLES en agenda-calendario.js) — mismo criterio tipográfico que
   ".muted" (texto chico, tenue) usado para "+N más" en la vista semanal. */
.agenda-cal-punto-extra { font-size: 0.62rem; line-height: 1; color: var(--text-muted); margin-left: 1px; }

/* Submodo Semanal (celdas grandes, con nombres de eventos truncados) */
.agenda-cal-grid-semanal .agenda-cal-celda {
  align-items: stretch;
  min-height: 120px;
  padding: 8px;
  text-align: left;
}
.agenda-cal-nombre-evento {
  display: block;
  font-size: 0.72rem;
  padding: 2px 5px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
[data-mode="light"] .agenda-cal-nombre-evento { background: rgba(0, 0, 0, 0.05); }


/* ---- v8.3: indicador de "desliza para sincronizar" ----
   Vive fijo arriba del todo, centrado, oculto (translateY negativo) hasta
   que app.js lo va acercando en tiempo real según cuánto arrastra el
   usuario (móvil con el dedo, compu con el mouse) desde el tope de la
   página. Al soltar pasado el umbral, queda fijo en su posición "activa" y
   la flecha gira 180° mientras se sincroniza (luego se oculta sola). */
.pull-refresh-indicador {
  position: fixed;
  top: 10px;
  left: 50%;
  transform: translate(-50%, -60px);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--gradient-accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-float);
  z-index: 500;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast);
}
.pull-refresh-indicador.visible { opacity: 1; }
/* Mientras se arrastra, JS controla el translateY directo (inline style)
   para que siga el dedo/mouse en tiempo real — sin transición, para que se
   sienta 1:1 con el gesto. */
.pull-refresh-indicador.arrastrando { transition: none; }
.pull-refresh-flecha {
  font-size: 1.2rem;
  line-height: 1;
  transition: transform var(--transition-fast);
}
.pull-refresh-indicador.listo .pull-refresh-flecha { transform: rotate(180deg); }
.pull-refresh-indicador.sincronizando .pull-refresh-flecha { animation: pull-refresh-girar 0.9s linear infinite; }
@keyframes pull-refresh-girar {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* ---- 2026-08-19: pantalla de carga de sesión (arranque + post-login) ----
   A propósito NO reutiliza el fondo translúcido/blur de .overlay-cargando
   (ese es para tapar un pedacito de la app ya visible detrás, esto en
   cambio reemplaza por completo a la pantalla de login mientras se decide
   si hace falta mostrarla) — usa el mismo fondo con degradados que ya trae
   <body> (ver bg-canvas + accent-glow arriba) para que no haya ningún
   salto de color entre este loader y la pantalla de login o el app-shell
   que aparezcan después. */
.overlay-carga-sesion {
  position: fixed;
  inset: 0;
  z-index: 1500;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: var(--bg-canvas);
}
.overlay-carga-sesion.oculto { display: none; }
.carga-sesion-anillo-wrap {
  position: relative;
  width: 84px;
  height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.carga-sesion-logo {
  width: 40px;
  height: 40px;
  object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.35));
}
.carga-sesion-anillo {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid var(--accent-1-10, transparent);
  border-top-color: var(--accent-1);
  animation: carga-sesion-girar 0.9s linear infinite;
}
.carga-sesion-texto {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-secondary);
  letter-spacing: 0.01em;
}
@keyframes carga-sesion-girar {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
[data-rendimiento="reducido"] .carga-sesion-anillo { animation-duration: 1.4s; }

/* ---- v8.3: overlay de carga reutilizable — 3 puntitos en ola ---- */
.overlay-cargando {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg-canvas) 55%, transparent);
  backdrop-filter: blur(2px);
  z-index: 700;
  transition: opacity var(--transition-fast);
}
.overlay-cargando.oculto { display: none; }
.puntitos-carga {
  display: flex;
  gap: 10px;
}
.puntitos-carga span {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--gradient-accent);
  box-shadow: var(--shadow-float);
  animation: puntito-rebote 1.1s ease-in-out infinite;
}
.puntitos-carga span:nth-child(1) { animation-delay: 0s; }
.puntitos-carga span:nth-child(2) { animation-delay: 0.15s; }
.puntitos-carga span:nth-child(3) { animation-delay: 0.3s; }
/* Destello de resaltado al navegar desde otra sección a una tarjeta
   puntual (ver desplazarYResaltarElemento en componentes.js) — un halo con
   el color de acento que aparece y se desvanece solo, sin dejar rastro
   permanente ni pisar el box-shadow propio de la tarjeta (ej. la barra de
   categoría de una materia) una vez termina. */
.destello-resaltado {
  animation: destello-resaltado 1.6s ease-out;
}
/* Variante para la tarjeta de profesor en Comunidad, al navegar ahí desde
   la tarjeta flotante de Semestres (ver navegarYResaltarProfesor en
   comunidad.js) — pedido explícito de que el brillo dure 2s exactos en
   este caso puntual, sin tocar el 1.6s que ya usan otros destellos de la
   app (ej. navegarASemestre). */
.com-tarjeta-profesor.destello-resaltado {
  animation-duration: 2s;
}
@keyframes destello-resaltado {
  0% { box-shadow: 0 0 0 3px var(--accent-1), 0 0 28px 6px var(--accent-glow-1); }
  70% { box-shadow: 0 0 0 3px var(--accent-1), 0 0 28px 6px var(--accent-glow-1); }
  100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}

/* v1.16: animar box-shadow con blur grande (el glow de 28px) es de lo más
   caro que existe en repintado — bajo rendimiento se anima la misma idea
   (aro de acento que aparece y se desvanece, mismo color, mismo timing:
   solo se cambia animation-name, la duration/easing de cada variante -
   1.6s acá, 2s en .com-tarjeta-profesor - se respeta igual) pero sin el
   segundo layer difuso. */
[data-rendimiento="reducido"] .destello-resaltado {
  animation-name: destello-resaltado-reducido;
}
@keyframes destello-resaltado-reducido {
  0% { box-shadow: 0 0 0 3px var(--accent-1); }
  70% { box-shadow: 0 0 0 3px var(--accent-1); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

@keyframes puntito-rebote {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.6; }
  30% { transform: translateY(-16px); opacity: 1; }
}

/* =========================================================================
   B.3 (v8/v9) — Vista de Mapa interactivo del Plan de Estudios
   ========================================================================= */

.vista-encabezado {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.vista-controles {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 4px;
}

/* ---- V1.10: filas de la tarjeta "Vista" ----
   Cada línea (2, 3, 4) tiene hasta 2 elementos: uno anclado a la izquierda,
   otro a la derecha. Juntos deben ocupar TODO el ancho disponible; si no
   caben cómodos uno junto al otro, se pasan a 2 líneas (flex-wrap). */
.vista-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
}
.vista-fila > * { flex: 1 1 260px; min-width: 0; }
/* El botón "Descargar" y el control de zoom no necesitan crecer tanto como
   un pill-group completo — se quedan a su tamaño natural. */
.vista-fila > .mapa-zoom-controles,
.vista-fila > .btn { flex: 0 1 auto; }

/* ---- V1.10: pill-group que NUNCA corta texto con "…" ----
   A diferencia del .pill-item genérico (que trunca si no cabe), estos
   botones conservan su ancho natural; si el conjunto no cabe en el grupo,
   el propio .pill-group (ya es overflow-x:auto) se vuelve scrolleable en
   vez de cortar las letras. Cuando SÍ hay espacio de sobra, los botones
   crecen parejo para repartirse todo el ancho del grupo. */
.pill-group-vista .pill-item {
  flex: 1 1 0;
  min-width: max-content;
  overflow: visible;
  text-overflow: unset;
  white-space: nowrap;
}

/* ---- Finanzas: pestañas responsivas (Resumen/Semestres/Gastos generales
   U/Beneficios) — v2.8.8, 2026-08-11 ----
   A diferencia de .pill-group genérico (trunca con "…" y se vuelve
   scrolleable horizontal si no caben) y de .pill-group-vista (conserva el
   ancho natural pero también resuelve el desborde con scroll horizontal),
   acá el pedido es explícito: el texto JAMÁS se corta, ni con "…" ni
   apretado ni recortado — si no entran cómodas en una fila, las 4
   pestañas pasan a apilarse en una columna, cada una ocupando el ancho
   completo del contenedor, creciendo en alto lo que haga falta.

   Se usa una @container en vez de un @media de viewport porque el ancho
   real disponible para este grupo depende del layout (sidebar colapsada o
   expandida, con o sin panel lateral) y no del viewport completo — un
   @media fijo podría no activarse (o activarse tarde) según ese contexto
   aunque las pestañas ya estén apretadas ahí adentro. */
.finanzas-tabs-contenedor {
  container-type: inline-size;
  container-name: finanzas-tabs;
}

.pill-group-finanzas {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  background: rgba(0, 0, 0, 0.18);
  padding: 4px;
  border-radius: var(--radius-pill);
}
[data-mode="light"] .pill-group-finanzas { background: rgba(0, 0, 0, 0.05); }

.pill-group-finanzas .pill-item {
  flex: 1 1 0;
  min-width: 0;
  overflow: visible;
  text-overflow: unset;
  white-space: nowrap;
  text-align: center;
}

/* Ancho insuficiente para una fila cómoda -> columna, ancho completo por
   botón, alto libre (el texto decide cuánto crece el botón, nunca al
   revés). 560px cubre con margen el peor caso ("Gastos generales U" es la
   etiqueta más larga de las 4) sin activarse de más en anchos donde en
   realidad sí entrarían bien. */
@container finanzas-tabs (max-width: 560px) {
  .pill-group-finanzas {
    flex-direction: column;
    border-radius: var(--radius-lg);
  }
  .pill-group-finanzas .pill-item {
    width: 100%;
    white-space: normal;
    height: auto;
    padding: 10px 14px;
  }
}

/* Fallback para navegadores sin soporte de container queries (Safari <16 —
   poco frecuente ya en 2026, pero de costo casi nulo cubrirlo dado que acá
   el requisito de "nunca cortar texto" es duro): un @media de viewport
   conservador que como mínimo evita el corte, aunque no sea tan preciso
   como la container query de arriba frente a un sidebar colapsado/expandido. */
@supports not (container-type: inline-size) {
  @media (max-width: 480px) {
    .pill-group-finanzas { flex-direction: column; }
    .pill-group-finanzas .pill-item {
      width: 100%;
      white-space: normal;
      height: auto;
    }
  }
}

.mapa-zoom-controles {
  display: flex;
  align-items: center;
  gap: 8px;
}
.mapa-zoom-btn {
  padding: 6px 14px;
  min-width: 40px;
  font-size: 1.05rem;
  line-height: 1;
}
.mapa-zoom-etiqueta {
  min-width: 44px;
  text-align: center;
  font-size: 0.85rem;
}

/* ---- V1.x: botón "solo ícono" sin fondo — pantalla completa, girar
   pantalla, zoom -/+ y el chevrón de ocultar/mostrar controles del mapa.
   Mismo espíritu que .flecha-plan (glyph sin caja de botón), pero
   pensado para íconos cuadrados en vez de flechas de texto grueso. ---- */
.btn-icono-fantasma {
  background: none;
  border: none;
  padding: 6px 10px;
  color: var(--text-secondary);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--transition-fast), background var(--transition-fast);
}
.btn-icono-fantasma:hover { color: var(--text-primary); background: var(--bg-panel); }
.btn-icono-fantasma:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.btn-icono-fantasma:disabled { opacity: 0.35; cursor: default; }

/* ---- V1.x (rediseño 3): fila de íconos ⛶ / ⌃⌄ / 🔄 del bloque de
   controles de la tarjeta "Vista" en modo Mapa. Los 3 son ahora SVG del
   mismo tamaño/trazo (ver plan-mapa.js), así que ya no hace falta ningún
   ajuste especial de márgen/tamaño de fuente para el chevrón — basta con
   alinearlos en una fila. El gap entre ellos se duplica (2px → 4px) SOLO
   cuando hay ancho de pantalla de sobra, para no apretar el layout en
   celulares angostos (pedido explícito). ---- */
.mapa-controles-centro {
  display: flex;
  align-items: center;
  gap: 2px;
}
@media (min-width: 480px) {
  .mapa-controles-centro { gap: 4px; }
}

/* ---- V1.x: pantalla completa (Fullscreen API) sobre la tarjeta "Vista"
   completa — la .glass-card de fondo semitransparente se ve mal sobre el
   negro por defecto del navegador en pantalla completa, así que acá se
   fuerza un fondo sólido y se ocupa toda la ventana. El :fullscreen nativo
   ya saca al elemento de su posición normal en el documento, no hace falta
   position/inset. Se agrega también con prefijo -webkit- para Safari. ---- */
.vista-card:fullscreen,
.vista-card:-webkit-full-screen {
  width: 100vw;
  height: 100vh;
  /* REGRESIÓN (corregida de nuevo, 2026-09-12): en móvil real, 100vh se
     calcula sobre el viewport MÁS GRANDE (con la barra del navegador
     oculta), que es más alto que lo que de verdad se ve en pantalla
     mientras esa barra sigue visible — la tarjeta terminaba más alta que
     el área visible real, y la parte de abajo (mapa/controles) quedaba
     literalmente por debajo del borde inferior de la pantalla en vez de
     ocupar/mostrarse por encima como corresponde a pantalla completa.
     100dvh (alto real visible en cada momento) YA se usa para exactamente
     este mismo problema en .app-sidebar y el drawer móvil (ver esas 2
     reglas) — ahí se arregló el 2026-08-23 pero nunca se replicó acá,
     así que el mismo bug reaparecía en este tercer lugar. Los 3 casos de
     "ocupar toda la pantalla real" del proyecto son: .app-sidebar, el
     drawer móvil de Enlaces rápidos, y este .vista-card:fullscreen — si
     agregás un CUARTO elemento con esta misma necesidad, replicá el
     mismo par vh/dvh (nunca solo uno de los tres existentes). Se declara
     DESPUÉS de 100vh (nunca antes) para que actúe como mejora progresiva
     sin romper nada en navegadores sin soporte de dvh. */
  height: 100dvh;
  max-width: none;
  border-radius: 0;
  background: var(--bg-canvas);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* V1.x: pedido explícito — reducir a la mitad (24px del .glass-card base
     → 12px) el espacio entre el borde superior de la pantalla y los
     botones. SOLO dentro de pantalla completa; el exterior sigue con el
     padding normal de .glass-card, sin cambios.
     V1.x (rediseño 3): pedido explícito de nuevo — reducir ESTE espacio
     (ya reducido antes) un 66% adicional: 12px → 4px. */
  padding-top: 4px;
  /* V1.x: pedido explícito — los laterales también se ajustan para que las
     flechas ‹ › del mapa (ver .flecha-plan) queden cerca del borde de la
     pantalla sin espacio de sobra, pero sin quedar 100% pegadas al borde
     (se deja un margen mínimo visible). SOLO dentro de pantalla completa. */
  padding-left: 8px;
  padding-right: 8px;
}
.vista-card:fullscreen .mapa-wrapper,
.vista-card:-webkit-full-screen .mapa-wrapper {
  flex: 1;
  min-height: 0;
  /* V1.x: pedido explícito — reducir a la mitad (16px del .mapa-wrapper
     base → 8px) el espacio entre la línea de Descargar/⛶/Zoom y el mapa.
     SOLO dentro de pantalla completa (afuera sigue en 16px, sin cambios).
     V1.x (rediseño 3): pedido explícito de nuevo — reducir ESTE espacio
     (ya reducido antes) un 50% adicional: 8px → 4px. */
  margin-top: 4px;
}
.vista-card:fullscreen .mapa-scroll,
.vista-card:-webkit-full-screen .mapa-scroll {
  height: 100%;
}

/* ---- Contenedor con flechas ‹ › (mismo patrón visual que .scroll-con-flechas) ---- */
.mapa-wrapper {
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin-top: 16px;
}
.mapa-scroll {
  flex: 1;
  overflow-x: auto;
  /* Punto 1 (2026-08-07, pedido explícito): antes overflow-y:hidden +
     touch-action:pan-x bloqueaban el arrastre libre de un dedo en vertical
     — solo dejaban pasar el pinch-zoom (2 dedos, manejado a mano en JS con
     preventDefault, ver plan-mapa.js). Con pan-y habilitado el navegador
     deja pasar el arrastre vertical de un dedo de forma nativa, y el
     handler de touchmove de plan-mapa.js sigue haciendo su propio
     preventDefault cuando detecta 2 dedos, así que el pinch-zoom no se
     rompe. */
  overflow-y: auto;
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.mapa-scroll:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

.mapa-sizer { position: relative; }
.mapa-track {
  position: relative;
  transform-origin: top left;
  will-change: transform;
}
.mapa-caminos {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: visible;
  pointer-events: none;
}
.mapa-columnas {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  gap: 28px;
  padding: 4px 8px 16px;
}
.mapa-columna {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 190px;
  flex-shrink: 0;
}
.mapa-columna-titulo {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.85rem;
  text-align: center;
  color: var(--text-secondary);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-glass);
  margin-bottom: 2px;
}

.mapa-nodo {
  --nodo-color: #94a3b8;
  --nodo-color-sombra: transparent;
  /* V10: fondo SÓLIDO (no --bg-panel, que es semitransparente en todas las
     paletas) para que el camino de desbloqueo nunca se vea "opacado" de
     forma rara al pasar detrás de una tarjeta. */
  background: var(--bg-canvas);
  border: 1.5px solid var(--nodo-color);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
  position: relative;
  /* Última instrucción V1.10: sombra sutil tintada según el color de borde activo. */
  box-shadow: 0 4px 12px -4px var(--nodo-color-sombra);
}
.mapa-nodo::before {
  content: "";
  position: absolute;
  top: 6px; left: 6px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--nodo-color);
}
.mapa-nodo:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px -4px var(--nodo-color-sombra), var(--shadow-float);
}
/* V1.10: línea 1 = luz (::before) + código + créditos. En modo normal/
   compacto es un wrapper "invisible" (display:contents) y el código se ve
   exactamente igual que antes; los créditos solo aparecen en "extendido". */
.mapa-nodo-fila1 { display: contents; }
.mapa-nodo-codigo {
  font-family: monospace;
  font-size: 0.7rem;
  color: var(--text-muted);
  padding-left: 14px;
}
.mapa-nodo-creditos { display: none; }
.mapa-nodo-nombre {
  font-size: 0.78rem;
  line-height: 1.25;
  color: var(--text-primary);
  padding-left: 14px;
}
.mapa-nodo-en-camino {
  box-shadow: 0 0 0 2px var(--nodo-color), 0 0 14px var(--nodo-color);
  transform: translateY(-1px);
}

/* ---- V1.10: tamaño de tarjeta (Compacto / Normal / Extendido) ---- */
.mapa-wrapper[data-tamanio="compacto"] .mapa-columna { width: 133px; } /* 190px - 30% */
.mapa-wrapper[data-tamanio="extendido"] .mapa-columna { width: 285px; } /* 190px + 50% */
.mapa-wrapper[data-tamanio="extendido"] .mapa-nodo-fila1 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding-left: 14px;
}
.mapa-wrapper[data-tamanio="extendido"] .mapa-nodo-fila1 .mapa-nodo-codigo { padding-left: 0; }
.mapa-wrapper[data-tamanio="extendido"] .mapa-nodo-creditos {
  display: inline;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ---- V1.10: tema SOLO del interior de las tarjetas del mapa (independiente
   del tema general de la app) — muy simple, blanco+grises / negro+grises. ---- */
.mapa-wrapper[data-tema-tarjeta="clara"] .mapa-nodo { background: #ffffff; }
.mapa-wrapper[data-tema-tarjeta="clara"] .mapa-nodo-nombre { color: #18181b; }
.mapa-wrapper[data-tema-tarjeta="clara"] .mapa-nodo-codigo,
.mapa-wrapper[data-tema-tarjeta="clara"] .mapa-nodo-creditos { color: #52525b; }

.mapa-wrapper[data-tema-tarjeta="oscura"] .mapa-nodo { background: #0b0b0d; }
.mapa-wrapper[data-tema-tarjeta="oscura"] .mapa-nodo-nombre { color: #fafafa; }
.mapa-wrapper[data-tema-tarjeta="oscura"] .mapa-nodo-codigo,
.mapa-wrapper[data-tema-tarjeta="oscura"] .mapa-nodo-creditos { color: #a1a1aa; }

/* ---- V1.10: "Tema default" al exportar el mapa como imagen — paleta
   neutra temporal (blanco+grises en claro, negro+grises en oscuro), pisando
   momentáneamente las variables de color mientras dura la captura. ---- */
html.exportar-tema-default[data-mode="light"] {
  --bg-canvas: #ffffff;
  --bg-panel: #f4f4f5;
  --border-glass: #d4d4d8;
  --text-primary: #18181b;
  --text-secondary: #52525b;
  --text-muted: #71717a;
  --accent-1: #71717a;
  --accent-2: #52525b;
}
html.exportar-tema-default:is([data-mode="dark"], [data-mode="true-dark"]) {
  --bg-canvas: #000000;
  --bg-panel: #18181b;
  --border-glass: #3f3f46;
  --text-primary: #fafafa;
  --text-secondary: #d4d4d8;
  --text-muted: #a1a1aa;
  --accent-1: #a1a1aa;
  --accent-2: #71717a;
}

/* ---- Efecto neón del camino de desbloqueo ---- */
.mapa-camino-linea {
  fill: none;
  stroke: var(--accent-1);
  stroke-width: 2.5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 3px var(--accent-1)) drop-shadow(0 0 8px var(--accent-1));
  opacity: 0.9;
}
[data-mode="light"] .mapa-camino-linea {
  stroke: var(--accent-2);
  stroke-width: 3;
  filter: drop-shadow(0 0 2px var(--accent-2)) drop-shadow(0 0 6px var(--accent-2));
  opacity: 0.85;
}

@media (max-width: 640px) {
  .mapa-columna { width: 150px; }
  .mapa-nodo-nombre { font-size: 0.72rem; }
}

/* =========================================================================
   v1.14.1: MODO DE RENDIMIENTO (reduce carga de GPU en equipos con
   gráficos integrados). Se activa con [data-rendimiento="reducido"] en
   <html>, igual patrón que data-palette / data-mode. No se toca ningún
   estilo por defecto — todo vive dentro de estas reglas.
   ========================================================================= */

/* 2. y 3. — --shadow-glass/--shadow-float y --transition-fast/--transition-med
   se reutilizan en TODO el sistema (tarjetas, botones, pills, modales,
   nodos del mapa, toasts, etc.), así que sobreescribirlas aquí ya reduce
   sombras grandes y anima casi instantáneo en un solo golpe, sin tener
   que tocar cada selector uno por uno. */
[data-rendimiento="reducido"] {
  --shadow-glass: 0 2px 6px rgba(0, 0, 0, 0.28);
  --shadow-float: 0 2px 6px rgba(0, 0, 0, 0.3);
  --transition-fast: 0.05s linear;
  --transition-med: 0.05s linear;
}

/* v1.16 (2026-08-21): background-attachment: fixed en <body> obliga a
   muchos navegadores (sobre todo Android en GPUs integradas) a repintar
   los 3 radial-gradient en CADA frame de scroll, en vez de componerlos una
   sola vez — es, con diferencia, lo más caro de toda la app en equipos de
   gama baja. "scroll" en vez de "fixed" elimina ese costo por completo; el
   único cambio perceptible es que el glow ya no queda "clavado" al viewport
   mientras se scrollea (un detalle que nadie nota en el uso normal), la
   estética del fondo (colores, forma, posición inicial) queda idéntica. */
[data-rendimiento="reducido"] body {
  background-attachment: scroll;
}

/* 1. backdrop-filter: blur(...) fuera. Antes esto subía de "opacidad" con
   color-mix(var(--bg-card) 90%, var(--bg-canvas) 10%) — pero --bg-card y
   --bg-panel YA son semitransparentes en casi todas las paletas (ver los
   bloques [data-palette=...]: rgba(255,255,255,0.05) en oscuro es típico,
   incluso 0.75 en claro). color-mix combina TAMBIÉN el canal alfa, así
   que "90% de un color con alfa 0.05" da un resultado con alfa ≈ 0.9*0.05
   + 0.1*1 = 0.145 — apenas 14.5% opaco, NO el 90% que el nombre de la
   variable y los comentarios anteriores asumían. Este es el motivo real
   por el que el reporte de transparencia volvió 3 veces (50% → 22.5% →
   10% "de transparencia") sin que nunca mejorara de forma consistente:
   se ajustaba el porcentaje de la mezcla, pero la mezcla en sí nunca
   garantizaba opacidad real cuando el color de entrada ya venía con
   alfa bajo.

   REGRESIÓN (2026-09-12) — FIX DE RAÍZ: pedido explícito de que la
   transparencia baje a 0% exacto en este modo (ya no "10%" ni ningún
   porcentaje chico), y que quede reservada por completo al modo Fancy.
   En vez de seguir ajustando el porcentaje de color-mix (que nunca fue
   la pieza rota), se compone el fondo en 2 capas DENTRO del mismo
   elemento: una capa sólida (--bg-canvas, sin alfa) por debajo y el tono
   de --bg-card/--bg-panel (con su alfa normal) encima, como
   background-image. El navegador aplana esas 2 capas del propio
   elemento ANTES de pintarlas sobre lo que sea que haya detrás en la
   página — el resultado siempre queda 100% opaco sin importar cuál sea
   el alfa real de --bg-card/--bg-panel en cada paleta (actual o
   cualquiera que se agregue a futuro), así que este mismo patrón queda
   centralizado acá y no depende de mantener un porcentaje "mágico"
   sincronizado a mano con 24 valores de paleta distintos.
   IMPORTANTE: no reemplazar esto por color-mix(var(--bg-card) X%,
   var(--bg-canvas) Y%) de nuevo — ver el párrafo de arriba, ese enfoque
   NUNCA garantiza opacidad real. */
[data-rendimiento="reducido"] .glass-card {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: var(--bg-canvas);
  background-image: linear-gradient(var(--bg-card), var(--bg-card));
}
[data-rendimiento="reducido"] .glass-panel {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: var(--bg-canvas);
  background-image: linear-gradient(var(--bg-panel), var(--bg-panel));
}
[data-rendimiento="reducido"] .mobile-topbar {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: var(--bg-canvas);
  background-image: linear-gradient(var(--bg-panel), var(--bg-panel));
}
[data-rendimiento="reducido"] .modal-overlay {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(0, 0, 0, 0.9);
}
[data-mode="light"][data-rendimiento="reducido"] .modal-overlay {
  background: rgba(30, 41, 59, 0.67);
}
[data-rendimiento="reducido"] .overlay-cargando {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: color-mix(in srgb, var(--bg-canvas) 94%, transparent);
}
/* v1.16: el dropdown de los selects custom (.select-custom-lista) ya usa
   --bg-header-solido, que es un color sólido sin alpha en toda paleta —
   el blur de acá no tapaba nada realista detrás, así que sacarlo es
   ahorro puro sin ningún cambio visual perceptible. */
[data-rendimiento="reducido"] .select-custom-lista {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Reporte de Wagner (ronda de ajustes visuales): la barra lateral se veía
   demasiado transparente en Modo Rendimiento. Causa: #app-sidebar define su
   fondo con un selector por ID (línea ~1204/1207) — más específico que
   cualquier regla de .glass-panel — así que aunque comparta esa clase,
   nunca heredaba el fix de arriba (cortar la transparencia a la mitad).
   Se repite acá el mismo criterio (color-mix 90%/10%, ajustado 2026-08-23
   junto con el resto de reglas de arriba) pero apuntando
   directo al ID, con la variante de color correspondiente a cada modo.

   FIX (punto 4, 2026-08-23): esta regla reemplazaba el `background` COMPLETO
   del sidebar, incluyendo el `linear-gradient(...)` que la regla base
   (línea ~1251/1254) suma encima de --bg-panel para que el sidebar quede
   "un poco menos transparente que el resto de tarjetas de vidrio" (fancy).
   Como Rendimiento Reducido es ahora el modo por defecto (ver punto 2), esa
   capa de gradiente extra se perdía SIEMPRE, dejando el sidebar con la
   transparencia plana de un glass-panel normal — de ahí que pasara de
   "transparencia ligera (como en fancy)" a "casi total". Se reincorpora el
   mismo linear-gradient de la regla base, solo con el color-mix (sin
   backdrop-filter) como capa de fondo en vez de --bg-panel puro.

   REGRESIÓN (2026-09-12) — mismo bug de fondo que en .glass-card/.glass-
   panel de arriba: color-mix(var(--bg-panel) 90%, var(--bg-canvas) 10%)
   nunca daba 90% de opacidad real cuando --bg-panel ya venía con alfa
   bajo (ver el comentario largo junto a [data-rendimiento="reducido"]
   .glass-card, misma causa exacta). Se reemplaza con el mismo patrón de
   2 capas dentro del elemento (sólido abajo + tinte encima) para llegar
   a 0% de transparencia real, sin tocar la capa de gradiente extra del
   sidebar (esa sigue igual, es una capa aparte con su propio propósito
   visual, no la fuente del bug). */
[data-rendimiento="reducido"]:is([data-mode="dark"], [data-mode="true-dark"]) #app-sidebar {
  background-color: var(--bg-canvas);
  background-image:
    linear-gradient(rgba(0,0,0,0.16), rgba(0,0,0,0.16)),
    linear-gradient(var(--bg-panel), var(--bg-panel));
}
[data-rendimiento="reducido"][data-mode="light"] #app-sidebar {
  background-color: var(--bg-canvas);
  background-image:
    linear-gradient(rgba(255,255,255,0.16), rgba(255,255,255,0.16)),
    linear-gradient(var(--bg-panel), var(--bg-panel));
}

/* 2. Glow del botón primario en hover y de la "luz" de disponibilidad:
   se mantiene el color que comunica el estado, sin el brillo expandido. */
[data-rendimiento="reducido"] .btn-primary:hover {
  box-shadow: 0 2px 6px var(--accent-glow-1);
}
[data-rendimiento="reducido"] .luz-punto.disponible {
  box-shadow: 0 0 0 2px var(--accent-2, #34d399);
}

/* 5. Camino de desbloqueo del mapa: línea sólida, sin el glow neón. */
[data-rendimiento="reducido"] .mapa-camino-linea {
  filter: none;
}
/* v1.16: mismo criterio con los nodos "en camino" — se conserva el anillo
   sólido de 2px (comunica el estado igual de claro) y se saca el segundo
   layer difuso de 14px, que con varios nodos desbloqueados a la vez en
   pantalla es varios box-shadow con blur superpuestos de una. */
[data-rendimiento="reducido"] .mapa-nodo-en-camino {
  box-shadow: 0 0 0 2px var(--nodo-color);
}

/* 4. Los degradados de fondo del body ya son estáticos (sin keyframes
   asociados), así que no requieren ningún cambio adicional aquí. */


/* =========================================================================
   v1.13: PANEL "CREAR MI PALETA"
   Agregar esto al final de css/design-system.css. Reutiliza las variables
   del sistema (--radius, --shadow, --font, etc.) para que el panel se vea
   consistente con la paleta que esté activa en ese momento.
   ========================================================================= */

.ppz-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  overflow-y: auto;
  /* BUG FIX v1.15.3: sin esto, el rebote de scroll al tope del overlay se
     encadenaba hacia el body y el navegador lo tomaba como gesto de "pull
     to refresh" — recargando la página a mitad de la edición de paleta. */
  overscroll-behavior: contain;
}

.ppz-panel {
  width: 100%;
  max-width: 460px;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overscroll-behavior: contain;
}

.ppz-titulo { margin: 0; }
.ppz-subtitulo { font-size: 0.85rem; margin: 0; }

/* ---- Paso 1: elegir paleta base ---- */
.ppz-grid-base {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 10px;
}
.ppz-swatch-base {
  cursor: pointer;
  border-radius: var(--radius-md);
  padding: 14px 8px;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: #ffffff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  min-height: 52px;
}
.ppz-cancelar-base { align-self: center; }

/* ---- Paso 2: los 5 selectores ---- */
.ppz-campos {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* v1.15.4: reemplaza a .ppz-campos — 2 columnas (Fondo/Tarjeta/Borde a la
   izquierda, Detalles/Luz/Degradado a la derecha) para ahorrar espacio
   vertical en el panel. En pantallas muy angostas colapsa a 1 columna. */
.ppz-campos-columnas {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 14px;
}
.ppz-columna {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
@media (max-width: 380px) {
  .ppz-campos-columnas {
    grid-template-columns: 1fr;
  }
}

.ppz-grupo { display: flex; flex-direction: column; gap: 8px; }
.ppz-grupo-label { font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); }

.ppz-fila-slider {
  display: flex;
  align-items: center;
  gap: 10px;
}
.ppz-swatch-vivo {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 2px solid var(--border-glass);
  box-shadow: var(--shadow-float);
}

.ppz-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 14px;
  border-radius: var(--radius-pill);
  outline: none;
  cursor: pointer;
}
/* Thumb: EXACTO mismo lenguaje que .ppz-rueda-perilla (círculo de 18px
   tintado con el color elegido del degradado, anillo blanco de 2px,
   shadow-float) — antes era un círculo blanco genérico de navegador que
   desentonaba con el resto del panel. */
.ppz-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ppz-intensidad-thumb, var(--accent-1));
  border: 2px solid #ffffff;
  box-shadow: var(--shadow-float);
  cursor: pointer;
}
.ppz-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ppz-intensidad-thumb, var(--accent-1));
  border: 2px solid #ffffff;
  box-shadow: var(--shadow-float);
  cursor: pointer;
}
.ppz-slider::-moz-range-track { border: none; background: transparent; }

/* ---- Vista previa en vivo (v1.15.3: 2 capas, lienzo + tarjeta) ---- */
.ppz-preview {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ppz-preview-titulo { color: var(--text-primary); margin: 0; }
.ppz-preview-texto { color: var(--text-secondary); margin: 0 0 4px; }

/* "Lienzo": representa el <body> real — mismo fondo sólido + mismo glow
   radial que design-system.css ya aplica sobre body, así el usuario ve el
   fondo de la app tal cual, no solo un rectángulo plano. */
.ppz-preview-lienzo {
  position: relative;
  border-radius: var(--radius-lg);
  padding: 22px;
  overflow: hidden;
  background: var(--bg-canvas);
  background-image:
    radial-gradient(circle at 15% 10%, var(--accent-glow-1) 0%, transparent 45%),
    radial-gradient(circle at 85% 0%, var(--accent-glow-2) 0%, transparent 40%),
    radial-gradient(circle at 50% 100%, var(--accent-glow-1) 0%, transparent 55%);
  transition: background-color var(--transition-med);
}

/* "Tarjeta": la misma .glass-card real, flotando sobre el lienzo — así se
   distingue de un vistazo fondo vs. tarjeta, que es justo lo que antes no
   se notaba. */
.ppz-preview-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px;
}
.ppz-preview-card-titulo { color: var(--text-primary); margin: 0; font-size: 1rem; }
.ppz-preview-card-texto { color: var(--text-secondary); margin: 0; font-size: 0.85rem; }

.ppz-preview-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  color: var(--text-muted);
  font-size: 0.78rem;
}

.ppz-preview-fila {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ppz-preview-btn { align-self: auto; }

.ppz-preview-badge {
  /* Mismo fix v3 que .badge-accent: solo el fondo cambia, ver tema.js. */
  background: var(--badge-bg-solid, var(--accent-1-10));
  color: var(--accent-1);
  border: 1px solid var(--accent-1-20);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  font-size: 0.78rem;
  font-weight: 600;
}

.ppz-preview-danger {
  color: var(--color-danger);
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
}

/* ---- Botones inferiores ---- */
.ppz-fila-botones {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

/* ---- 15ª opción del grid principal ---- */
.ppz-swatch-crear {
  cursor: pointer;
  color: #ffffff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  font-size: 0.72rem;
}
.ppz-btn-editar {
  grid-column: 1 / -1;
  margin-top: 6px;
}

/* =========================================================================
   v1.15 (Parte 2): DEGRADADO CONFIGURABLE
   Toggle + color libre + intensidad + rueda de ángulo, dentro del mismo
   panel "Crear mi paleta". El contenido se pliega con max-height/opacity
   (no display:none abrupto) para que activar el switch se sienta parte del
   mismo lenguaje de transiciones que ya usa el resto de la app.
   ========================================================================= */

/* v1.15.7: bloque de ancho completo con el toggle + rueda + intensidad —
   va debajo de .ppz-campos-columnas (después de Borde) y arriba de la
   vista previa, ver abrirPanelDeEdicion. El color del degradado vive
   aparte, dentro de la columna 2 (ver .ppz-degradado-color-inline). */
.ppz-degradado-bloque {
  display: flex;
  flex-direction: column;
  padding-top: 16px;
  border-top: 1px solid var(--border-glass);
}

.ppz-degradado-toggle-fila {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.ppz-degradado-toggle-fila .ppz-grupo-label { margin: 0; }

/* Cuerpo plegable de abajo: rueda + intensidad. El campo de color tiene su
   propio .oculto aparte (ver más abajo) porque vive en otro contenedor
   (columna 2) — ambos se togglean juntos desde la misma función en JS
   (sincronizarVisibilidad), así nunca quedan desincronizados. */
.ppz-degradado-contenido {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 14px;
  max-height: 600px;
  opacity: 1;
  overflow: hidden;
  transition: max-height var(--transition-med), opacity var(--transition-med), margin-top var(--transition-med);
}
.ppz-degradado-contenido.oculto {
  max-height: 0;
  opacity: 0;
  margin-top: 0;
  pointer-events: none;
}

.ppz-subetiqueta {
  font-size: 0.78rem;
  opacity: 0.85;
  margin-top: -4px;
}

/* Campo de color del degradado: vive DENTRO de la columna 2, mismo look
   que Detalles/Luz (.ppz-grupo). Se oculta con display:none simple — es
   un solo campo, no necesita animación de plegado como el bloque grande. */
.ppz-degradado-color-inline.oculto {
  display: none;
}

/* Rueda de ángulo + intensidad vertical, centradas en el bloque ancho
   completo de abajo. */
.ppz-degradado-controles-derecha {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 20px;
}

/* ---- Color libre del degradado (input nativo, mismo swatch redondo que
   ya usan los otros 5 campos, para que se sienta el mismo control) ---- */
.ppz-color-nativo {
  width: 48px;
  height: 34px;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0;
  cursor: pointer;
  background: transparent;
  flex-shrink: 0;
}
.ppz-color-nativo::-webkit-color-swatch-wrapper { padding: 0; }
.ppz-color-nativo::-webkit-color-swatch {
  border: 2px solid var(--border-glass);
  border-radius: var(--radius-sm);
}
.ppz-color-nativo::-moz-color-swatch {
  border: 2px solid var(--border-glass);
  border-radius: var(--radius-sm);
}

/* El fondo de esta barra se pinta inline en JS (pintarFondoIntensidad) con
   los 3 colores reales del degradado — así el usuario ve dónde va a caer
   el color del medio antes de soltar el slider. */
.ppz-slider-intensidad { background: var(--bg-panel); }

/* v1.15.4: intensidad como barra VERTICAL, misma altura que la rueda
   (112px) para que ambas se vean como un solo bloque de controles. */
.ppz-intensidad-vertical-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding-top: 4px;
}
.ppz-intensidad-vertical-pista {
  height: 112px;
  width: 34px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-glass);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.ppz-slider-vertical {
  /* Estándar moderno para <input type="range"> vertical (Chrome/Edge 89+,
     Firefox, Safari 16.4+) — rota el eje del control sin transform, así el
     thumb y el track de .ppz-slider se comportan igual que en la versión
     horizontal, solo que en el otro eje. */
  writing-mode: vertical-lr;
  direction: rtl;
  flex: none; /* .ppz-slider trae flex:1 (pensado para el layout horizontal) */
  /* BUG FIX (reportado: "es una pill pero tiene un slider default adentro"):
     acá abajo había además `-webkit-appearance: slider-vertical` como
     "fallback" — pero esa propiedad NO es un fallback inofensivo, es una
     segunda declaración de la MISMA propiedad que `appearance: none` de
     `.ppz-slider`. Al venir después en la hoja de estilos, gana ella y
     REVIERTE el `none`: el navegador vuelve a dibujar su control nativo
     (surco gris + thumb redondo del sistema) ENCIMA del fondo degradado y
     el thumb personalizado que se seteaban en este mismo elemento — de ahí
     el "slider default" flotando dentro de la pill. Se quita del todo:
     writing-mode ya cubre a todos los navegadores que de verdad importan
     hoy, y con appearance:none heredado de .ppz-slider el control se ve
     100% con nuestro estilo. */
  /* Barra completa: ocupa casi todo el ancho de la pill (34px), no una
     tira angosta centrada con huecos de la sombra interior a los lados. */
  width: 30px;
  height: 100%;
}

/* ---- Rueda de ángulo (arrastre libre + snapping de 5°) ---- */
.ppz-rueda-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  align-self: center;
  padding-top: 4px;
}
.ppz-rueda {
  position: relative;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  background: var(--bg-panel);
  border: 1px solid var(--border-glass);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.25);
  cursor: grab;
  touch-action: none;
}
.ppz-rueda:active { cursor: grabbing; }
.ppz-rueda:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; }
/* Marca fija arriba (0°) para que el usuario tenga una referencia visual
   de "hacia dónde apunta 0deg", igual que cualquier selector de ángulo. */
.ppz-rueda::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 50%;
  width: 2px;
  height: 8px;
  background: var(--border-glass);
  transform: translateX(-50%);
}
.ppz-rueda-aguja {
  position: absolute;
  inset: 0;
  transform-origin: 50% 50%;
  transition: transform var(--transition-fast);
}
.ppz-rueda-perilla {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ppz-rueda-color, var(--accent-1));
  border: 2px solid #ffffff;
  box-shadow: var(--shadow-float);
}
.ppz-rueda-lectura {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
}

/* ---- Modo de rendimiento: mismo criterio que el resto de la app — se
   quita cualquier cosa que dependa de blur/sombra suave/transición
   animada, la estética (colores, forma, tamaños) queda exactamente igual.
   El panel de "Crear mi paleta" es un overlay temporal, no persistente
   como el fondo de la app, así que el ahorro real de GPU aquí es menor —
   pero se aplica igual por consistencia y porque la rueda sí anima en
   cada frame mientras se arrastra. ---- */
[data-rendimiento="reducido"] .ppz-rueda-aguja { transition: none; }
[data-rendimiento="reducido"] .ppz-rueda { box-shadow: none; }
[data-rendimiento="reducido"] .ppz-rueda-perilla { box-shadow: none; }
[data-rendimiento="reducido"] .ppz-degradado-contenido {
  transition: none;
}

/* ---- Fase 8 — Drag and drop de criterios/asignaciones (2026-08-04) ----
   Bajo demanda desde "Reordenar" en el menú de long-press — fuera de ese
   modo no existe ningún ícono ni espacio reservado (cero impacto visual
   en el uso normal, pedido explícito). ---- */
.handle-mover {
  cursor: grab;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-secondary);
  padding: 2px 6px;
  flex-shrink: 0;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.handle-mover:active { cursor: grabbing; }

.arrastrando {
  opacity: 0.92;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.4);
  border-radius: var(--radius-sm);
}

.arrastre-placeholder {
  border: 2px dashed var(--border-glass);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.03);
}

/* ---- Ajustes: switches de navegación reordenables (2026-08-06) ----
   Reusa a propósito el mismo motor y las mismas clases de arrastre que la
   Fase 8 de criterios/asignaciones (.handle-mover/.arrastrando/.arrastre-
   placeholder, arriba) para que el gesto se sienta igual en toda la app.
   A diferencia de esas listas, acá el ícono de agarre queda SIEMPRE
   visible (lista corta y fija, sin el modo "bajo demanda") — por eso cada
   fila necesita su propio fondo/borde en vez de quedar "pelada" como una
   .row-between común. */
.fila-nav-orden {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  border: 1px solid var(--border-glass);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.fila-nav-orden:hover { border-color: var(--accent-1); }
.fila-nav-orden .handle-mover { cursor: grab; }
.fila-nav-orden .handle-mover:active { cursor: grabbing; }
[data-rendimiento="reducido"] .fila-nav-orden { transition: none; }

/* =========================================================================
   HORARIO — Núcleo (bloque aditivo, no toca reglas existentes)
   ========================================================================= */

/* Glow discreto en la letra del día actual dentro del header de columnas. */
.horario-bloque-nombre {
  font-family: var(--font-body);
  font-size: .76rem;
  font-weight: 650;
  line-height: 1.18;
  letter-spacing: normal;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  overflow: hidden;
  margin-bottom: 2px;
  overflow-wrap: anywhere;
  word-break: normal;
}
.horario-dia-actual-glow {
  color: var(--accent-1);
  text-shadow: 0 0 8px var(--accent-1-40, var(--accent-1));
  font-weight: 700;
}

/* Línea de "hora actual" (igual al indicador de Google Calendar) — abarca
   TODO el ancho del grid de días a la vez (todas las columnas), no solo la
   de hoy (ver construirLineaHoraActualGrid en horario.js, que setea top +
   left:28px inline; acá solo se define right:0 para que llegue justo al
   borde derecho del último día). Un solo elemento: la línea (border-top) +
   un puntito sobresaliendo a la izquierda del primer día, mismo lenguaje
   visual que Google Calendar.
   z-index 35: por encima de las tarjetas de clase (10 + lane, lane rara vez
   pasa de ~20) pero por debajo del header sticky del grid (z-index 50) y de
   la barra de expandir (z-index 40) — nunca las tapa, y tampoco se sale del
   área con scroll porque vive dentro de filaGrid, no del contenedor.
   Glow rojo sutil (pedido: "brillante pero discreta") — blur chico y
   spread mínimo, no agranda el grosor de la línea ni el tamaño del punto. */
.horario-linea-hora-actual {
  position: absolute;
  right: 0;
  height: 0;
  border-top: 2px solid #ef4444;
  box-shadow: 0 0 4px 0 rgba(239,68,68,0.65);
  z-index: 35;
  pointer-events: none;
}
.horario-linea-hora-actual-punto {
  /* Bug: quedaba 2px más abajo que el centro real de la línea. La línea es
     el border-top (2px) de .horario-linea-hora-actual, que se dibuja hacia
     ABAJO desde su "top" (ocupa de top a top+2px, centro visual en
     top+1px). Este punto, al ser absolute dentro de ese mismo elemento,
     se posiciona respecto al padding-box del padre — que por el border-top
     ya arranca en top+2px, no en top — así que top:-2px lo dejaba en
     top+2-2 = top (con alto 6px, centro en top+3), 2px por debajo del
     centro real (top+1) de la línea. Con top:-4px el punto arranca en
     top+2-4 = top-2 (alto 6px, centro en top+1), coincidiendo con el
     centro visual de la línea. */
  position: absolute;
  left: -3px;
  top: -4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ef4444;
  box-shadow: 0 0 5px 1px rgba(239,68,68,0.7);
}

/* Días del modal: por default fila única con scroll horizontal (pill-group
   normal); cuando horario-modal.js detecta que no caben todas las etiquetas
   sin cortarse, pasa a wrap (4-3 / 3-3-1 / 2-2-2-1, ancho fijo por JS
   inline) — flexbox en vez de grid para que la última fila, si queda
   impar, se vea centrada en vez de pegada a la izquierda con hueco. */
.horario-dias-pill-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  overflow: visible;
}
.horario-dias-pill-grid .pill-item { flex: none; white-space: normal; }

/* Selector de hora personalizado (2 selects tipo select-custom lado a lado). */
.horario-selector-hora { display: flex; align-items: center; gap: 4px; }
.horario-selector-hora .select-custom { flex: 1; min-width: 0; }
.horario-selector-hora .select-custom-boton { text-align: center; padding-left: 8px; padding-right: 8px; }
.horario-selector-hora-separador { font-weight: 700; color: var(--text-muted); }

/* Ajustes → Horario → "Días de la semana" (2026-08-23): antes eran 2 listas
   separadas ("Días visibles" con switch, "Nombres personalizados" con
   input), cada una repitiendo el nombre del día — se unifican acá en una
   sola tabla de 3 columnas (Día | Palabra | Visibilidad). El día queda
   anclado a la izquierda (1fr, crece); Palabra y Visibilidad quedan
   ancladas a la derecha con ancho fijo, alineadas bajo su propio
   encabezado — la fila y el encabezado comparten el mismo
   grid-template-columns a propósito, para que las columnas calcen. */
.tabla-dias-horario-fila {
  display: grid;
  grid-template-columns: 1fr 64px 52px;
  align-items: center;
  gap: 10px;
}
.tabla-dias-horario-header {
  padding: 0 0 2px;
}
.tabla-dias-horario-header span {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  text-align: right;
}
.tabla-dias-horario-header span:first-child { text-align: left; }
.tabla-dias-horario-fila:not(.tabla-dias-horario-header) span { font-size: 0.9rem; }
.tabla-dias-horario-fila .form-input {
  width: 100%;
  min-width: 0;
  text-align: center;
  padding-left: 4px;
  padding-right: 4px;
}
.tabla-dias-horario-fila .switch { justify-self: end; }

/* Ajustes → Horario → "Horario compartido", ahora anidado dentro de la
   misma tarjeta de Horario en vez de ser su propio glass-card (2026-08-23,
   pedido explícito de unificar) — el borde superior marca la separación
   visual que antes daba el espacio entre 2 cards distintas. */
.ajuste-subseccion {
  border-top: 1px solid var(--border-glass);
  padding-top: 14px;
}

/* Botón "Entrar a clase" dentro de una tarjeta de bloque del grid. */
.horario-btn-entrar-clase {
  display: inline-block;
  margin-top: 3px;
  font-size: 13.5px; /* 50% más grande, pedido explícito */
  font-weight: 700;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.25);
  color: #fff;
  text-decoration: none;
}
.horario-btn-entrar-clase:hover { background: rgba(255, 255, 255, 0.4); }

/* Barra inferior delgada de expandir/contraer 24h. */
.horario-barra-expandir {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 6px 0;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity var(--transition-fast);
}
.horario-barra-expandir:hover { opacity: 1; }
.horario-barra-expandir-icono { font-size: 1.5rem; color: var(--text-muted); transition: transform var(--transition-fast); }

/* =========================================================================
   ADJUNTOS — UI compartida (2026-08-19)
   Modal "Adjuntar", menú de gestión con drag-and-drop, y las pills que
   los muestran (Cronograma de materia, chip de adjuntos en una tarjeta de
   evento). Ver ui/adjuntos-ui.js.
   ========================================================================= */

/* ---- Fila del menú de gestión (modal ancho, una fila por adjunto) ---- */
.adjuntos-menu-lista { max-height: min(50vh, 420px); overflow-y: auto; gap: 6px; }
.adjunto-fila {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-md, 10px);
  background: rgba(255, 255, 255, 0.05);
  transition: opacity var(--transition-fast), background var(--transition-fast);
}
[data-mode="light"] .adjunto-fila { background: rgba(0, 0, 0, 0.04); }
.adjunto-fila-inactiva { opacity: 0.5; }
.adjunto-fila-arrastrando { opacity: 0.35; }
.adjunto-drag-handle { cursor: grab; opacity: 0.6; user-select: none; flex-shrink: 0; }
.adjunto-drag-handle:active { cursor: grabbing; }
.adjunto-fila-nombre {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.adjunto-fila-nombre:hover { text-decoration: underline; }
.adjunto-fila-editar,
.adjunto-fila-eliminar {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  opacity: 0.7;
  flex-shrink: 0;
  padding: 2px;
}
.adjunto-fila-editar:hover,
.adjunto-fila-eliminar:hover { opacity: 1; }

/* ---- Campo de emoji del modal Adjuntar/Editar adjunto: input de texto +
   paleta de accesos rápidos al lado (crearCampoEmojiModal en
   adjuntos-ui.js) — nunca un diálogo nativo del navegador. ---- */
.adjunto-emoji-paleta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.adjunto-emoji-opcion {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md, 10px);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 1rem;
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}
.adjunto-emoji-opcion:hover { background: rgba(255, 255, 255, 0.14); transform: translateY(-1px); }
[data-mode="light"] .adjunto-emoji-opcion { background: rgba(0, 0, 0, 0.04); border-color: rgba(0, 0, 0, 0.08); }
[data-mode="light"] .adjunto-emoji-opcion:hover { background: rgba(0, 0, 0, 0.08); }

/* ---- Pills de adjuntos (fila debajo del selector de materia en
   Cronograma; también sirve para cualquier otra fila de pills chicas de
   adjuntos que se agregue después) — todas del mismo tamaño, centradas,
   pasan a una 2da línea si no caben en la 1ra. ---- */
.adjuntos-pills-fila {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 8px;
}
.adjunto-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--text-primary);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background var(--transition-fast), transform var(--transition-fast);
}
[data-mode="light"] .adjunto-pill { background: rgba(0, 0, 0, 0.05); border-color: rgba(0, 0, 0, 0.08); }
.adjunto-pill:hover { background: rgba(255, 255, 255, 0.16); transform: translateY(-1px); }
[data-mode="light"] .adjunto-pill:hover { background: rgba(0, 0, 0, 0.09); }
.adjunto-pill-agregar {
  opacity: 0.75;
  border-style: dashed;
}

/* ===================== Tiempo de Estudio (Parte 1) ===================== */

/* ---- Indicador de sesión activa (rediseño 2026-09-19) ----
   Reemplaza al badge fijo de la esquina inferior izquierda (.badge-tiempo-
   estudio-fijo, ya eliminado), que tapaba el perfil de la barra lateral.
   Una lucecita que parpadea del COLOR DE LA MATERIA + su nombre (sin
   código) + el tiempo. Es UN solo componente con 3 presentaciones, y el CSS
   muestra la que corresponde al ancho de pantalla (mismos cortes que ya usa
   el resto del layout: 900px y 1500px):

     >= 1500px   .timer-indicador--lateral   tarjeta al fondo de .columna-derecha
     900–1499px  .timer-indicador--flotante  tarjeta fija abajo-derecha, sobre
                                             el botón flotante de Enlaces
     < 900px     .timer-indicador--linea     2ª línea bajo el nombre de la app
                                             en .mobile-topbar

   El color llega por la variable --te-color (la pone tiempo-estudio-
   indicador.js en línea, con el color de la materia); el valor de acá abajo
   es solo el respaldo. Nada de hex fijos: fondo y borde son mezclas de
   --te-color con las variables de la paleta, así que funciona en las 13
   paletas y en claro/oscuro. */
.timer-indicador {
  --te-color: var(--accent-1);
  --timer-fondo: color-mix(in srgb, var(--te-color) 13%, var(--bg-panel));
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin: 0;
  padding: 0;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--te-color) 42%, transparent);
  background: var(--timer-fondo);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform var(--transition-fast), border-color var(--transition-fast);
}
.timer-indicador:hover { border-color: color-mix(in srgb, var(--te-color) 80%, transparent); }
.timer-indicador:active { transform: scale(0.98); }
.timer-indicador:focus-visible { outline: 2px solid var(--te-color); outline-offset: 2px; }
[data-rendimiento="reducido"] .timer-indicador {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: var(--bg-canvas);
  background-image: linear-gradient(var(--timer-fondo), var(--timer-fondo));
}

/* La lucecita: parpadea suave mientras corre; en pausa se queda fija y
   atenuada. prefers-reduced-motion ya apaga toda animación más arriba. */
.timer-luz {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--te-color);
  box-shadow: 0 0 9px 1px var(--te-color);
  animation: timer-luz-parpadeo 1.7s ease-in-out infinite;
}
@keyframes timer-luz-parpadeo {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.22; box-shadow: 0 0 2px 0 var(--te-color); }
}
.timer-en-pausa .timer-luz { animation: none; opacity: 0.5; box-shadow: none; }

.timer-textos {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.timer-materia {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  font-size: 0.85rem;
  line-height: 1.25;
}
.timer-sep { display: none; }
/* wrap: en la tarjeta (260px) "05:00 +1:02:03" + estado puede no caber en un
   renglón; en vez de desbordar, el estado baja. La variante --linea lo
   vuelve a fijar en un solo renglón. */
.timer-fila-tiempo { display: flex; align-items: baseline; gap: 2px 6px; flex-wrap: wrap; }
.timer-tiempo {
  white-space: nowrap; /* "40:00 +05:12" nunca se parte en 2 renglones */
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  line-height: 1.2;
}
.timer-estado {
  white-space: nowrap;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.timer-estado[hidden] { display: none; }

/* -- >= 1500px: tarjeta anclada al fondo de la columna derecha --
   La columna mide el alto de la pantalla (menos los 20px de aire arriba y
   abajo) y se queda pegada al hacer scroll, igual que hacía el panel de
   enlaces solo. Los enlaces van arriba; la tarjeta baja al fondo con
   margin-top:auto (como el perfil en la barra lateral). Si los enlaces son
   tantos que no caben, el PANEL DE ENLACES se encoge y hace scroll por
   dentro (flex: 0 1 auto + min-height:0) — la tarjeta del timer no se
   encoge, así que nunca queda tapada ni fuera de pantalla. */
.columna-derecha {
  display: none;
  flex-direction: column;
  gap: 12px;
  width: 260px;
  flex-shrink: 0;
  align-self: flex-start;
  position: sticky;
  top: 20px;
  height: calc(100vh - 40px);
  height: calc(100dvh - 40px);
}
@media (min-width: 1500px) {
  .columna-derecha { display: flex; }
}
.columna-derecha > .enlaces-lateral {
  position: static;
  width: 100%;
  max-height: none;
  flex: 0 1 auto;
  min-height: 0;
  align-self: stretch;
}
.timer-indicador--lateral {
  flex: none;
  width: 100%;
  margin-top: auto;
  padding: 13px 15px;
  border-radius: var(--radius-md);
}

/* Choque con "Modo edición": ese badge fijo (.badge-modo-edicion-fijo) vive
   abajo-derecha, en el mismo rincón donde ahora está la tarjeta del timer.
   Antes el timer estaba abajo-IZQUIERDA justamente para no chocar con él.
   Mientras el badge está visible (Plan de Estudios en modo edición), la
   columna derecha se acorta 52px por abajo para que la tarjeta quede justo
   encima. (:has() — mismo nivel de soporte que el color-mix que ya usa todo
   este archivo.) En 900–1499px la tarjeta flotante ya está a 88px del fondo,
   por encima del badge, así que no necesita ajuste. */
#app-shell:has(#badge-modo-edicion:not(.oculto)) .columna-derecha {
  height: calc(100vh - 92px);
  height: calc(100dvh - 92px);
}

/* -- 900–1499px: sin columna derecha ni barra superior — tarjeta fija abajo
   a la derecha, por encima del botón flotante de Enlaces (52px + 24px de
   margen). Solo existe en ese rango; fuera de él se oculta. -- */
.timer-indicador--flotante {
  display: none;
  position: fixed;
  right: 24px;
  bottom: 88px;
  z-index: 80;
  width: max-content;
  max-width: min(260px, calc(100vw - 48px));
  padding: 10px 14px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  animation: badge-tiempo-estudio-in var(--transition-fast) ease;
}
@media (min-width: 900px) and (max-width: 1499.98px) {
  .timer-indicador--flotante { display: flex; }
}
@keyframes badge-tiempo-estudio-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* -- < 900px: segunda línea bajo "App Académica" en la barra superior.
   Sin tarjeta ni fondo (es una línea de texto), para no engordar la barra.
   El nombre de la materia se recorta con "…" si no cabe; el tiempo nunca. -- */
.mobile-topbar-centro {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}
.mobile-topbar .mobile-topbar-centro h3 { flex: none; }
.timer-indicador--linea {
  max-width: 100%;
  gap: 6px;
  padding: 1px 6px;
  border: none;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.timer-indicador--linea .timer-luz { width: 7px; height: 7px; box-shadow: 0 0 7px 1px var(--te-color); }
.timer-indicador--linea.timer-en-pausa .timer-luz { box-shadow: none; }
.timer-indicador--linea .timer-textos { flex-direction: row; align-items: baseline; gap: 5px; }
.timer-indicador--linea .timer-materia {
  flex: 0 1 auto;
  font-size: 0.74rem;
  font-weight: 500;
}
.timer-indicador--linea .timer-sep { display: inline; flex: none; font-size: 0.74rem; color: var(--text-muted); }
.timer-indicador--linea .timer-fila-tiempo { flex: none; flex-wrap: nowrap; gap: 5px; }
.timer-indicador--linea .timer-tiempo { font-size: 0.74rem; }
.timer-indicador--linea .timer-estado { font-size: 0.68rem; }

/* Encabezado (B.1): título + engranaje de Ajustes (el filtro Todo/Activos
   se mudó adentro del modal de Ajustes). */
.te-encabezado { flex-wrap: wrap; }

.te-codigo-clickeable { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
.te-codigo-clickeable:hover { color: var(--text-primary); }

/* D.2: badges clickeables (Es requisito / Buscarlo / Cerrar) — mismo
   tamaño que .badge (padding/font-size/pill), pero .badge sola no trae
   color propio (viene de badge-success/warning/danger/neutral, pensados
   para estados, no para esto) — sin un modificador de color, el <button>
   se queda con el blanco/negro nativo del navegador. Se le da el mismo
   tratamiento que .btn-secondary (el "botón neutro" de siempre en toda la
   app) para que seaga la estética del resto. */
.badge-accion {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border-color: var(--border-glass);
  cursor: pointer;
  transition: filter var(--transition-fast), background var(--transition-fast);
}
.badge-accion:hover { background: rgba(255, 255, 255, 0.1); }

/* Agenda — Entrega 5: filas de "Estudio para hoy" (fila clickeable, lleva
   al detalle de esa materia en Tiempo de Estudio). Mismo tratamiento chato
   que el resto de filas de la tarjeta de día — sin fondo propio, para no
   competir visualmente con los ítems de tareas/exámenes de abajo. */
.agenda-estudio-hoy-item {
  width: 100%;
  background: none;
  border: none;
  padding: 4px 2px;
  cursor: pointer;
  align-items: center;
  color: var(--text-primary);
  font-size: 0.85rem;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}
.agenda-estudio-hoy-item:hover { background: var(--bg-panel); }

/* Tarjeta de materia (rediseño: "copiá el diseño de las tarjetas de
   Semestres") — mismo .materia-card delgado (12px 14px) + línea1 con
   código+nombre (.materia-linea1/.materia-codigo/.materia-nombre, ya
   definidas para Plan de Estudios/Semestres, reusadas tal cual acá) en vez
   del bloque ancho de antes. Franja de color SOLO a la izquierda (mismo
   criterio que la franja de categoría en Agenda/Plan de Estudios: box-shadow
   inset, no un borde completo alrededor) — el color viene por JS: el que
   eligió el usuario o, si no eligió ninguno, el mismo que ya resuelve
   Horario para esa materia (color de categoría, ver obtenerColorMateria). */
.te-tarjeta-materia {
  display: flex;
  flex-direction: column;
  gap: 6px;
  cursor: pointer;
}
/* Línea 2: tiempo anclado a la izquierda, botones (engranaje + play/pausa)
   agrupados y anclados a la derecha — sin barra de progreso acá (esa se
   mudó a la pantalla de detalle, más abajo), para que la tarjeta quede
   delgada de verdad. */
.te-tarjeta-materia-linea2 {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.te-tarjeta-materia-tiempo {
  font-size: 0.8rem;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.te-tarjeta-materia-botones {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* Botones de ícono de Tiempo de Estudio — caja cuadrada fija (36×36, mismo
   criterio que .agenda-btn-ajustes) con flex centrado de verdad, así el
   glyph queda centrado sin importar su ancho natural (▶ no es igual de
   ancho que ⚙️ o ←, por eso antes se veían descentrados con solo
   padding). Engranaje y flecha son "fantasma" (sin fondo, ni siquiera al
   hover — pedido explícito: "sin fondo de color raro"); iniciar/detener sí
   llevan fondo de color para que se note cuál acción hace cada uno. */
.te-btn-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-pill);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--transition-fast), background var(--transition-fast), filter var(--transition-fast);
}
/* Engranaje de la tarjeta (66% del tamaño anterior, 52px→34px) — la misma
   clase la usa también el engranaje del encabezado (pedido: mismo tamaño
   entre los dos). */
.te-btn-icono-grande {
  width: 34px;
  height: 34px;
  font-size: 1.4rem;
}
.te-btn-icono-fantasma {
  background: none;
  color: var(--text-secondary);
}
.te-btn-icono-fantasma:hover { color: var(--text-primary); }
.te-btn-icono-iniciar { background: var(--gradient-accent); color: var(--on-accent); }
.te-btn-icono-iniciar:hover { filter: brightness(1.08); }
.te-btn-icono-detener { background: var(--color-danger, #ef4444); color: #fff; }
.te-btn-icono-detener:hover { filter: brightness(1.08); }
/* Mantener los símbolos centrados en su caja. El cuadrado de detener necesita
   un poco más de tamaño visual para igualar el peso de las barras de pausa. */
.te-btn-icono-iniciar, .te-btn-icono-detener { padding: 0; }
.te-btn-icono-detener { font-size: 1.3rem; }
/* Símbolos geométricos en CSS: la fuente del teléfono dibuja ■ desplazado
   hacia abajo. Estas formas tienen caja propia y quedan centradas por flex,
   independientemente de la fuente o del sistema operativo. */
.te-btn-control-pausa,
.te-btn-icono-detener { font-size: 0; position: relative; padding: 0; }
.te-btn-control-pausa::before,
.te-btn-icono-detener::before {
  content: "";
  display: block;
  flex: 0 0 auto;
  background: currentColor;
}
.te-btn-control-pausa[data-estado="corriendo"]::before {
  width: 13px;
  height: 14px;
  background: linear-gradient(to right, currentColor 0 35%, transparent 35% 65%, currentColor 65% 100%);
}
.te-btn-control-pausa[data-estado="pausado"]::before {
  width: 0;
  height: 0;
  background: transparent;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  border-left: 12px solid currentColor;
  transform: translateX(1px);
}
.te-btn-icono-detener::before { width: 13px; height: 13px; }

/* Encabezado de detalle (D.1): la franja de color acá NO va a la izquierda
   como en la tarjeta de la lista — recorre TODO el ancho, arriba, en
   horizontal (pedido explícito), como un acento de cabecera. Mismo truco de
   box-shadow inset que la franja izquierda, solo que en el otro eje. */
.te-encabezado-detalle {
  box-shadow: var(--shadow-glass), inset 0 4px 0 0 var(--te-color-materia, var(--accent-1));
}
.te-encabezado-detalle-nombre {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Pantalla de detalle: timer grande centrado, barra de progreso alargada
   debajo (rediseño: antes iba arriba del timer con el texto "Te faltan...
   para tu meta de..."; ahora la barra va debajo del timer y el texto se
   simplifica a un solo renglón centrado debajo de la barra). */
.te-timer-display {
  font-family: var(--font-display, inherit);
  font-size: 3rem;
  font-weight: 700;
  text-align: center;
  letter-spacing: 1px;
  font-variant-numeric: tabular-nums;
}
/* Tiempo extra (2026-09-19): renglón propio debajo del cronómetro, en una
   pastilla suave con los colores de acento del tema. Con Pomodoro activo
   reserva su alto desde el inicio (opacity, no display) para que cuando
   aparece el extra el cronómetro principal NO se mueva ni se descentre. */
.te-timer-extra {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  min-height: 2.1rem;
  padding: 4px 16px;
  border-radius: 999px;
  background: var(--accent-1-10);
  border: 1px solid var(--accent-1-20);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.te-timer-extra[hidden] { display: none; }
.te-timer-extra--visible { opacity: 1; transform: none; }
.te-timer-extra-etiqueta {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.te-timer-extra-valor {
  font-family: var(--font-display, inherit);
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.te-detalle-progreso {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
/* Base: la misma barra de progreso, usada tanto en la tarjeta de la lista
   (delgada, 6px) como en el detalle (más gruesa, 12px — ver
   .te-detalle-progreso .te-barra-progreso más abajo, que pisa la altura). */
.te-barra-progreso {
  width: 100%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent-1) 22%, var(--bg-panel));
  overflow: hidden;
}
.te-barra-progreso-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--gradient-accent);
  transition: width var(--transition-fast) ease;
}
.te-barra-progreso-fill.te-completada { filter: brightness(1.15); }

.te-detalle-progreso .te-barra-progreso {
  height: 12px;
}
.te-detalle-meta {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.9rem;
}

/* =========================================================================
   Racha de estudio (2026-09-19) — chip del encabezado de Tiempo, llama SVG,
   odómetro (número que rueda) y avisos a pantalla completa.
   Los colores de la LLAMA son fijos a propósito (el fuego es naranja en
   cualquier paleta); todo lo demás sale de las variables de la paleta.
   prefers-reduced-motion ya apaga toda animación/transición más arriba, así
   que sin movimiento el odómetro salta directo al valor final.
   ========================================================================= */

.te-encabezado-titulo-grupo { display: flex; align-items: center; gap: 10px; min-width: 0; }

/* ---- Llama (SVG de 3 capas) ---- */
.te-llama { display: block; width: auto; overflow: visible; transform-origin: 50% 100%; }
.te-llama--chip { height: 22px; }
.te-llama--grande { height: 104px; }
.te-llama-capa { transform-box: fill-box; transform-origin: 50% 100%; }
.te-llama-capa--exterior { fill: #ff6a1a; }
.te-llama-capa--medio { fill: #ffb420; }
.te-llama-capa--nucleo { fill: #fff2b0; }

@keyframes te-llama-flicker {
  0%, 100% { transform: scale(1, 1) rotate(0deg); }
  25%      { transform: scale(0.96, 1.05) rotate(-1.6deg); }
  50%      { transform: scale(1.03, 0.97) rotate(1deg); }
  75%      { transform: scale(0.98, 1.04) rotate(-0.6deg); }
}
@keyframes te-llama-capa-vaiven {
  0%, 100% { transform: scale(1, 1); }
  50%      { transform: scale(0.92, 1.1); }
}
@keyframes te-llama-entrada {
  0%   { transform: scale(0.15) rotate(-14deg); opacity: 0; }
  60%  { transform: scale(1.18) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0deg); opacity: 1; }
}

.te-racha-chip--encendida .te-llama,
.te-racha-card:not(.te-racha-card--oferta):not(.te-racha-card--apagada) .te-llama--grande {
  animation: te-llama-flicker 1.6s ease-in-out infinite;
}
.te-racha-chip--encendida .te-llama-capa--medio,
.te-racha-card:not(.te-racha-card--oferta):not(.te-racha-card--apagada) .te-llama-capa--medio {
  animation: te-llama-capa-vaiven 1.1s ease-in-out infinite;
}
.te-racha-chip--encendida .te-llama-capa--nucleo,
.te-racha-card:not(.te-racha-card--oferta):not(.te-racha-card--apagada) .te-llama-capa--nucleo {
  animation: te-llama-capa-vaiven 0.8s ease-in-out infinite reverse;
}
.te-racha-card:not(.te-racha-card--oferta):not(.te-racha-card--apagada) .te-llama--grande {
  animation: te-llama-entrada 0.9s cubic-bezier(0.2, 1.3, 0.4, 1) both,
             te-llama-flicker 1.6s ease-in-out 0.9s infinite;
}

/* ---- Chip del encabezado ---- */
.te-racha-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  margin: 0;
  padding: 3px 11px 3px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-glass);
  background: var(--bg-panel);
  color: var(--text-primary);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast);
}
.te-racha-chip:active { transform: scale(0.96); }
.te-racha-chip:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.te-racha-num { display: inline-flex; align-items: center; font-size: 1.05rem; min-width: 0.6em; }
.te-racha-num .te-odo { font-size: 1.05rem; }

/* Encendida: hoy ya cumplió. */
.te-racha-chip--encendida {
  border-color: color-mix(in srgb, #ff8a1f 55%, transparent);
  background: color-mix(in srgb, #ff8a1f 14%, var(--bg-panel));
}
/* Pendiente: la racha vive, hoy falta estudiar (llama "dormida"). */
.te-racha-chip--pendiente .te-llama { filter: grayscale(0.85); opacity: 0.85; }
.te-racha-chip--pendiente .te-racha-num { color: var(--text-secondary); }
/* Riesgo: ya usó los 2 descansos de la semana — hoy es clave. */
.te-racha-chip--riesgo {
  border-color: color-mix(in srgb, var(--color-danger) 65%, transparent);
  animation: te-racha-riesgo 1.8s ease-in-out infinite;
}
.te-racha-chip--riesgo .te-llama { filter: grayscale(0.3); opacity: 1; }
@keyframes te-racha-riesgo {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-danger) 45%, transparent); }
  50%      { box-shadow: 0 0 0 5px transparent; }
}
/* Apagada: sin racha (o con una recuperable, borde punteado). */
.te-racha-chip--apagada { color: var(--text-muted); }
.te-racha-chip--apagada .te-llama { filter: grayscale(1); opacity: 0.5; }
.te-racha-chip--recuperable { border-style: dashed; }

/* Pop cuando la racha sube. */
.te-racha-chip--pop { animation: te-racha-pop 0.72s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes te-racha-pop {
  0%   { transform: scale(1); }
  42%  { transform: scale(1.09); }
  74%  { transform: scale(0.99); }
  100% { transform: scale(1); }
}

/* ---- Odómetro: cada dígito es una columna con una tira 0–9 que rueda ----
   Las celdas miden 1.3em (no 1em) y el margen negativo devuelve la altura de
   línea a 1em: con celdas justas, la tinta del dígito vecino de la tira se
   asomaba como un filo bajo el número. */
.te-odo {
  --odo-h: 1.3em;
  display: inline-flex;
  height: var(--odo-h);
  margin: calc((1em - var(--odo-h)) / 2) 0;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.te-odo-col { position: relative; display: block; width: 0.62em; height: var(--odo-h); overflow: hidden; }
.te-odo-tira {
  display: flex;
  flex-direction: column;
  transform: translateY(calc(var(--i, 0) * var(--odo-h) * -1));
  transition: transform var(--odo-dur, 850ms) cubic-bezier(0.22, 1, 0.36, 1) var(--odo-retraso, 0ms);
  will-change: transform;
}
.te-odo-tira > span { display: block; height: var(--odo-h); line-height: var(--odo-h); text-align: center; }
.te-odo--giro .te-odo-tira { transition-timing-function: cubic-bezier(0.22, 0.8, 0.2, 1.05); }
.te-odo-col--nueva { opacity: 0; transition: opacity 0.6s ease var(--odo-retraso, 0ms); }
.te-odo-col--nueva.te-odo-col--visible { opacity: 1; }

/* ---- Avisos a pantalla completa ---- */
.te-racha-overlay { z-index: 330; animation: te-racha-fondo 0.3s ease both; }
@keyframes te-racha-fondo { from { opacity: 0; } to { opacity: 1; } }
.te-racha-card {
  width: min(400px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 24px 24px 22px;
  text-align: center;
  overflow-x: hidden;
  animation: te-racha-entra 0.45s cubic-bezier(0.2, 1.2, 0.4, 1) both;
}
@keyframes te-racha-entra {
  from { opacity: 0; transform: translateY(18px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}
.te-racha-escena { position: relative; width: 150px; height: 130px; display: grid; place-items: center; margin-top: 2px; }
.te-racha-escena > .te-racha-halo,
.te-racha-escena > .te-llama { grid-area: 1 / 1; }
.te-racha-halo {
  width: 130px;
  height: 130px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 140, 30, 0.55) 0%, rgba(255, 140, 30, 0.18) 45%, transparent 70%);
  animation: te-racha-halo 2.4s ease-in-out infinite;
}
@keyframes te-racha-halo {
  0%, 100% { transform: scale(0.9); opacity: 0.75; }
  50%      { transform: scale(1.08); opacity: 1; }
}
.te-racha-chispas { position: absolute; left: 50%; bottom: 24px; width: 0; height: 0; pointer-events: none; }
.te-racha-chispa {
  position: absolute;
  left: 0;
  bottom: 0;
  width: var(--tam, 4px);
  height: var(--tam, 4px);
  margin-left: calc(var(--tam, 4px) / -2);
  border-radius: 50%;
  background: #ffc247;
  box-shadow: 0 0 6px 1px rgba(255, 150, 30, 0.8);
  opacity: 0;
  animation: te-racha-chispa 2.2s ease-out var(--retraso, 0s) infinite;
}
@keyframes te-racha-chispa {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx, 0), var(--dy, -90px)) scale(0.2); }
}
/* Oferta de recuperación: llama apagada, sin fuegos artificiales. */
.te-racha-card--oferta .te-racha-halo,
.te-racha-card--apagada .te-racha-halo {
  background: radial-gradient(circle, color-mix(in srgb, var(--text-muted) 35%, transparent) 0%, transparent 70%);
  animation: none;
}
.te-racha-card--oferta .te-llama--grande,
.te-racha-card--apagada .te-llama--grande { filter: grayscale(1); opacity: 0.6; }

.te-racha-numero { display: flex; align-items: center; justify-content: center; gap: 10px; font-family: var(--font-display); }
.te-racha-numero .te-odo { font-size: 3.6rem; font-weight: 800; color: var(--text-primary); }
.te-racha-unidad { font-size: 1rem; font-weight: 600; color: var(--text-secondary); }
.te-racha-titulo { margin: 4px 0 0; font-size: 1.35rem; }
.te-racha-texto { margin: 0; color: var(--text-secondary); font-size: 0.95rem; line-height: 1.5; }
.te-racha-avance { width: 100%; display: flex; flex-direction: column; align-items: center; gap: 6px; margin-top: 2px; }

/* Casilla "No volver a mostrar": el input global es display:none (no recibe
   foco con Tab); acá queda invisible pero enfocable, encima de la caja. */
.te-racha-check { position: relative; margin-top: 6px; }
.te-racha-check input {
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  width: 22px;
  height: 22px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.te-racha-check input:focus-visible + .box { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.te-racha-aceptar { margin-top: 6px; }

/* ---- Ventana de detalle (al tocar el chip) ---- */
.te-llama--mini { height: 17px; }
.te-racha-card--detalle .te-racha-escena { height: 112px; }
.te-racha-card--detalle .te-llama--grande { height: 88px; }
.te-racha-card--detalle .te-racha-halo { width: 112px; height: 112px; }
.te-racha-card--detalle .te-racha-numero .te-odo { font-size: 3.1rem; }
.te-racha-detalle { width: 100%; display: flex; flex-direction: column; gap: 10px; margin-top: 4px; text-align: left; }

/* Semana lun→dom */
.te-racha-semana { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.te-racha-dia {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  animation: te-racha-dia-in 0.45s cubic-bezier(0.2, 1.4, 0.4, 1) both;
  animation-delay: calc(0.45s + var(--n, 0) * 70ms);
}
@keyframes te-racha-dia-in {
  from { opacity: 0; transform: translateY(8px) scale(0.6); }
  to   { opacity: 1; transform: none; }
}
.te-racha-dia-letra { font-size: 0.72rem; font-weight: 600; color: var(--text-muted); }
.te-racha-dia-marca {
  width: 100%;
  max-width: 38px;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1.5px solid var(--border-glass);
  background: var(--bg-panel);
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1;
}
.te-racha-dia--cumplido .te-racha-dia-marca {
  border-color: color-mix(in srgb, #ff8a1f 60%, transparent);
  background: color-mix(in srgb, #ff8a1f 18%, var(--bg-panel));
}
.te-racha-dia--descanso .te-racha-dia-marca {
  border-style: dashed;
  background: color-mix(in srgb, var(--accent-1) 10%, var(--bg-panel));
}
.te-racha-dia--hoy .te-racha-dia-marca { border: 2px dashed var(--accent-2); }
.te-racha-dia--riesgo .te-racha-dia-marca {
  border: 2px dashed var(--color-danger);
  animation: te-racha-riesgo 1.8s ease-in-out infinite;
}
.te-racha-dia--hoy .te-racha-dia-letra,
.te-racha-dia--riesgo .te-racha-dia-letra,
.te-racha-dia--cumplido .te-racha-dia-letra { color: var(--text-primary); }
.te-racha-dia--futuro .te-racha-dia-marca,
.te-racha-dia--previo .te-racha-dia-marca { opacity: 0.4; border-color: transparent; }
.te-racha-leyenda { display: flex; justify-content: center; gap: 16px; font-size: 0.72rem; color: var(--text-muted); }

/* Filas de datos */
.te-racha-filas {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  border-radius: var(--radius-md, 14px);
  border: 1px solid var(--border-glass);
  background: var(--bg-panel);
}
.te-racha-fila { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 0.9rem; }
.te-racha-fila-etiqueta { color: var(--text-secondary); }
.te-racha-fila-valor { font-weight: 700; color: var(--text-primary); text-align: right; }
.te-racha-barra-hoy { height: 8px; }
.te-racha-descansos { display: inline-flex; align-items: center; gap: 8px; }
.te-racha-pips { display: inline-flex; gap: 5px; }
.te-racha-pip {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--border-glass);
  background: transparent;
}
.te-racha-pip--llena {
  border-color: transparent;
  background: linear-gradient(135deg, #ffb420, #ff6a1a);
  box-shadow: 0 0 6px rgba(255, 140, 30, 0.6);
}

/* Rendimiento reducido: sin destellos ni halo animado; el número igual rueda. */
[data-rendimiento="reducido"] .te-racha-halo,
[data-rendimiento="reducido"] .te-racha-chispa,
[data-rendimiento="reducido"] .te-racha-chip--riesgo { animation: none; }
[data-rendimiento="reducido"] .te-racha-chispas { display: none; }
[data-rendimiento="reducido"] .te-racha-overlay { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(0, 0, 0, 0.8); }

/* Acciones de cierre de la lista de Tiempo: dos pestañas anchas, discretas,
   con toda su celda como área táctil. */
.te-pestanas-finales-materias {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 4px;
  border-top: 1px solid var(--border-glass);
}
.te-pestana-final-materias {
  width: 100%;
  min-height: 60px;
  padding: 12px 8px;
  border: 0;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.9rem;
  text-align: center;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.te-pestana-final-materias:hover,
.te-pestana-final-materias:focus-visible {
  background: var(--bg-panel);
  color: var(--text-primary);
}
.selector-idioma-login { width: 100%; margin: 4px 0 0; text-align: left; }
.selector-idioma-login > .form-label { display: block; margin: 0 0 3px; font-size: .7rem; }
.selector-idioma-login .select-custom,
.selector-idioma-login .select-custom-boton { width: 100%; }

/* Las opciones nativas respetan el tema y conservan contraste al abrir el
   selector de semestre de Tiempo de Estudio en escritorio. */
select option, select optgroup { background-color: var(--bg-card); color: var(--text-primary); }

/* Actualización obligatoria: cubre toda la interfaz hasta instalar la nueva
   versión, con una tarjeta centrada y sin cierre accidental. */
.actualizacion-obligatoria-overlay {
  position: fixed; inset: 0; z-index: 12000; display: grid; place-items: center;
  padding: 22px; background: color-mix(in srgb, var(--bg-canvas) 76%, #05030c 24%);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
}
.actualizacion-obligatoria-card {
  width: min(560px, 100%); display: flex; flex-direction: column; align-items: center;
  gap: 14px; padding: clamp(28px, 7vw, 54px); text-align: center;
  border: 1px solid color-mix(in srgb, var(--accent-1) 45%, var(--border-glass));
  border-radius: 28px; background: var(--bg-card);
  box-shadow: 0 28px 100px rgba(0,0,0,.48), 0 0 80px color-mix(in srgb, var(--accent-1) 22%, transparent);
  animation: actualizacion-entrada .45s cubic-bezier(.2,.8,.2,1) both;
}
.actualizacion-obligatoria-icono { display:grid; place-items:center; width:68px; height:68px; border-radius:22px; color:white; font-size:2rem; background:linear-gradient(135deg,var(--accent-1),var(--accent-2)); box-shadow:0 12px 35px color-mix(in srgb,var(--accent-1) 38%,transparent); }
.actualizacion-obligatoria-etiqueta { margin:0; color:var(--accent-2); font-size:.78rem; font-weight:800; letter-spacing:.12em; text-transform:uppercase; }
.actualizacion-obligatoria-card h2 { margin:0; font-size:clamp(1.6rem,5vw,2.35rem); line-height:1.12; }
.actualizacion-obligatoria-card > p:not(.actualizacion-obligatoria-etiqueta) { max-width:40ch; margin:0 0 4px; line-height:1.6; }
.actualizacion-obligatoria-card .btn { min-width:min(260px,100%); min-height:50px; }
.actualizacion-obligatoria-nota { color:var(--text-muted); font-size:.8rem; }
@keyframes actualizacion-entrada { from { opacity:0; transform:translateY(16px) scale(.98); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) { .actualizacion-obligatoria-card { animation:none; } }

/* Resumen: la tarjeta de estudio ocupa menos espacio y separa con claridad
   el avance general de las materias individuales. */
.resumen-estudio-hoy-tarjeta { border-left:3px solid var(--accent-1); gap:10px !important; }
.resumen-estudio-hoy-tarjeta > div:first-child { align-items:center !important; flex-wrap:wrap; }
.resumen-estudio-hoy-tarjeta > div:first-child h2 { font-size:1rem; }
.resumen-estudio-hoy-tarjeta > div:first-child > span { font-variant-numeric:tabular-nums; font-weight:700; }
.resumen-estudio-hoy-detalle { padding-top:2px; }
.resumen-estudio-hoy-detalle > .row-between { padding:5px 8px; border-radius:8px; background:color-mix(in srgb,var(--bg-panel) 68%,transparent); }

/* Ajustes del recorrido: contenido desplazable, apariencia con transición
   suave y popover por encima del elemento resaltado. */
.onboarding-contenido { display:flex; flex-direction:column; gap:14px; max-height:72vh; overflow:auto; padding:2px; }
.guia-plan-flotante { z-index:200020; }
.selector-modos-apariencia { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); }
.selector-modos-apariencia .pill-item { transition:background .24s cubic-bezier(.2,.8,.2,1),color .24s ease,box-shadow .24s ease; }

/* Correcciones de bienvenida: pantalla opaca real, panel lateral + preview a sangre. */
.onboarding-overlay { background:color-mix(in srgb,var(--bg-app,#10111a) 84%,transparent)!important; backdrop-filter:blur(10px) saturate(.72); opacity:1!important; }
.onboarding-panel {
  display:grid!important; grid-template-columns:minmax(300px,.72fr) minmax(0,1.28fr);
  grid-template-rows:auto minmax(0,1fr) auto; align-items:stretch; gap:0!important;
  padding:0!important; overflow:hidden!important; background:transparent!important;
}
.onboarding-panel > .onboarding-top { grid-column:1; grid-row:1; width:auto!important; margin:0!important; padding:20px 28px 8px; }
.onboarding-panel > .onboarding-layout { grid-column:1/-1; grid-row:2; display:grid; grid-template-columns:minmax(300px,.72fr) minmax(0,1.28fr); gap:0!important; width:100%!important; max-width:none!important; min-height:0; margin:0!important; }
.onboarding-copy { max-height:100%; overflow:auto; padding:24px clamp(22px,4vw,56px); align-self:stretch; background:color-mix(in srgb,var(--bg-app,#10111a) 90%,transparent); backdrop-filter:blur(12px); }
.onboarding-panel > .onboarding-acciones { grid-column:1; grid-row:3; width:auto!important; margin:0!important; padding:12px 28px calc(22px + env(safe-area-inset-bottom)); justify-content:flex-start; }
.onboarding-preview-wrap { min-height:0!important; height:100%; max-height:none!important; border:0!important; border-radius:0!important; }
.onboarding-preview { min-height:0!important; height:100%; pointer-events:auto!important; }
.onboarding-preview-caption { display:none!important; }
.onboarding-color { border:0!important; outline:1px solid color-mix(in srgb,var(--text-primary) 18%,transparent); outline-offset:2px; }
.onboarding-color[aria-pressed="true"] { outline:3px solid var(--text-primary); outline-offset:3px; }
.onboarding-lista { margin:0; padding-left:1.25em; display:grid; gap:10px; line-height:1.55; }
.guia-plan-resaltado { box-shadow:0 0 0 3px color-mix(in srgb,var(--accent-1) 28%,transparent)!important; }
@media(max-width:760px) {
  .onboarding-panel { grid-template-columns:minmax(0,1fr)!important; grid-template-rows:auto minmax(0,1fr) auto!important; overflow:auto!important; }
  .onboarding-panel > .onboarding-top { grid-column:1; grid-row:1; }
  .onboarding-panel > .onboarding-layout { grid-column:1; grid-row:2; grid-template-columns:minmax(0,1fr); grid-template-rows:minmax(0,auto) minmax(28vh,36vh); overflow:auto; }
  .onboarding-copy { max-height:none; padding:12px 22px 20px; }
  .onboarding-preview-wrap { height:36vh; min-height:28vh!important; }
  .onboarding-panel > .onboarding-acciones { grid-column:1; grid-row:3; padding:12px 22px calc(18px + env(safe-area-inset-bottom)); }
}


/* Tarjetas de estudio más cercanas a Agenda: nombre, meta legible y avance propio. */
.resumen-estudio-materia { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:5px 12px; padding:9px 12px; border-radius:12px; background:color-mix(in srgb,var(--bg-panel) 78%,transparent); border-left:3px solid var(--accent-1); }
.resumen-estudio-materia-nombre { min-width:0; overflow-wrap:anywhere; font-size:.86rem; font-weight:650; }
.resumen-estudio-materia-tiempo { white-space:normal; text-align:right; font-size:.78rem; font-variant-numeric:tabular-nums; }
.resumen-estudio-materia-barra { grid-column:1/-1; height:5px; overflow:hidden; border-radius:99px; background:color-mix(in srgb,var(--accent-1) 17%,var(--bg-panel)); }
.resumen-estudio-materia-barra span { display:block; height:100%; border-radius:inherit; background:var(--accent-1); transition:width .3s ease; }
.plan-gestion-fila > .row { align-items:center; flex-wrap:nowrap; gap:6px; }
.plan-gestion-fila > .row .btn { min-width:40px; min-height:40px; padding:8px 10px; white-space:nowrap; }
.plan-gestion-fila > .row .btn-danger { width:42px; min-width:42px; padding:8px; font-size:1rem; }
.plan-gestion-fila > .row .btn-secondary[title^="Exportar"] { min-width:48px; min-height:36px; padding:6px 10px; font-size:.82rem; }
.te-encabezado { display:grid!important; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); align-items:center!important; min-height:64px; }
.te-encabezado-titulo-grupo { display:contents; }
.te-encabezado-titulo-grupo > h2 { grid-column:2; grid-row:1; justify-self:center; text-align:center; max-width:100%; overflow-wrap:anywhere; }
.te-encabezado-titulo-grupo > .te-racha-chip { grid-column:1; grid-row:1; justify-self:start; min-height:48px; }
.te-encabezado-titulo-grupo > .row { grid-column:1; grid-row:2; }
.te-encabezado > div:last-child { grid-column:3; grid-row:1; justify-self:end; }
.plan-exportar-overlay { z-index:250000!important; }
@media(max-width:600px) {
  .te-encabezado { grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); gap:4px; }
  .te-encabezado-titulo-grupo > h2 { font-size:1rem; }
  .te-encabezado-titulo-grupo > .te-racha-chip { min-height:44px; }
  .resumen-estudio-materia { grid-template-columns:minmax(0,1fr); }
  .resumen-estudio-materia-tiempo { text-align:left; }
}


.onboarding-file-picker { position:relative; display:inline-flex; align-items:center; justify-content:center; min-height:44px; width:max-content; max-width:100%; padding:10px 16px; border:1px solid color-mix(in srgb,var(--accent-1) 40%,var(--border-glass)); border-radius:14px; cursor:pointer; overflow:hidden; }
.onboarding-file-input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
.onboarding-file-picker:focus-within { outline:2px solid var(--accent-1); outline-offset:3px; }
.modal-overlay:has(.paleta-personalizada), .modal-overlay:has(#paleta-personalizada) { z-index:300000!important; }

.ppz-overlay { z-index:310000!important; }

/* Onboarding: cambio de personalización/vista previa sin recargar el iframe. */
html:has(.onboarding-overlay), body:has(.onboarding-overlay) { overflow:hidden!important; }
.onboarding-logo-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.onboarding-logo-heading h3 { margin:0; }
.logo-selector-encabezado { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.logo-selector-personalizado { display:inline-flex; align-items:center; justify-content:center; min-height:36px; padding:6px 12px; border:1px solid var(--border-glass); border-radius:999px; color:var(--text-primary); background:var(--bg-panel); cursor:pointer; }
#selector-logo-app .onboarding-logos button[data-logo-app] { width:92px; height:92px; border-color:transparent; background:transparent; }
#selector-logo-app .onboarding-logos button[data-logo-app] img { width:76px; height:76px; background:transparent; object-fit:contain; }
.onboarding-file-picker { width:auto; min-height:36px; padding:6px 12px; }
.onboarding-logo { width:96px; height:96px; background:transparent!important; border-color:transparent!important; }
.onboarding-logo img { width:76px!important; height:76px!important; background:transparent!important; object-fit:contain; }
.onboarding-paletas { grid-template-columns:repeat(7,minmax(0,1fr))!important; }
.onboarding-paleta-nueva { width:40px; height:40px; min-height:40px; padding:0; border-radius:50%; font-size:1.35rem; line-height:1; }
.onboarding-con-tour .onboarding-lista { animation:onboarding-lista-entrada .35s ease both; }
.onboarding-conoce > button { justify-content:flex-start; width:100%; text-align:left; }
.onboarding-conoce.plegado .onboarding-lista { display:none; }
.onboarding-dependencias { list-style:none; counter-reset:flujo; padding:0; display:grid; gap:10px; }
.onboarding-dependencias li { counter-increment:flujo; position:relative; padding:12px 14px 12px 52px; border-radius:14px; background:color-mix(in srgb,var(--bg-panel) 78%,transparent); line-height:1.45; }
.onboarding-dependencias li::before { content:counter(flujo); position:absolute; left:13px; top:50%; transform:translateY(-50%); display:grid; place-items:center; width:27px; height:27px; border-radius:50%; color:var(--on-accent); background:var(--accent-1); font-weight:800; }
.onboarding-dependencias li:not(:last-child)::after { content:""; position:absolute; left:25px; top:calc(100% - 1px); height:12px; border-left:2px solid color-mix(in srgb,var(--accent-1) 55%,transparent); }
@keyframes onboarding-lista-entrada { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
.guia-plan-resaltado { box-shadow:0 0 0 3px color-mix(in srgb,var(--accent-1) 28%,transparent),0 0 0 9999px rgba(0,0,0,.48)!important; }
.onboarding-pregunta-plan { position:absolute; inset:0; z-index:5; display:grid; place-items:center; padding:20px; background:rgba(0,0,0,.64); backdrop-filter:blur(8px); }
.onboarding-pregunta-plan > section { width:min(480px,100%); padding:clamp(22px,5vw,36px); border:1px solid var(--border-glass); border-radius:24px; background:var(--bg-card); box-shadow:var(--shadow-float); }
.onboarding-pregunta-plan h2 { margin-top:0; }
.onboarding-pregunta-plan p { color:var(--text-secondary); line-height:1.55; }
@media(max-width:760px) {
  .onboarding-overlay { padding:0!important; }
  .onboarding-panel { width:100vw!important; height:100vh!important; height:100dvh!important; max-height:none!important; border:0!important; border-radius:0!important; }
  .onboarding-panel > .onboarding-layout { min-height:0!important; grid-template-rows:minmax(0,1fr)!important; overflow:hidden!important; }
  .onboarding-copy { min-height:0; overflow:auto; }
  .onboarding-preview-wrap { display:none!important; }
  .onboarding-mostrar-preview .onboarding-layout { grid-template-rows:minmax(0,1fr)!important; }
  .onboarding-mostrar-preview .onboarding-copy { display:none!important; }
  .onboarding-mostrar-preview .onboarding-preview-wrap { display:flex!important; height:100%!important; min-height:0!important; }
  .onboarding-mostrar-preview .onboarding-preview { min-height:0!important; height:100%!important; }
  .onboarding-logo-heading { gap:8px; }
  .onboarding-logo-heading h3 { font-size:1rem; }
  .onboarding-logos { gap:8px; }
  .onboarding-paletas { gap:9px!important; }
  .onboarding-panel:not(.onboarding-con-tour) .onboarding-acciones { justify-content:space-between!important; }
  .onboarding-con-tour .onboarding-layout { grid-template-rows:minmax(0,.7fr) minmax(0,1fr)!important; }
  .onboarding-con-tour .onboarding-copy { padding:12px 20px!important; }
  .onboarding-con-tour .onboarding-preview-wrap { display:flex!important; height:auto!important; min-height:0!important; }
  .onboarding-con-tour .onboarding-preview { min-height:0!important; }
}

/* Evita desalinear el encabezado de Agenda cuando crece el texto o cambia el ancho. */
#agenda-header > .row-between { display:grid!important; grid-template-columns:44px minmax(0,1fr) 44px; align-items:center!important; }
#agenda-subheader-dinamico { min-width:0; max-width:100%; overflow-wrap:anywhere; }
#agenda-header #btn-agenda-agregar,#agenda-header #btn-agenda-ajustes { justify-self:center; align-self:center; }
#agenda-lista-dias,#agenda-vista-calendario,#agenda-vista-materia { min-width:0; overflow-x:auto; overflow-y:visible; overscroll-behavior-x:contain; }
@media(max-width:520px) { #agenda-header > .row-between { grid-template-columns:40px minmax(0,1fr) 40px; gap:4px!important; } }

/* Pulido de bienvenida: panel y vista real ocupan el alto completo en escritorio. */
.onboarding-copy > h1 { font-size:clamp(1.55rem,3.4vw,2.76rem)!important; }
.onboarding-modos,.onboarding-calidades { width:min(100%,520px); margin-inline:auto; justify-content:center; }
.onboarding-modos > .btn,.onboarding-calidades > .btn { flex:1 1 0; min-width:0; padding-inline:clamp(8px,1.4vw,16px); font-size:.92rem; }
.onboarding-calidades { display:flex; gap:8px; flex-wrap:wrap; }
.onboarding-calidades > .btn { min-height:40px; }
.onboarding-logo-heading { align-items:center; }
.onboarding-logo-heading > button:first-child { min-height:36px; padding:6px 0; border:0; background:transparent; font-weight:700; }
.tutorial-seccion-card { width:min(560px,100%); max-height:min(82dvh,760px); overflow:auto; }
.tutorial-seccion-card h2 { margin:0; font-size:clamp(1.25rem,3vw,1.8rem); }
.tutorial-seccion-card .onboarding-lista { padding-left:1.35em; }
.te-selector-semestre-tiempo { width:100%; max-width:460px; margin:0 auto; text-align:center; }
.te-racha-calendario { width:100%; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin-top:12px; }
.te-racha-mes { min-width:0; padding:10px; border-radius:12px; background:color-mix(in srgb,var(--bg-panel) 74%,transparent); }
.te-racha-mes-titulo { margin:0 0 8px; text-align:center; font-size:.82rem; text-transform:capitalize; }
.te-racha-mes-dias { display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:3px; text-align:center; }
.te-racha-cal-dia-semana { color:var(--text-muted); font-size:.58rem; font-weight:700; }
.te-racha-cal-dia { display:grid; place-items:center; min-width:0; aspect-ratio:1; border-radius:50%; font-size:.68rem; color:var(--text-muted); }
.te-racha-cal-dia--estudio { color:var(--on-accent); background:var(--accent-1); }
.te-racha-cal-dia--descanso { color:var(--text-primary); background:color-mix(in srgb,var(--text-muted) 32%,transparent); }
.te-racha-cal-dia--hoy { outline:2px solid var(--accent-2); outline-offset:1px; }
.te-racha-cal-leyenda { display:flex; justify-content:center; flex-wrap:wrap; gap:12px; color:var(--text-secondary); font-size:.82rem; }
.btn-instalar-app-flotante { width:44px!important; height:44px!important; right:18px!important; bottom:18px!important; border-radius:50%!important; transition:width .46s cubic-bezier(.22,1,.36,1),height .46s cubic-bezier(.22,1,.36,1),border-radius .3s ease,opacity .35s ease,transform .46s cubic-bezier(.22,1,.36,1),box-shadow .3s ease; }
.btn-instalar-app-flotante.instalar-expandido { width:168px!important; height:44px!important; border-radius:999px!important; }
@media(min-width:761px) {
  .onboarding-panel { position:relative!important; display:grid!important; width:100%!important; height:100dvh!important; min-height:100dvh!important; max-height:100dvh!important; grid-template-columns:minmax(300px,36%) minmax(0,64%)!important; grid-template-rows:minmax(0,1fr)!important; overflow:hidden!important; }
  .onboarding-panel > .onboarding-top { position:absolute!important; top:0; left:0; z-index:2; width:36%!important; max-width:none!important; padding:16px 28px 8px!important; }
  .onboarding-panel > .onboarding-layout { grid-column:1/-1!important; grid-row:1!important; grid-template-columns:minmax(300px,36%) minmax(0,64%)!important; width:100%!important; height:100%!important; min-height:0!important; }
  .onboarding-copy { grid-column:1; grid-row:1; width:100%; height:100%; max-height:none!important; padding:clamp(78px,10vh,110px) clamp(22px,3.6vw,56px) clamp(90px,12vh,122px)!important; overflow-y:auto!important; }
  .onboarding-preview-wrap { grid-column:2; grid-row:1; width:100%; height:100%!important; min-height:0!important; }
  .onboarding-preview { width:100%; height:100%!important; min-height:0!important; }
  .onboarding-panel > .onboarding-acciones { position:absolute!important; left:0; bottom:0; z-index:2; width:36%!important; max-width:none!important; padding:12px 28px 22px!important; }
}
@media(max-width:760px) {
  .onboarding-copy > h1 { font-size:clamp(1.55rem,8vw,2.35rem)!important; }
  .te-racha-calendario { grid-template-columns:repeat(2,minmax(0,1fr)); }
  body.instalacion-flotante-activa .btn-flotante-enlaces { bottom:calc(76px + env(safe-area-inset-bottom)); }
}
.onboarding-logos[hidden] { display:none!important; }
.te-racha-card--detalle { max-height:calc(100dvh - 24px); overflow-y:auto; overscroll-behavior:contain; }
.te-racha-cal-dia--vacio { visibility:hidden; }

/* Calendario de racha: un mes por vista, navegable con flechas. */
.te-racha-calendario { width:min(100%,390px); display:block; margin:12px auto 0; }
.te-racha-cal-nav { display:grid; grid-template-columns:42px minmax(0,1fr) 42px; align-items:center; gap:10px; margin-bottom:10px; }
.te-racha-cal-nav .te-racha-mes-titulo { margin:0; text-align:center; font-size:1rem; }
.te-racha-cal-nav-btn { width:40px; min-width:40px; min-height:40px; padding:0; font-size:1.45rem; line-height:1; }
.te-racha-mes-dias { display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:5px; text-align:center; }
.te-racha-cal-dia-semana { color:var(--text-muted); font-size:.72rem; font-weight:700; }
.te-racha-cal-dia { display:grid; place-items:center; min-width:0; aspect-ratio:1; border-radius:50%; font-size:.82rem; color:var(--text-muted); }
.te-racha-cal-dia--vacio { visibility:hidden; }
.te-racha-cal-leyenda { font-size:.76rem; }

/* Bienvenida: controles completos, sin recortes, con la forma de las muestras. */
.onboarding-contenido { max-height:none!important; overflow:visible!important; }
.onboarding-copy { min-width:0; overflow-x:visible!important; }
.onboarding-paletas { width:min(100%,520px); grid-template-columns:repeat(7,minmax(0,1fr))!important; gap:10px!important; overflow:visible; }
.onboarding-color { position:relative; width:100%!important; height:auto!important; aspect-ratio:1; min-width:0; padding:0!important; border:2px solid transparent!important; border-radius:15px!important; outline:1px solid color-mix(in srgb,var(--text-primary) 18%,transparent)!important; outline-offset:1px!important; box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--bg-canvas) 24%,transparent); }
.onboarding-color[aria-pressed="true"] { outline:2px solid var(--text-primary)!important; outline-offset:2px!important; box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-1) 55%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--bg-canvas) 24%,transparent); }
.onboarding-paleta-nueva { display:grid; place-items:center; border-radius:15px!important; font-size:1.5rem!important; line-height:1; background:var(--bg-panel)!important; color:var(--text-primary)!important; }
.onboarding-logos { min-width:0; overflow:visible; flex-wrap:wrap; padding:5px 4px 8px; }
.onboarding-logo { flex:0 0 92px; width:92px!important; height:92px!important; overflow:visible!important; }
.onboarding-logo img { width:76px!important; height:76px!important; max-width:100%; }
.onboarding-file-picker { min-width:0; max-width:100%; white-space:nowrap; overflow:visible!important; }
.onboarding-file-input { overflow:hidden; border-radius:inherit; }
.onboarding-logo-heading { min-width:0; flex-wrap:wrap; overflow:visible; }
.onboarding-logo-heading > * { max-width:100%; }
.onboarding-calidad-nota { margin:0; text-align:center; color:var(--accent-1); font-size:.86rem; font-weight:650; }
.onboarding-wapper-cta { width:100%; min-height:56px; font-size:1.02rem; font-weight:750; }
.onboarding-wapper-estado { margin:4px 0; color:var(--accent-1); font-weight:700; }
.onboarding-wapper-etiqueta { display:grid; gap:7px; }
.onboarding-saltar-demo { position:absolute; right:24px; top:14px; z-index:4; min-height:38px; }
.onboarding-modos > .btn,.onboarding-calidades > .btn { transition:background-color .32s ease,color .32s ease,border-color .32s ease,box-shadow .32s ease; }
html,body,#app-shell,.glass-card,.glass-panel,#app-sidebar,.mobile-topbar { transition:background-color .32s ease,color .32s ease,border-color .32s ease; }
@media(min-width:900px) {
  .onboarding-conoce > button { display:none!important; }
  .onboarding-conoce .onboarding-lista { display:grid!important; }
}
@media(max-width:760px) {
  .onboarding-paletas { gap:8px!important; }
  .onboarding-saltar-demo { top:8px; right:12px; min-height:34px; padding:6px 10px; }
  .onboarding-logo-heading { justify-content:space-between; }
}

/* Texto ampliado: los controles crecen en alto y envuelven palabras en vez
   de recortarlas o dibujar puntos suspensivos. */
html[data-text-size="grande"] .pill-group .pill-item,
html[data-text-size="grande"] .btn,
html[data-text-size="grande"] .select-custom-opcion {
  min-width:0; min-height:max-content; height:auto; white-space:normal; overflow-wrap:anywhere; word-break:normal; line-height:1.25; text-overflow:clip;
}
html[data-text-size="grande"] .pill-group { flex-wrap:wrap; overflow-x:visible; overflow-y:visible; }
html[data-text-size="grande"] .ajuste-seccion-cabecera,
html[data-text-size="grande"] .ajuste-seccion-cabecera * { min-width:0; white-space:normal; overflow-wrap:anywhere; }
.tutorial-seccion-acciones { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
.tutorial-seccion-acciones > .btn { min-width:0; white-space:normal; }
.guia-seccion-flotante .guia-seccion-acciones { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:8px; margin-top:12px; }
.guia-seccion-flotante .guia-seccion-acciones .btn { flex:0 1 auto; min-width:96px; }
@media(max-width:420px) { .tutorial-seccion-acciones { grid-template-columns:1fr; } .guia-seccion-flotante .guia-seccion-acciones .btn { flex:1 1 100%; } }

/* Tiempo: semestre como contexto encima del título, al estilo del encabezado de Agenda. */
.te-contexto-semestre { position:relative; z-index:20; width:100%; display:flex; justify-content:center; margin:0 auto -4px; }
.te-contexto-semestre-boton { display:inline-flex; align-items:center; justify-content:center; gap:7px; max-width:100%; padding:3px 12px; border:0; border-radius:999px; background:transparent; color:var(--accent-1); font-size:.82rem; font-weight:750; text-align:center; overflow-wrap:anywhere; cursor:pointer; transition:background-color .25s ease,color .25s ease; }
.te-contexto-semestre-boton::after { content:"⌄"; font-size:.9em; opacity:.8; }
.te-contexto-semestre-boton:hover,.te-contexto-semestre-boton[aria-expanded="true"] { background:color-mix(in srgb,var(--accent-1) 12%,transparent); }
.te-contexto-semestre-opciones { position:absolute; top:calc(100% + 5px); left:50%; transform:translateX(-50%); z-index:50; display:grid; gap:4px; width:min(340px,calc(100vw - 32px)); max-height:min(45vh,360px); overflow:auto; padding:7px; border:1px solid var(--border-glass); border-radius:16px; background:var(--bg-card); box-shadow:var(--shadow-float); }
.te-contexto-semestre-opcion { min-width:0; padding:10px 12px; border:0; border-radius:11px; background:transparent; color:var(--text-primary); text-align:left; overflow-wrap:anywhere; cursor:pointer; }
.te-contexto-semestre-opcion:hover,.te-contexto-semestre-opcion[aria-pressed="true"] { background:color-mix(in srgb,var(--accent-1) 18%,var(--bg-panel)); }

/* Ajustes solicitados para el onboarding y el encabezado de Tiempo. */
.te-encabezado { grid-template-rows:auto auto!important; row-gap:2px!important; }
.te-encabezado > .te-contexto-semestre { grid-column:1/-1; grid-row:1; justify-self:center; margin:0 auto; }
.te-encabezado-titulo-grupo > h2,
.te-encabezado-titulo-grupo > .te-racha-chip,
.te-encabezado > div:last-child { grid-row:2!important; }
.te-encabezado-titulo-grupo > h2 { grid-column:2!important; }
.te-encabezado-titulo-grupo > .te-racha-chip { grid-column:1!important; }
.te-encabezado > div:last-child { grid-column:3!important; }
.te-contexto-semestre-boton { min-height:28px; padding:2px 8px; font-size:.84rem; font-weight:650; }
.onboarding-paletas { margin-inline:auto; }
.onboarding-logo-heading > button:first-child { display:inline-flex; align-items:center; justify-content:flex-start; gap:8px; line-height:1; }
.onboarding-wapper-cta-wrap { width:100%; margin:14px 0 4px; }
.onboarding-calidad-nota { width:100%; }
.onboarding-modos > .btn,.onboarding-calidades > .btn { transition:background-color .48s cubic-bezier(.22,.61,.36,1),color .48s cubic-bezier(.22,.61,.36,1),border-color .48s cubic-bezier(.22,.61,.36,1),box-shadow .48s cubic-bezier(.22,.61,.36,1); }
html,body,#app-shell,.glass-card,.glass-panel,#app-sidebar,.mobile-topbar { transition:background .48s cubic-bezier(.22,.61,.36,1),background-color .48s cubic-bezier(.22,.61,.36,1),color .48s cubic-bezier(.22,.61,.36,1),border-color .48s cubic-bezier(.22,.61,.36,1); }
@media(prefers-reduced-motion:reduce) {
  .onboarding-modos > .btn,.onboarding-calidades > .btn,html,body,#app-shell,.glass-card,.glass-panel,#app-sidebar,.mobile-topbar { transition-duration:.01ms!important; }
}
.csv-archivo-wrap { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin:8px 0; }
.csv-archivo-boton { display:inline-flex; align-items:center; justify-content:center; gap:9px; min-height:44px; cursor:pointer; }
.csv-archivo-icono { display:grid; place-items:center; width:24px; height:24px; border-radius:50%; background:color-mix(in srgb,var(--accent-1) 18%,transparent); font-size:1.1rem; font-weight:800; }
.csv-archivo-input { position:absolute!important; width:1px!important; height:1px!important; padding:0!important; margin:-1px!important; overflow:hidden!important; clip:rect(0,0,0,0)!important; white-space:nowrap!important; border:0!important; }
.csv-archivo-input:focus-visible + .csv-archivo-nombre { outline:2px solid var(--accent-1); }

/* Agenda: búsqueda transversal, filtros contextuales y acciones rápidas. */
.agenda-busqueda-cont { width:100%; margin:8px 0 6px; }
.agenda-busqueda-cont .form-input { min-height:44px; }
.agenda-busqueda-resultados { display:grid; gap:10px; margin:8px 0 14px; }
.agenda-busqueda-grupo { min-width:0; padding:12px; gap:9px; }
.agenda-busqueda-grupo h3 { margin:0; font-size:.94rem; }
.agenda-busqueda-fecha { padding-left:3px; font-size:.76rem; }
.agenda-menu-rapido-evento { position:fixed; z-index:100001; display:grid; min-width:142px; gap:3px; padding:6px; border:1px solid var(--border-glass); border-radius:13px; background:var(--bg-card); box-shadow:var(--shadow-float); }
.agenda-menu-rapido-evento > button { min-height:38px; justify-content:flex-start; text-align:left; }
.agenda-filtros-cronograma { display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:7px; margin:0 0 2px; }
.agenda-filtros-cronograma .agenda-filtro-estado-btn { flex:0 1 auto; min-width:0!important; }
.agenda-select-tipos { display:grid!important; width:100%; gap:8px; overflow:visible!important; background:transparent!important; }
.agenda-select-tipos-fila { display:flex; width:100%; gap:8px; }
.agenda-select-tipos-fila > .pill-item { flex:1 1 0; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.agenda-tipo-config-fila { display:grid!important; grid-template-columns:40px minmax(0,1fr) auto auto; align-items:center; }
.agenda-tipo-config-fila > .agenda-color-picker { grid-column:1; grid-row:1; }
.agenda-tipo-config-fila > input.form-input { grid-column:2; grid-row:1; }
.agenda-tipo-config-fila > .agenda-toggle-activo { grid-column:3; grid-row:1; }
.agenda-tipo-config-fila > .agenda-etiqueta-eliminar { grid-column:4; grid-row:1; min-width:38px; width:38px; min-height:38px; padding:4px; border-color:transparent; color:#fb7185; font-size:1.15rem; }
.agenda-tipo-config-fila > .muted { grid-column:2/-1; grid-row:2; }
.agenda-estado-config-fila { display:flex; justify-content:flex-start; }
.agenda-estado-config-fila > .agenda-color-picker { order:0; flex:0 0 auto; }
.agenda-estado-config-fila > span { order:1; text-align:left; }
.agenda-tipo-config-fila[draggable="true"] { cursor:grab; }
.agenda-tipo-config-fila.arrastrando { opacity:.45; }
.agenda-tipo-config-fila.destino-arrastre { outline:2px solid var(--accent-1); outline-offset:1px; }
.agenda-tipo-nuevo-abrir { display:grid; place-items:center; width:42px; min-width:42px; height:42px; min-height:42px; padding:0; margin:8px auto 0; border-radius:13px; font-size:1.5rem; line-height:1; }
.agenda-tipo-nuevo-form { margin-top:8px; padding:14px; border:1px solid var(--border-glass); border-radius:16px; }
.agenda-tipo-nuevo-opciones { justify-content:center; }
.agenda-tipo-nuevo-opciones > .agenda-selector-comportamiento { flex:1 1 100%; justify-content:center; }
.agenda-tipo-nuevo-form #agenda-tipo-nuevo-agregar { width:100%; }
@media(max-width:480px) {
  .agenda-tipo-config-fila { grid-template-columns:36px minmax(0,1fr) auto 36px; gap:6px!important; }
  .agenda-tipo-config-fila > .agenda-toggle-activo { font-size:.76rem; }
  .agenda-tipo-config-fila > .agenda-etiqueta-eliminar { width:36px; min-width:36px; }
}

/* ---------- Animación de apertura (Ajustes → Personalizar) ----------
   6 bolitas = los colores de las pestañas del folder. Cada una es un
   <input type="color"> recortado en círculo: tocarla abre el selector nativo. */
.intro-colores { display: flex; flex-wrap: wrap; gap: 12px; padding: 4px 2px; }
.intro-color-bolita {
  position: relative; display: block; width: 38px; height: 38px; border-radius: 50%;
  overflow: hidden; cursor: pointer; flex-shrink: 0;
  box-shadow: 0 0 0 2px var(--border-glass), 0 2px 6px rgba(0, 0, 0, 0.25);
  transition: transform var(--transition-fast);
}
.intro-color-bolita:hover { transform: scale(1.08); }
.intro-color-bolita:focus-within { outline: 2px solid var(--accent-2); outline-offset: 3px; }
.intro-color-bolita input[type="color"] {
  position: absolute; inset: -8px; width: calc(100% + 16px); height: calc(100% + 16px);
  padding: 0; border: 0; background: none; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
.intro-color-bolita input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.intro-color-bolita input[type="color"]::-webkit-color-swatch { border: 0; }
.intro-color-bolita input[type="color"]::-moz-color-swatch { border: 0; }
.intro-colores-acciones { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
#btn-intro-colores-paleta[aria-pressed="true"] { border-color: var(--accent-2); color: var(--text-primary); }
#intro-colores-bloque.deshabilitado { opacity: 0.45; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .intro-color-bolita { transition: none; } }
