/* ==========================================================================
   CertiProf OS · Login, shell, sidebar, topbar, buscador y notificaciones
   ========================================================================== */

/* ============================================================== LOGIN ===
   Pantalla congelada: se mantiene tal cual estaba antes del rediseño
   minimalista. Los tokens antiguos se le devuelven en `tokens.css`; aquí solo
   hace falta recuperar el degradado del botón de acceso, que fuera del login
   ya no existe.                                                             */
#login .btn-brand {
  --btn-bg: var(--brand-gradient);
  --btn-bg-hover: var(--brand-gradient);
  --btn-ink: #fff; --btn-ink-hover: #fff;
  --btn-bd: transparent;
  background-size: 170% 170%; background-position: 0% 50%;
  box-shadow: 0 4px 14px rgba(0, 59, 200, .3);
}
#login .btn-brand:hover { background-position: 100% 50%; box-shadow: 0 6px 20px rgba(0, 59, 200, .38); }

#login {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: grid; grid-template-columns: 1.05fr .95fr;
  background: var(--bg);
  animation: fade-in var(--t) var(--ease) both;
}
#login.is-leaving { animation: fade-out var(--t) var(--ease) forwards; }
@keyframes fade-out { to { opacity: 0; visibility: hidden } }

.login-aside {
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: var(--s9) var(--s10);
  color: #fff;
  background: #04081c;
}

/* Fotografía real del equipo (reemplazable por la foto corporativa) */
.login-photo {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 38%;
  filter: saturate(1.06) contrast(1.02) brightness(1.02);
  animation: photo-in 1.4s var(--ease-out) both, ken-burns 30s var(--ease) 1.4s infinite alternate;
}
@keyframes photo-in { from { opacity: 0; transform: scale(1.08) } to { opacity: 1; transform: scale(1.02) } }
@keyframes ken-burns {
  from { transform: scale(1.02) translate3d(0, 0, 0) }
  to   { transform: scale(1.1) translate3d(-1.5%, -1.5%, 0) }
}

/* Capa 1 — tinte de marca muy leve: da color corporativo sin tapar la foto */
.login-aside::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    radial-gradient(760px 560px at 16% 18%, rgba(0, 59, 200, .38), transparent 66%),
    radial-gradient(680px 520px at 86% 26%, rgba(20, 196, 237, .24), transparent 64%),
    radial-gradient(820px 620px at 52% 96%, rgba(1, 181, 162, .22), transparent 66%),
    radial-gradient(520px 440px at 96% 92%, rgba(109, 48, 238, .26), transparent 62%);
  mix-blend-mode: multiply;
  animation: aurora 24s var(--ease) infinite alternate;
}
@keyframes aurora {
  0%   { transform: translate3d(0, 0, 0) scale(1) }
  50%  { transform: translate3d(-3%, 2%, 0) scale(1.07) }
  100% { transform: translate3d(3%, -2%, 0) scale(1.03) }
}

/* Capa 2 — solo dos extremos oscuros; el centro de la foto queda limpio */
.login-aside::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(180deg,
    rgba(4, 8, 28, .55) 0%, rgba(4, 8, 28, .12) 22%,
    rgba(4, 8, 28, 0) 42%, rgba(4, 8, 28, .1) 58%, rgba(4, 8, 28, .55) 100%);
}
/* Solo el contenido se eleva. Si aquí se usara `> *`, la regla pisaría el
   `position: absolute` de la foto y esta dejaría de cubrir el panel. */
.login-aside > .brand,
.login-aside > .login-hero,
.login-aside > footer { position: relative; z-index: 3; }
.login-aside .brand { filter: drop-shadow(0 2px 12px rgba(0, 0, 0, .55)); }

/* El texto vive en un panel translúcido: así se lee sin oscurecer la imagen */
.login-hero {
  padding: var(--s5) var(--s6);
  border-radius: var(--r-xl);
  background: rgba(4, 8, 28, .42);
  border: 1px solid rgba(255, 255, 255, .14);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .3);
}

/* Cifra flotante sobre la foto */
.login-proof {
  position: absolute; right: var(--s8); bottom: 25%; z-index: 3;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--s4) var(--s5);
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: var(--r-lg);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .3);
  animation: rise 1s var(--ease-out) .5s both;
}
.login-proof .n {
  font-family: var(--font-display); font-size: var(--fs-3xl); font-weight: 600;
  letter-spacing: -.03em; line-height: 1; color: #fff;
}
.login-proof .l { font-size: var(--fs-2xs); color: rgba(255, 255, 255, .78); }
@media (max-height: 720px) { .login-proof { display: none; } }
.login-aside img.brand { width: 190px; }
.login-hero h1 {
  font-size: clamp(2rem, 3.2vw, 3rem); font-weight: 600;
  letter-spacing: -.03em; color: #fff; max-width: 15ch;
}
.login-hero p { margin-top: var(--s4); max-width: 46ch; font-size: var(--fs-lg); color: rgba(255, 255, 255, .78); }
.login-feats { display: flex; flex-direction: column; gap: var(--s4); margin-top: var(--s8); }
.login-feat { display: flex; align-items: center; gap: var(--s3); font-size: var(--fs-md); color: rgba(255, 255, 255, .9); }
.login-feat span.ico {
  display: grid; place-items: center; flex: none; width: 34px; height: 34px;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .18);
  backdrop-filter: blur(8px);
}
.login-feat svg { width: 17px; height: 17px; }
.login-aside footer { font-size: var(--fs-xs); color: rgba(255, 255, 255, .5); }

.login-main {
  position: relative;
  display: grid; place-items: center; padding: var(--s7) var(--s6);
  /* Con `.login-tools` fuera de la caja son DOS filas de rejilla, no una. Sin
     esto se irían las dos arriba: las filas automáticas no reparten el hueco
     sobrante, y `place-items` solo centra cada una dentro de la suya. */
  align-content: center;
  overflow-y: auto;
}

/* Idioma y tema. En escritorio conservan el sitio de siempre —arriba de la
   caja, pegados a su borde derecho— y por eso toman su mismo ancho en vez de
   encogerse a lo que ocupan. */
.login-tools {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--s1);
  width: min(400px, 100%);
  margin-bottom: var(--s5);
}
.login-box { width: min(400px, 100%); animation: rise var(--t-slow) var(--ease-out) both; }
/* Solo se ve en teléfono; lo enciende el bloque de 1000 px de más abajo. */
.login-box .mark { display: none; }
/* De los dos, el que contrasta con el lienzo. La especificidad es mayor que la
   de la regla que los enciende, así que manda esté donde esté en el archivo.
   `:not([data-theme="dark"])` y no `[data-theme="light"]`: si el script del
   arranque no llegara a correr, el atributo se queda como venga del HTML y el
   logotipo tiene que salir igual. */
html[data-theme="dark"] .login-box .mark-claro { display: none; }
html:not([data-theme="dark"]) .login-box .mark-oscuro { display: none; }
.login-box h2 { font-size: var(--fs-2xl); letter-spacing: -.025em; }
.login-box > p.lead { margin-top: 6px; color: var(--ink-3); font-size: var(--fs-md); }
.login-form { display: flex; flex-direction: column; gap: var(--s4); margin-top: var(--s7); }
.login-form .row-between { font-size: var(--fs-sm); }
.pw-wrap { position: relative; }
.pw-wrap .input { padding-right: 42px; }
.pw-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center; width: 32px; height: 32px;
  color: var(--ink-4); border-radius: var(--r-xs);
}
.pw-toggle:hover { color: var(--ink-2); background: var(--surface-3); }
.pw-toggle svg { width: 17px; height: 17px; }

.login-sep {
  display: flex; align-items: center; gap: var(--s3);
  margin: var(--s6) 0 var(--s4);
  font-size: var(--fs-xs); color: var(--ink-4); text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
}
.login-sep::before, .login-sep::after { content: ""; flex: 1; height: 1px; background: var(--border); }

.demo-users { display: flex; flex-direction: column; gap: var(--s2); }
.demo-user {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%; padding: var(--s3);
  text-align: left;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: all var(--t-fast) var(--ease);
}
.demo-user:hover {
  border-color: var(--primary); background: var(--primary-soft);
  transform: translateX(2px);
}
.demo-user .nm { font-size: var(--fs-md); font-weight: 600; }
.demo-user .rl { font-size: var(--fs-xs); color: var(--ink-3); }
.demo-user .go { margin-left: auto; color: var(--ink-4); }
.demo-user:hover .go { color: var(--primary); }
.demo-user .go svg { width: 17px; height: 17px; }

@media (max-width: 1000px) {
  #login { grid-template-columns: 1fr; }
  .login-aside { display: none; }
  /* El doble que antes (168 → 336), a petición de Juan: sin el panel
     lateral esta es la única marca de la pantalla y era la más pequeña de
     las dos. `min()` la sujeta al ancho de la caja para que no toque los
     bordes en un teléfono estrecho. */
  .login-box .mark { display: block; width: min(336px, 100%); margin: 0 auto var(--s6); }
  .login-box { text-align: center; }
  .login-form, .demo-users { text-align: left; }

  /* Idioma y tema, a la esquina superior derecha de la PANTALLA.
     `fixed` y no `absolute`: `.login-main` puede desplazarse cuando se abre el
     teclado, y estos dos no tienen por qué irse con el formulario. Funciona
     porque están fuera de `.login-box` — dentro, su animación de entrada los
     habría anclado a la caja.
     `max()` con el área segura los baja por debajo de la muesca del iPhone. */
  .login-tools {
    position: fixed; z-index: 2;
    top: max(var(--s4), var(--sa-t, 0px));
    right: max(var(--s4), var(--sa-r, 0px));
    width: auto; margin: 0;
  }

  /* El bloque, equilibrado en la ventana.
     Con idioma y tema fuera del flujo, la caja es más corta y el hueco
     sobrante se reparte: antes la franja muerta se acumulaba toda encima del
     logotipo y el conjunto parecía flotar.
     `safe center` y no `center` a secas: en una pantalla corta —o con el
     teclado abierto— centrar un bloque más alto que la ventana lo desborda
     por los DOS lados, y el trozo de arriba deja de poder alcanzarse con el
     dedo. Con `safe`, cuando no cabe se alinea arriba y todo sigue a mano.
     El relleno superior deja libre la fila de idioma y tema. */
  .login-main {
    align-content: safe center;
    /* El relleno se calcula a partir de DÓNDE está la fila de idioma y tema,
       no a ojo: su borde superior + sus 44 px de alto + una separación. Así,
       cuando la pantalla es corta y `safe` alinea arriba, el logotipo empieza
       por debajo de ella y no se le mete debajo (medido a 560 px de alto:
       empezaba en 54 y la fila termina en 60). */
    padding-top: calc(max(var(--s4), var(--sa-t, 0px)) + 44px + var(--s3));
    padding-bottom: var(--s9);
  }
}

/* =============================================================== SHELL == */
#app { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }
#app[hidden] { display: none; }
[data-sidebar="collapsed"] #app { grid-template-columns: var(--sidebar-w-collapsed) minmax(0, 1fr); }

/* ============================================================= SIDEBAR == */
/* El raíl: navy de marca, mate, igual en los dos temas. Es la capa de la casa.
   Sus componentes internos —fichaje, insignias, botones— resuelven los tokens
   genéricos con la paleta del raíl declarada en tokens.css (`.sidebar {…}`),
   así que aquí solo se dibuja la navegación. */
.sidebar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; flex-direction: column;
  height: 100vh;
  color: var(--rail-ink-2);
  background: var(--rail-bg);
  border-right: 1px solid var(--rail-line);
  /* Sin `transition: width`: el ancho del raíl lo pone la columna de `#app`
     y el raíl no declara ninguno propio, así que no había nada que animar. */
}
.sidebar-head {
  display: flex; align-items: center; gap: var(--s3);
  min-height: var(--topbar-h);
  padding: var(--s4) var(--s5) var(--s3);
  flex: none;
}
/* Siempre la versión con la palabra en blanco: el raíl es navy en los dos
   temas, así que el logotipo no cambia con la luz. */
.sidebar-head .logo-full { height: 50px; width: auto; max-width: 206px; margin-left: -6px; object-fit: contain; }
.sidebar-head .logo-mark { height: 36px; width: 36px; display: none; }
[data-sidebar="collapsed"] .sidebar-head { justify-content: center; padding: var(--s4) 0 var(--s3); }
[data-sidebar="collapsed"] .sidebar-head .logo-full { display: none; }
[data-sidebar="collapsed"] .sidebar-head .logo-mark { display: block; }
[data-sidebar="collapsed"] .sidebar-head .collapse-btn { display: none; }
.sidebar-head .collapse-btn { margin-left: auto; color: var(--rail-ink-3); }
.sidebar-head .collapse-btn:hover { color: var(--rail-ink); background: var(--rail-hover); }

.sidebar-nav {
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  padding: var(--s2) var(--s3) var(--s5);
  display: flex; flex-direction: column; gap: 2px;
  scrollbar-color: rgba(255, 255, 255, .18) transparent;
}
.nav-group { margin-top: var(--s3); }
.nav-group:first-child { margin-top: 0; }

/* Cabecera de grupo. Los grupos plegables son un botón; el grupo fijo conserva
   la etiqueta estática. Es la versalita espaciada de la interfaz: rotula una
   superficie de navegación, se escanea y no se lee. */
.nav-group-label,
.nav-group-toggle {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%;
  padding: var(--s2) var(--s3) 6px;
  /* 11 px y no `--fs-3xs` (10). Lo pidio Juan el 4-sep-2026, al renombrar
     cinco grupos: con rotulos mas largos --«EQUIPO RENOVACIONES» donde antes
     decia «RENOVACIONES»-- los cortos se leian mas pequeños de lo que son.
     Va el valor y no el token porque `--fs-3xs` lo usan tambien las pastillas
     y los pies de tarjeta, y ahi 10 px sigue siendo lo correcto. */
  font-size: 11px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--rail-ink-4);
  white-space: nowrap; overflow: hidden;
  border-radius: var(--r-xs);
  text-align: left;
}
.nav-group-toggle {
  padding: 7px var(--s3);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.nav-group-toggle:hover { background: var(--rail-hover); color: var(--rail-ink-2); }
.nav-group-toggle .g-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
/* Que el grupo esté abierto lo dice el galón girado y un punto más de tinta. */
.nav-group-toggle .chev {
  flex: none; color: currentColor;
  transition: transform var(--t) var(--ease);
}
.nav-group.is-open > .nav-group-toggle { color: var(--rail-ink-3); }
.nav-group.is-open > .nav-group-toggle .chev { transform: rotate(180deg); }

/* Contador agregado del grupo cuando está cerrado. Rojo de marca con texto
   blanco, declarado aquí y no por token: el rojo del raíl es más claro para
   leerse como texto y no aguanta blanco encima. */
.nav-group-toggle .g-pill {
  flex: none; padding: 1px 6px;
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: 0;
  background: var(--brand-red); color: #fff; border-radius: var(--r-full);
  font-variant-numeric: tabular-nums;
}
.nav-group.is-open > .nav-group-toggle .g-pill { display: none; }

.nav-items { display: flex; flex-direction: column; gap: 2px; }
.nav-group:not(.is-open):not(.is-static) > .nav-items { display: none; }
.nav-group.is-open > .nav-items { animation: nav-open var(--t) var(--ease-out) both; }
@keyframes nav-open {
  from { opacity: 0; transform: translateY(-4px) }
  to   { opacity: 1; transform: none }
}

/* Rail colapsado: no hay acordeón posible, se muestran todos los iconos */
[data-sidebar="collapsed"] .nav-group-label,
[data-sidebar="collapsed"] .nav-group-toggle {
  justify-content: center; padding: var(--s3) 0 var(--s2); font-size: 0; pointer-events: none;
}
[data-sidebar="collapsed"] .nav-group-toggle .chev,
[data-sidebar="collapsed"] .nav-group-toggle .g-pill { display: none; }
[data-sidebar="collapsed"] .nav-group-label::after,
[data-sidebar="collapsed"] .nav-group-toggle::after {
  content: ""; display: block; width: 18px; height: 1px; margin: 0 auto;
  background: rgba(255, 255, 255, .16);
}
[data-sidebar="collapsed"] .nav-group > .nav-items { display: flex !important; }

/* El enlace: texto claro sobre navy, icono atenuado. El destino activo se
   rellena con una píldora blanca translúcida y su icono pasa a cian —el único
   color del raíl aparte del logotipo—, así que se encuentra de un vistazo sin
   que doce iconos compitan por el mismo acento. */
.nav-link {
  position: relative;
  display: flex; align-items: center; gap: var(--s3);
  padding: 8px var(--s3);
  font-size: var(--fs-md); font-weight: 500; color: var(--rail-ink-2);
  border-radius: var(--r-sm);
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.nav-link:hover { background: var(--rail-hover); color: var(--rail-ink); text-decoration: none; }
.nav-link svg {
  width: 18px; height: 18px; flex: none;
  color: var(--rail-ink-3);
  transition: color var(--t-fast) var(--ease);
}
.nav-link:hover svg { color: var(--rail-ink); }
/* El rótulo hereda del enlace, y hay que decirlo: `class="label"` choca con la
   utilidad de formularios de components.css, que impone otro color y tamaño. */
.nav-link .label {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis;
  color: inherit; font-size: inherit; font-weight: inherit; letter-spacing: normal;
}
.nav-link.is-active {
  background: var(--rail-active); color: var(--rail-ink); font-weight: 600;
}
.nav-link.is-active svg { color: var(--rail-accent); }
.nav-link.is-active:hover { background: var(--rail-active); }

.nav-link .pill {
  padding: 1px 7px; font-size: var(--fs-2xs); font-weight: 700;
  border-radius: var(--r-full);
  background: var(--brand-red); color: #fff;
  font-variant-numeric: tabular-nums;
}
.nav-link .pill[data-tone="soft"] { background: rgba(255, 255, 255, .14); color: var(--rail-ink); }

[data-sidebar="collapsed"] .nav-link { justify-content: center; padding: 10px 0; }
[data-sidebar="collapsed"] .nav-link .label,
[data-sidebar="collapsed"] .nav-link .pill { display: none; }
[data-sidebar="collapsed"] .nav-link[data-tip]::after { font-size: var(--fs-xs); }

.sidebar-foot {
  flex: none; padding: var(--s3);
  border-top: 1px solid var(--rail-line);
}

/* ==========================================================================
   CertiPoo
   La mascota se sienta ENCIMA de la tarjeta de fichaje, montada sobre su borde
   con un margen negativo. No ocupa alto propio: si lo ocupara, el pie del menú
   crecería y en pantallas bajas empujaría la navegación fuera de la vista.

   Su paleta es la de la hoja de personaje y se declara aquí, no en los tokens:
   el naranja es de CertiPoo, no de la marca. Mezclarlo con los tokens haría
   que un cambio de tema le cambiara la piel.
   ========================================================================== */
.certipoo {
  --cp-fur-dark: #4B2E4D;
  --cp-ink: #1E1E24;
  display: block; width: 76px; margin: 0 auto -18px;
  /* Se le puede hablar: al pulsarlo cuenta cómo va la jornada. El sitio que
     ocupa no pisa el botón de fichar —se monta sobre el borde de la tarjeta,
     no sobre su contenido—, así que el clic nunca es ambiguo. */
  cursor: pointer;
}
.cp-svg { width: 100%; height: auto; overflow: visible; display: block; }

/* El degradado del cuerpo va en el atributo `fill` del propio SVG y NO aquí:
   cada CertiPoo genera su degradado con un id distinto —si dos comparten uno,
   el que desaparece primero deja al otro sin piel—, y una regla de CSS con un
   id fijo ganaría a la del atributo y los dejaría a los dos en negro. */
.cp-body-c { fill: #FF8A4D; }
.cp-foot,
.cp-paw    { fill: var(--cp-fur-dark); }
.cp-ball   { fill: var(--cp-ink); }
.cp-shine  { fill: #fff; }
.cp-eye,
.cp-mouth  { fill: none; stroke: var(--cp-ink); stroke-width: 3.4; stroke-linecap: round; }
.cp-mouth.cp-fill { fill: var(--cp-ink); stroke: none; }
.cp-spark  { fill: #FAE721; }
.cp-bell   { fill: #FAE721; }
.cp-drop   { fill: #7FC6FF; }

/* Respira. Un personaje quieto sobre una tarjeta parece una calcomanía; el
   movimiento es lo que lo convierte en alguien que está ahí. */
@keyframes cp-breathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-2px) scale(1.015); }
}
@keyframes cp-wave {
  0%, 60%, 100% { transform: rotate(0) }
  20%, 40%      { transform: rotate(-22deg) }
}
@keyframes cp-spark {
  0%, 100% { opacity: .35; transform: scale(.85) }
  50%      { opacity: 1;   transform: scale(1.1) }
}
.cp-svg      { animation: cp-breathe 3.6s var(--ease) infinite; }
.cp-wave     { transform-origin: 20px 52px; animation: cp-wave 2.8s var(--ease) infinite; }
.cp-spark    { transform-origin: 50% 50%; animation: cp-spark 1.6s var(--ease) infinite; }

/* ---- Parpadeo y mirada --------------------------------------------------
   Las dos cosas que más «vivo» lo hacen y las dos más baratas. El parpadeo es
   irregular a propósito: uno cada seis segundos y medio, con dos seguidos de
   vez en cuando, que es como parpadea alguien. Un parpadeo metronómico se
   nota, y lo que se nota deja de ser vida y pasa a ser una animación. */
@keyframes cp-blink {
  0%, 88%, 100% { transform: scaleY(1) }
  91%, 93%      { transform: scaleY(.08) }
  92%           { transform: scaleY(1) }
}
.cp-eyes { transform-origin: 50px 52px; animation: cp-blink 6.4s var(--ease) infinite; }

/* La mirada sigue al cursor. El recorrido lo pone el motor en `--lx`/`--ly`,
   con tope de dos píxeles y medio: lo justo para que se note que te mira. */
.cp-look {
  transform: translate(calc(var(--lx, 0) * 1px), calc(var(--ly, 0) * 1px));
  transition: transform .18s var(--ease-out);
}

/* ---- Reacciones ---------------------------------------------------------
   Una por evento, y todas terminan: solo el reposo se repite para siempre.
   Una celebración en bucle deja de celebrar a la tercera vuelta. */
@keyframes cp-jump {
  0%   { transform: translateY(0) scale(1) }
  30%  { transform: translateY(-16px) scale(1.06, .97) }
  60%  { transform: translateY(0) scale(.96, 1.04) }
  100% { transform: translateY(0) scale(1) }
}
@keyframes cp-shake {
  0%, 100% { transform: translateX(0) }
  25%      { transform: translateX(-4px) rotate(-2deg) }
  75%      { transform: translateX(4px) rotate(2deg) }
}
@keyframes cp-swing {
  0%, 100% { transform: rotate(0) }
  25%      { transform: rotate(-7deg) }
  60%      { transform: rotate(7deg) }
}
@keyframes cp-nod {
  0%, 100% { transform: rotate(0) translateY(0) }
  40%      { transform: rotate(-5deg) translateY(-3px) }
}
@keyframes cp-perk {
  0%, 100% { transform: translateY(0) scale(1) }
  30%      { transform: translateY(-9px) scale(1.06, .95) }
  60%      { transform: translateY(0) scale(.97, 1.03) }
}
@keyframes cp-peek {
  0%, 100% { transform: rotate(0) }
  30%      { transform: rotate(-8deg) }
  65%      { transform: rotate(6deg) }
}

.certipoo.is-celebra .cp-svg { animation: cp-jump .62s var(--ease-out) 3, cp-breathe 3.6s var(--ease) 1.9s infinite; }
.certipoo.is-ups     .cp-svg { animation: cp-shake .46s var(--ease) 3, cp-breathe 3.6s var(--ease) 1.4s infinite; }
.certipoo.is-avisa   .cp-svg { animation: cp-swing .7s var(--ease) 3, cp-breathe 3.6s var(--ease) 2.1s infinite; }
.certipoo.is-guina   .cp-svg { animation: cp-nod .8s var(--ease) 2, cp-breathe 3.6s var(--ease) 1.6s infinite; }
.certipoo.is-perk    .cp-svg { animation: cp-perk 1.1s var(--ease-out); }
.certipoo.is-peek    .cp-svg { animation: cp-peek 1.3s var(--ease); }
.cp-bell   { transform-origin: 79px 18px; animation: cp-swing .55s var(--ease) 5; }
.cp-laptop { transform-origin: 50px 78px; animation: cp-breathe 2.4s var(--ease) infinite; }

/* En el rail plegado cabe, pero más pequeño y sin brazo asomando. */
[data-sidebar="collapsed"] .certipoo { width: 46px; margin-bottom: -12px; }

/* Con «reducir movimiento» se queda quieto. Un personaje que respira es
   simpático; uno que no para de moverse cuando alguien ha pedido lo contrario
   es un obstáculo. Habla igual: lo que se apaga es el gesto, no el aviso. */
@media (prefers-reduced-motion: reduce) {
  .cp-svg, .cp-wave, .cp-spark, .cp-eyes, .cp-bell, .cp-laptop,
  .certipoo.is-celebra .cp-svg, .certipoo.is-ups .cp-svg,
  .certipoo.is-avisa .cp-svg, .certipoo.is-guina .cp-svg,
  .certipoo.is-perk .cp-svg, .certipoo.is-peek .cp-svg { animation: none; }
  .cp-look { transition: none; }
}

/* ==========================================================================
   CertiPoo · la burbuja
   Cuando tiene algo que decir habla el CertiPoo que ya vive sobre el fichaje:
   la burbuja se ancla a su derecha con coordenadas calculadas en el motor. Si
   el rail no está a la vista —móvil, o el menú en cajón— sale él solo por la
   esquina de abajo a la izquierda, que es donde lo puso el diseño.

   Va suelta de <body> y no del shell porque el shell se reconstruye entero al
   cambiar de idioma o de tema, y eso no es motivo para callarlo a media frase.
   ========================================================================== */
.poo-layer {
  position: fixed; left: var(--s5); bottom: var(--s5);
  z-index: calc(var(--z-drawer) - 10);
  display: flex; align-items: flex-end; gap: 10px;
  pointer-events: none;      /* nunca se pone delante de un botón */
  transition: opacity .28s var(--ease), transform .34s var(--ease-out);
}
.poo-layer[data-visible="false"] {
  opacity: 0; visibility: hidden;
  transform: translateY(12px) scale(.96);
}
.poo-layer[data-visible="true"] { opacity: 1; visibility: visible; transform: none; }

.poo-float { width: 92px; filter: drop-shadow(0 10px 20px rgba(0, 0, 0, .32)); }
.poo-float:empty { display: none; }
.poo-float .certipoo { width: 100%; margin: 0; }

/* La burbuja es oscura en los dos temas a propósito: es la voz del personaje,
   no una tarjeta de la interfaz, y su fondo sale de la paleta de la mascota
   (#1E1E24). Con los tokens cambiaría de piel al cambiar de tema y dejaría de
   leerse como algo que dice él. */
.poo-bubble {
  max-width: 262px;
  padding: 12px 16px;
  border-radius: 16px 16px 16px 4px;
  background: #202126;
  border: 1px solid rgba(255, 255, 255, .09);
  color: #fff;
  font-size: 13.5px; line-height: 1.45; font-weight: 500;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .3);
}
.poo-layer[data-tone="error"] .poo-bubble { border-color: rgba(255, 138, 77, .5); }
.poo-layer[data-tone="party"] .poo-bubble { border-color: rgba(250, 231, 33, .45); }

/* Anclada al de la barra lateral, la burbuja apunta hacia abajo a la izquierda,
   hacia él. Suelta en la esquina, el pico va abajo y el personaje al lado. */
.poo-layer[data-anchored="true"] .poo-float { display: none; }

@keyframes poo-bubble-in {
  from { opacity: 0; transform: translateX(-10px) scale(.95) }
  to   { opacity: 1; transform: none }
}
.poo-bubble.is-in { animation: poo-bubble-in .35s var(--ease-out); }

@media (max-width: 640px) {
  .poo-layer { left: var(--s4); bottom: var(--s4); }
  .poo-bubble { max-width: min(70vw, 262px); }
  .poo-float { width: 76px; }
}

@media (prefers-reduced-motion: reduce) {
  .poo-layer, .poo-bubble.is-in { transition: none; animation: none; }
}

/* ---- Fichaje: reloj, estado y acción ------------------------------------
   Vive en el pie del raíl. Con los tokens del raíl, la caja es una superficie
   blanca translúcida sobre el navy y el botón de fichar sale en el cian del
   brandbook: es la acción que más se repite en el día y la única con color
   sólido en toda la columna. */
.punch-box {
  display: flex; flex-direction: column; gap: var(--s3);
  padding: var(--s3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.punch-now { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.punch-now .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); flex: none;
}
.punch-box.is-in .dot { background: var(--success); animation: pulse-ring 2.4s infinite; }
.punch-box.is-break .dot { background: var(--warning); }
.punch-now .t {
  font-family: var(--font-display);
  font-size: var(--fs-lg); font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--ink); letter-spacing: .01em; line-height: 1;
}
.punch-now .st {
  margin-left: auto;
  font-size: var(--fs-2xs); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.punch-done { justify-content: center; width: 100%; }

/* La mascota se apoya sobre la caja; una sombra suave la despega del navy. */
.sidebar .cp-svg { filter: drop-shadow(0 8px 16px rgba(0, 0, 0, .35)); }

/* Rail plegado (76 px): solo el punto y el icono de la acción. El reloj no
   cabe sin partirse, y una hora partida se lee peor que ninguna. */
[data-sidebar="collapsed"] .punch-box { padding: var(--s2); background: transparent; border-color: transparent; gap: var(--s2); }
[data-sidebar="collapsed"] .punch-now { justify-content: center; }
[data-sidebar="collapsed"] .punch-now .t,
[data-sidebar="collapsed"] .punch-now .st,
[data-sidebar="collapsed"] .punch-box .lb { display: none; }
[data-sidebar="collapsed"] .punch-box .btn { padding: 0; justify-content: center; }

/* Drawer móvil */
.sidebar-scrim {
  position: fixed; inset: 0; z-index: calc(var(--z-drawer) - 1);
  background: rgba(13, 16, 32, .38);
  animation: fade-in var(--t-fast) var(--ease) both;
}

@media (max-width: 1024px) {
  #app, [data-sidebar="collapsed"] #app { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: fixed; left: 0; top: 0; z-index: var(--z-drawer);
    width: var(--sidebar-w);
    transform: translateX(-100%);
    transition: transform var(--t) var(--ease-out);
    box-shadow: var(--shadow-xl);
  }
  [data-drawer="open"] .sidebar { transform: none; }
  [data-sidebar="collapsed"] .sidebar { width: var(--sidebar-w); }
  [data-sidebar="collapsed"] .nav-link { justify-content: flex-start; padding: 9px var(--s3); }
  [data-sidebar="collapsed"] .nav-link .label,
  [data-sidebar="collapsed"] .nav-link .pill { display: block; }
  [data-sidebar="collapsed"] .sidebar-head { justify-content: flex-start; padding: 0 var(--s4); }
  [data-sidebar="collapsed"] .sidebar-head .logo-full { display: block; }
  [data-sidebar="collapsed"] .sidebar-head .logo-mark { display: none; }
  /* En el cajón móvil el menú siempre va completo: el acordeón vuelve a mandar */
  [data-sidebar="collapsed"] .nav-group-label,
  [data-sidebar="collapsed"] .nav-group-toggle {
    justify-content: flex-start; text-align: left; font-size: var(--fs-3xs); pointer-events: auto;
  }
  [data-sidebar="collapsed"] .nav-group-label { padding: var(--s2) var(--s3) 6px; }
  [data-sidebar="collapsed"] .nav-group-toggle { padding: 7px var(--s3); }
  [data-sidebar="collapsed"] .nav-group-label::after,
  [data-sidebar="collapsed"] .nav-group-toggle::after { display: none; }
  [data-sidebar="collapsed"] .nav-group-toggle .chev,
  [data-sidebar="collapsed"] .nav-group-toggle .g-pill { display: block; }
  [data-sidebar="collapsed"] .nav-group.is-open > .nav-group-toggle .g-pill { display: none; }
  [data-sidebar="collapsed"] .nav-group:not(.is-open):not(.is-static) > .nav-items { display: none !important; }
  /* En el cajón el pie va completo: no es un rail, tiene los 264 px enteros */
  [data-sidebar="collapsed"] .punch-box {
    padding: var(--s3); background: var(--surface-2); border-color: var(--border); gap: var(--s3);
  }
  [data-sidebar="collapsed"] .punch-now { justify-content: flex-start; }
  [data-sidebar="collapsed"] .punch-now .t,
  [data-sidebar="collapsed"] .punch-box .lb { display: inline; }
  [data-sidebar="collapsed"] .punch-now .st { display: block; }
  [data-sidebar="collapsed"] .punch-box .btn { padding: 0 var(--s3); }
}

/* ============================================================== TOPBAR == */
.main-col { display: flex; flex-direction: column; min-width: 0; }

/* La barra es una superficie, como las tarjetas: blanca sobre el lienzo gris
   (y un tono más clara que el fondo en oscuro), con el mismo borde de
   tarjeta abajo. Pintada del gris del lienzo se perdía: la campana, el
   interruptor y el usuario flotaban sin que se viera dónde termina la barra.
   Opaca y sin desenfoque: el cristal deja translucir el contenido al hacer
   scroll y ensucia la lectura. */
.topbar {
  position: sticky; top: 0; z-index: calc(var(--z-sticky) - 1);
  display: flex; align-items: center; gap: var(--s2);
  height: var(--topbar-h); padding: 0 var(--s6);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.topbar .menu-btn { display: none; }
@media (max-width: 1024px) { .topbar .menu-btn { display: inline-flex; } }
@media (max-width: 640px) { .topbar { padding: 0 var(--s4); } }

/* Dónde estoy: grupo del menú y destino. Es lo que se pierde al plegar el
   raíl, y lo que orienta a quien llega por un enlace. */
.tb-crumb {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  margin-left: var(--s1);
  font-size: var(--fs-sm); color: var(--ink-3);
}
.tb-crumb svg { width: 14px; height: 14px; flex: none; color: var(--ink-4); }
.tb-crumb .tb-g { white-space: nowrap; }
.tb-crumb .tb-i { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 760px) { .tb-crumb { display: none; } }

/* El interruptor de tema queda entre la búsqueda y la campana, con el mismo
   aire a cada lado. */
.topbar > .theme-switch { margin: 0 var(--s2); }

/* Buscador global */
.search {
  position: relative; flex: 1 1 auto; max-width: 460px;
}
.search-input {
  width: 100%; height: 39px; padding: 0 var(--s8) 0 38px;
  font-size: var(--fs-md);
  background: var(--surface-3); border: 1px solid transparent;
  border-radius: var(--r-sm);
  transition: all var(--t-fast) var(--ease);
}
.search-input::placeholder { color: var(--ink-4); }
.search-input:hover { background: var(--surface-inset); border-color: var(--border); }
.search-input:focus {
  outline: none; background: var(--surface);
  border-color: var(--primary); box-shadow: var(--ring);
}
.search > .s-ico {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  width: 17px; height: 17px; color: var(--ink-4); pointer-events: none;
}
.search > .s-kbd { position: absolute; right: 9px; top: 50%; transform: translateY(-50%); pointer-events: none; }
.search-input:focus ~ .s-kbd { opacity: 0; }
@media (max-width: 700px) {
  .search { max-width: none; }
  .search > .s-kbd { display: none; }
}

.search-results {
  position: absolute; left: 0; right: 0; top: calc(100% + 8px);
  max-height: min(64vh, 520px); overflow-y: auto;
  padding: var(--s2);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-lg);
  animation: slide-down var(--t-fast) var(--ease-out) both;
}
@media (max-width: 700px) { .search-results { width: calc(100vw - var(--s7)); } }

.sr-group + .sr-group { margin-top: var(--s2); }
.sr-label {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 10px 5px;
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-4);
}
.sr-item {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%; padding: 8px 10px;
  border-radius: var(--r-xs); text-align: left;
  transition: background var(--t-fast) var(--ease);
}
.sr-item:hover, .sr-item.is-cursor { background: var(--primary-soft); text-decoration: none; }
.sr-item .sr-ico {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--r-xs);
  background: var(--surface-3); color: var(--ink-3);
}
.sr-item:hover .sr-ico, .sr-item.is-cursor .sr-ico { background: var(--surface); color: var(--primary); }
.sr-item .sr-ico svg { width: 16px; height: 16px; }
.sr-item .t { font-size: var(--fs-md); font-weight: 500; color: var(--ink); }
.sr-item .t mark { background: transparent; color: var(--primary); font-weight: 700; }
.sr-item .d { font-size: var(--fs-xs); color: var(--ink-3); }
.sr-item .go { margin-left: auto; opacity: 0; color: var(--ink-4); font-size: var(--fs-2xs); }
.sr-item.is-cursor .go { opacity: 1; }

/* Paleta ⌘K */
.palette-overlay {
  position: fixed; inset: 0; z-index: var(--z-palette);
  display: flex; justify-content: center;
  padding: clamp(48px, 12vh, 130px) var(--s5) var(--s5);
  background: rgba(13, 16, 32, .38);
  animation: fade-in var(--t-fast) var(--ease) both;
}
[data-theme="dark"] .palette-overlay { background: rgba(0, 0, 0, .6); }
.palette {
  display: flex; flex-direction: column;
  width: min(620px, 100%); max-height: 62vh;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-xl);
  overflow: hidden;
  animation: pop var(--t) var(--ease-out) both;
}
.palette-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border-soft);
}
.palette-head svg { width: 19px; height: 19px; color: var(--ink-4); flex: none; }
.palette-head input {
  flex: 1; border: 0; background: transparent; outline: none;
  font-size: var(--fs-lg); color: var(--ink);
}
.palette-head input::placeholder { color: var(--ink-4); }
.palette-body { flex: 1; overflow-y: auto; padding: var(--s2); }
.palette-foot {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s5);
  border-top: 1px solid var(--border-soft); background: var(--surface-2);
  font-size: var(--fs-2xs); color: var(--ink-4);
}
.palette-foot span { display: inline-flex; align-items: center; gap: 5px; }

/* ======================================================= NOTIFICACIONES = */
.bell { position: relative; }
.bell-dot {
  position: absolute; top: 5px; right: 5px;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: grid; place-items: center;
  font-size: var(--fs-3xs); font-weight: 700; line-height: 1;
  color: #fff; background: var(--brand-red);
  border: 2px solid var(--bg); border-radius: var(--r-full);
  font-variant-numeric: tabular-nums;
}
.notif-panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: var(--z-sticky);
  width: min(392px, calc(100vw - var(--s7)));
  max-height: min(72vh, 560px);
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: slide-down var(--t-fast) var(--ease-out) both;
}
.notif-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: var(--s4) var(--s4) var(--s3);
  border-bottom: 1px solid var(--border-soft);
}
.notif-list { flex: 1; overflow-y: auto; padding: var(--s2); }
.notif {
  position: relative;
  display: flex; gap: var(--s3);
  width: 100%; padding: var(--s3);
  text-align: left; border-radius: var(--r-sm);
  transition: background var(--t-fast) var(--ease);
}
.notif:hover { background: var(--surface-2); text-decoration: none; }
.notif.is-unread { background: var(--primary-soft); }
.notif.is-unread:hover { background: var(--primary-soft-2); }
.notif .n-ico {
  display: grid; place-items: center; flex: none;
  width: 34px; height: 34px; border-radius: var(--r-sm);
  background: var(--surface-3); color: var(--ink-3);
}
.notif .n-ico svg { width: 17px; height: 17px; }
.notif .n-ico[data-tone="success"] { background: var(--success-soft); color: var(--success); }
.notif .n-ico[data-tone="warning"] { background: var(--warning-soft); color: var(--warning); }
.notif .n-ico[data-tone="danger"]  { background: var(--danger-soft);  color: var(--danger); }
.notif .n-ico[data-tone="primary"] { background: var(--primary-soft); color: var(--primary); }
.notif .n-title { font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.notif .n-body  { font-size: var(--fs-sm); color: var(--ink-3); margin-top: 1px; }
.notif .n-time  { font-size: var(--fs-2xs); color: var(--ink-4); margin-top: 4px; }
.notif .n-unread {
  position: absolute; right: var(--s3); top: 50%; transform: translateY(-50%);
  width: 7px; height: 7px; border-radius: 50%; background: var(--primary);
}
.notif-day {
  padding: var(--s3) var(--s3) 5px;
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-4);
}

/* El reloj y el fichaje ya no viven aquí: están en el pie de la barra
   lateral, con las reglas `.punch-box` de más arriba. */

/* Menú de usuario */
.user-btn {
  display: flex; align-items: center; gap: var(--s2);
  height: 42px; padding: 0 8px 0 4px;
  border-radius: var(--r-full);
  border: 1px solid transparent;
  max-width: 250px;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.user-btn:hover { background: var(--surface-3); border-color: var(--border); }
.user-btn > svg.ico { width: 15px; height: 15px; color: var(--ink-4); }

.user-btn .who {
  display: none; min-width: 0; flex-direction: column;
  align-items: flex-start; justify-content: center;
  text-align: left; line-height: 1.2;
}
@media (min-width: 1240px) { .user-btn .who { display: flex; } }

/* Nombre y rol son bloques con truncado: si van en línea se pegan entre sí. */
.user-btn .nm {
  display: block; max-width: 150px;
  font-size: var(--fs-sm); font-weight: 600; line-height: 1.25; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-btn .rl {
  display: block; max-width: 150px;
  font-size: var(--fs-2xs); font-weight: 500; color: var(--ink-3); line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.user-card {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3); margin-bottom: var(--s2);
  background: var(--surface-2); border-radius: var(--r-sm);
}

/* ============================================================ CONTENIDO = */
.view {
  flex: 1 1 auto;
  width: 100%; max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--s6) var(--s7) var(--s8);
}
@media (max-width: 640px) { .view { padding: var(--s4); } }

/* Repintar la MISMA pantalla —un filtro, guardar, datos que llegan— no es
   entrar en ella: las animaciones de entrada solo se ven al llegar. Sin esto
   las tarjetas y las barras volvían a subir con cada clic y la pantalla
   parecía recargarse. La clase la pone el enrutador (ver «La vista no se
   mueve» en core/router.js); los cargadores y los latidos siguen vivos. */
#outlet.is-repaint .stagger > *,
#outlet.is-repaint .anim-rise,
#outlet.is-repaint .anim-fade,
#outlet.is-repaint .cc-bar,
#outlet.is-repaint .cg-bar,
#outlet.is-repaint .hbar-fill,
#outlet.is-repaint .stack-bar > span { animation: none; }

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s4);
  margin-bottom: var(--s6);
}
.page-head h1 { font-size: var(--fs-2xl); letter-spacing: -.028em; text-wrap: balance; }
/* El título con algo a su lado (`aside` en UI.pageHead): una cifra, una
   pastilla. Centrados en la línea del título, no en su caja. */
.page-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3); }
.page-head .sub { margin-top: 4px; font-size: var(--fs-md); color: var(--ink-3); max-width: 72ch; }
.page-head .actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* Aviso de módulo en construcción. Va pegado bajo el encabezado, por eso
   recupera parte del margen inferior de .page-head. Se apaga entero desde
   S.BUILDING cuando la plataforma tenga datos reales. */
.building-note {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin: calc(var(--s4) - var(--s6)) 0 var(--s6);
  padding: var(--s3) var(--s4);
  background: var(--warning-soft);
  /* El borde entero en ambar, no una banda de 3 px a la izquierda: el aviso
     ya va sobre relleno ambar y la banda solo desalineaba el texto. */
  border: 1px solid var(--warning);
  border-radius: var(--r-md);
}
.building-note p { max-width: 90ch; }
.building-note .badge { flex: none; }

.crumbs { display: flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--ink-4); margin-bottom: 7px; }
.crumbs a { color: var(--ink-3); font-weight: 500; }
.crumbs a:hover { color: var(--primary); }
.crumbs svg { width: 13px; height: 13px; }

.section-title {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  margin-bottom: var(--s4);
}
.section-title h2 { font-size: var(--fs-lg); }
.section-title .sub { font-size: var(--fs-xs); color: var(--ink-3); margin-top: 2px; }

/* Franja de bienvenida
   La única superficie del contenido con un lavado de color: un degradado del
   blanco de la tarjeta al azul de marca al 8 % hacia la derecha, con un halo de
   cian detrás. Es el saludo del día —un momento, no un patrón— y ninguna otra
   tarjeta lo hereda. Antes llevaba una línea azul a la izquierda y unas formas
   dibujadas en SVG; ahora la atmósfera la pone el color y nada más. */
.hero-band {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s6);
  padding: var(--s6) var(--s7);
  margin-bottom: var(--s6);
  border-radius: var(--r-lg);
  color: var(--ink);
  background:
    radial-gradient(520px 260px at 92% 0%, color-mix(in srgb, var(--brand-cyan) 16%, transparent), transparent 70%),
    linear-gradient(110deg, var(--surface) 0%, var(--surface) 46%, color-mix(in srgb, var(--primary) 8%, var(--surface)) 100%);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card);
}
[data-theme="dark"] .hero-band {
  background:
    radial-gradient(520px 260px at 92% 0%, color-mix(in srgb, var(--brand-cyan) 14%, transparent), transparent 70%),
    linear-gradient(110deg, var(--surface) 0%, var(--surface) 40%, color-mix(in srgb, var(--brand-blue) 26%, var(--surface)) 100%);
}
.hero-band > * { position: relative; z-index: 1; }
.hero-band h1 { color: var(--ink); font-size: var(--fs-3xl); letter-spacing: -.03em; text-wrap: balance; }
/* El emoji del saludo. Un poco más pequeño que el titular y sin `letter-spacing`
   heredado: el negativo del h1 le comía el aire por la derecha y quedaba pegado
   al nombre. `inline-block` para que `text-wrap: balance` no lo mande solo a la
   segunda línea, que es lo que hacía con el nombre largo. */
.hero-band h1 .hb-emoji {
  display: inline-block; font-size: .82em; letter-spacing: normal;
  margin-left: .08em; vertical-align: baseline;
}
.hero-band p { margin-top: 6px; color: var(--ink-3); font-size: var(--fs-md); }
.hero-band .hb-meta { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s4); }
.hero-band .hb-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; font-size: var(--fs-xs); font-weight: 500;
  color: var(--ink-2); background: color-mix(in srgb, var(--surface) 70%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
}
.hero-band .hb-chip svg { width: 14px; height: 14px; color: var(--ink-4); }
@media (max-width: 640px) { .hero-band { padding: var(--s5); } .hero-band h1 { font-size: var(--fs-2xl); } }

/* Panel de acceso denegado */
.denied {
  display: flex; flex-direction: column; align-items: center; gap: var(--s4);
  padding: var(--s10) var(--s5); text-align: center;
}
.denied .ico {
  display: grid; place-items: center; width: 72px; height: 72px;
  border-radius: var(--r-2xl); background: var(--danger-soft); color: var(--danger);
}
.denied .ico svg { width: 32px; height: 32px; }
