/* ============================================================
   kaizen build os — design tokens + components
   Type scale: base 16px, ratio 1.25 (major third).
   Timing: --t-fast 120ms · --t-base 200ms · --t-slow 400ms.
   Easing: --ease (standard out). --ease-spring para feedbacks.
   ============================================================ */

/* Geist auto-alojada (woff2 variable, latin + latin-ext). Antes se cargaba
   desde fonts.googleapis.com en CADA documento: conexión cross-origin +
   FOUT (texto que "baila") en cada navegación. Servida same-origin y
   cacheable, el texto deja de reflowear al cambiar de pestaña. Geist Mono
   nunca se cargó (el <link> solo pedía Geist), así --font-mono sigue en
   fallback de sistema exactamente igual que antes. */
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 200 700;
  font-display: swap;
  src: url("/static/fonts/geist-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 200 700;
  font-display: swap;
  src: url("/static/fonts/geist-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  /* Type scale (16px base, ratio 1.25) */
  --text-xs:   0.64rem;     /* 10.24px */
  --text-sm:   0.8rem;      /* 12.8px  */
  --text-base: 1rem;        /* 16px    */
  --text-md:   1.25rem;     /* 20px    */
  --text-lg:   1.5625rem;   /* 25px    */
  --text-xl:   1.953rem;    /* 31.25px */

  /* Timing + easing (unificados) */
  --t-fast:    120ms;
  --t-base:    200ms;
  --t-slow:    400ms;
  --ease:        cubic-bezier(0.4, 0, 0.2, 1);    /* standard ease-out */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* gentle spring */

  /* Color · DARK MODE como default único (decisión Ibon 2026-05-18).
     Refactor inspirado en Linear refresh 2026: gris cálido (no azul frío),
     "warmer gray that feels crisp but less saturated, avoiding muddy".
     Valores subidos en --fg-dim para contraste WCAG ≥7:1 (audit ux). */
  --bg:          #0a0a0c;   /* fondo principal, gris muy cálido */
  --bg-2:        #131316;   /* cabeceras / topbar atenuada / cards */
  --fg:          #ededee;   /* texto principal — NO #fff puro (menos glow OLED) */
  --fg-dim:      #a0a0a7;   /* texto secundario / metadata · WCAG ~8:1 */
  --fg-faint:    #6c6c73;   /* texto muy tenue · usar solo si tamaño ≥14px */
  --line:        #1f1f23;   /* separadores sutiles */
  --line-strong: #2c2c33;   /* borders más visibles */

  --warn:        #C97B3B;
  --err:         #DC4A3A;

  /* Acento del enso — IDENTIDAD visual de Kaizen, preservada (invariante).
     Cambiar este valor toca PRODUCT_INVARIANT y requiere validación Ibon. */
  --lime:        #C6F432;
  --lime-soft:   color-mix(in srgb, var(--lime) 80%, transparent);

  /* Pulse del enso/led — depende del estado */
  --accent: var(--fg-dim);
  --glow:   transparent;
  --pulse-duration: 1.8s;

  /* Layout */
  --topbar-h: 52px;
}

/* Modo claro disponible como opt-in explícito (data-theme="light" en <html>),
   NO automático por prefers-color-scheme. Valores conservados por si en el
   futuro queremos exponer un toggle — hoy no se activa nunca. */
[data-theme="light"] {
  --bg:          #FAFAFA;
  --bg-2:        #F4F4F5;
  --fg:          #0A0A0A;
  --fg-dim:      #737373;
  --fg-faint:    #A3A3A3;
  --line:        #F0F0F0;
  --line-strong: #E5E5E5;
  --warn:        #A55624;
  --err:         #B91C1C;
  --lime:        #65A30D;
  --lime-soft:   color-mix(in srgb, var(--lime) 60%, transparent);
}

[data-state="loading"]  { --accent: var(--fg-dim); --glow: transparent;       --pulse-duration: 1.8s; }
[data-state="ok"]       { --accent: var(--lime);   --glow: var(--lime-soft);  --pulse-duration: 2.4s; }
[data-state="degraded"] { --accent: var(--warn);   --glow: var(--warn);       --pulse-duration: 1.2s; }
[data-state="down"]     { --accent: var(--err);    --glow: var(--err);        --pulse-duration: 0.6s; }

/* ============================================================
   reset + base
   ============================================================ */

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Reservar siempre el espacio del scrollbar para que el ancho útil sea
   constante (sin "salto" cuando el contenido excede/no excede el viewport).
   Resuelve cambio de espaciado del topbar al filtrar por todos/guti/yipi. */
html { scrollbar-gutter: stable; }

/* Cross-document View Transitions API. Chrome 126+ y Safari 18.2+ hacen
   un cross-fade nativo entre la página vieja y la nueva — sin flash
   blanco, sin re-flow brusco. Sin soporte (Firefox hoy) → navegación
   normal sin cambios.

   Decisión 2026-05-19: NO se asigna `view-transition-name` al topbar.
   Nombrar un elemento persistente fuerza al browser a hacer "morph"
   entre old/new bounding box; cualquier diferencia mínima (scrollbar
   gutter en /actividad, presence avatars que se inyectan más tarde)
   se traduce en un salto visible. El topbar entra en el cross-fade
   del root junto al resto.

   Honest trade-off: la zona izquierda del topbar (brand + nav)
   cruza sin perceptibilidad porque el HTML servido es idéntico
   entre páginas. La zona derecha (.presence-stack) puede flickear
   ~100-300ms porque pulse.js inyecta los avatares
   post-DOMContentLoaded; el snapshot "new" arranca vacío y se
   rellena después. Este flicker es claramente menor que el morph
   forzado del enfoque anterior con view-transition-name. Para
   eliminarlo del todo habría que renderizar los avatares en SSR
   (out of scope WO). */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 240ms;
  animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms;
  }
}

/* Sub-fase 3A (2026-05-18): scrollbar custom estilo Linear/Notion.
   Sustituye el scrollbar nativo del SO por una versión más fina y discreta
   coherente con la paleta dark cálida. Aplica a todo elemento con scroll,
   incluido html, body, paneles y modales. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--fg-dim) 50%, var(--line-strong));
  background-clip: content-box;
}
*::-webkit-scrollbar-corner { background: transparent; }

/* En /actividad: bloqueamos completamente el scroll del <html> (el viewport
   debe ser EXACTAMENTE 100vh sin ninguna posibilidad de scrollbar global).
   Los paneles internos (master, detail) gestionan su propio scroll.

   `scrollbar-gutter: stable` se hereda del bloque `html { }` de arriba —
   aunque aquí overflow:hidden hace que no haya scrollbar real, mantener
   el gutter reservado evita que el ancho efectivo del topbar difiera
   ~10px respecto al de páginas con scroll. Sin esto, View Transitions
   API anima un cambio lateral del topbar entre páginas. */
html:has(body.page-activity) {
  overflow: hidden;
  height: 100vh;
}

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: 0;
  /* Background sutil: radial gradient muy leve que respira en 60s.
     Imperceptible en 1s, perceptible en 30s. Sensación de "espacio vivo". */
  background:
    radial-gradient(ellipse 80% 60% at 50% 30%,
                    color-mix(in srgb, var(--fg-dim) 6%, var(--bg)) 0%,
                    var(--bg) 60%),
    var(--bg);
  background-size: 100% 200%;
  background-position: 0% 0%;
  animation: bg-breath 60s ease-in-out infinite alternate;
  position: relative;          /* contexto para ::after */
}
/* Sub-fase 3A (2026-05-18): spotlight inferior. Halo gris cálido fijo
   en la parte baja-central del viewport — el "fondo gris asomando"
   estilo linear.app. Pseudo-element fixed con z-index:-1 para quedar
   detrás del contenido sin necesitar tocar z-index de cada stage. */
body::after {
  content: "";
  position: fixed;
  inset: auto 0 -120px 0;
  height: 60vh;
  pointer-events: none;
  background: radial-gradient(
    circle 640px at 50% 100%,
    color-mix(in srgb, var(--fg-dim) 16%, transparent) 0%,
    transparent 60%
  );
  z-index: -1;
}
@keyframes bg-breath {
  from { background-position: 0% 0%; }
  to   { background-position: 0% 40%; }
}
@media (prefers-reduced-motion: reduce) {
  body { animation: none; }
}

/* Login NO tiene topbar — mantiene centered grid puro */
/* ============================================================
   /login — superficie DARK premium coherente con la app interna
   ============================================================
   WO 2026-05-19 iter 4: Ibon revisó el approach light y prefirió
   mantener dark coherente con el resto de Kaizen, pero ELEVADO al
   nivel premium tipo Linear signup (estructura limpia, columna
   centrada, textura sutil, botón pill respirado). El login es la
   antesala de la app y debe sentirse parte del mismo lienzo
   tipográfico/cromático, no una pantalla blanca pegada.

   Sin tokens override (hereda --bg, --fg, --line del :root global
   dark). Solo añadimos textura de superficie y layout centrado. */
body.login-page {
  background: var(--bg);
  color: var(--fg);
  display: grid;
  place-items: center;
  padding: 2rem;
  min-height: 100vh;
  /* Textura sutil dark: dos radial-gradients muy tenues en blanco
     translúcido + un vignette periférico oscuro. Crea sensación de
     superficie inteligente sin protagonismo cromático. */
  background-image:
    radial-gradient(circle at 25% 22%, rgb(255 255 255 / 0.025) 0%, transparent 38%),
    radial-gradient(circle at 78% 80%, rgb(255 255 255 / 0.020) 0%, transparent 42%),
    radial-gradient(ellipse at 50% 50%, transparent 55%, rgb(0 0 0 / 0.45) 100%);
  background-attachment: fixed;
}

/* Focus accessible — anillo consistente en TODO lo focusable */
:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ============================================================
   layout primitives
   ============================================================ */

/* Wrapper que centra el stage en el espacio bajo el topbar */
/* Sub-fase 5B (2026-05-18): márgenes laterales aún más generosos para
   alinearnos con la "respiración" de linear.app en pantallas anchas
   (donde los laterales son ~140-180px). Anterior: max 5rem (80px).
   Ahora: max 9rem (144px). En pantallas estrechas mantiene compacto. */
.stage-container {
  flex: 1 1 auto;
  display: grid;
  place-items: center;
  padding: 2rem clamp(1.5rem, 10vw, 9rem);
}
/* NOTA: anteriormente había una regla `body.page-activity .stage-container`
   con padding 1.75rem/3rem, pero /actividad NO usa `.stage-container`
   (su template va directo a `.activity-toolbar` + `.activity-frame` con
   layout app-shell). La regla era código muerto y se eliminó 2026-05-19
   para evitar confusión: la respiración arriba en /actividad la define
   `.activity-toolbar` (más abajo). */

.stage {
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  text-align: center;
}

/* Stage de la home: ancho mayor, tipografía protagonista, alineado a la izquierda.
   Compactada para fit en viewport sin scroll (gap reducido).
   2026-05-19 (WO PREMIUM_DEPTH_B): gap baja a 1rem para acomodar la capa
   `.kz-live-system` entre .home-head y .home-orbs sin romper no-scroll. */
.stage-home {
  max-width: 880px;
  gap: 1rem;
  align-items: stretch;
  text-align: left;
  padding: 0.5rem 0;
}
/* Sub-fase 6A (2026-05-18): home hereda el padding lateral clamp del
   default. Antes tenía padding 1.5rem fijo lateral (rompía la
   consistencia con resto de páginas). Solo override vertical. */
body.page-home .stage-container { padding-top: 1.25rem; padding-bottom: 1rem; }

/* ============================================================
   hero lockup (enso + wordmark hero, juntos)
   El enso vuelve — pequeño-mediano, alineado con la altura del wordmark
   ============================================================ */

.hero-lockup {
  /* WO HOME BRAND MARK (2026-05-22): lockup vertical — texto "kaizen
     build os" arriba (pequeño) + enso/dot debajo. DOM no cambia (SVG
     primero, h1 después en index.html) para no remover el aria-hidden
     ni reordenar nodos; column-reverse hace el flip visual sin tocar
     el template. La identidad enso (medio círculo + dot) queda como
     ancla visual, el wordmark como label sutil arriba. */
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
}
.hero-enso {
  /* V3.1 PORT FIX (2026-05-18): enso authority — sube de 64-96 a 96-136
     para que el sensor lea como instrumento, no como logo. Mobile sigue
     dentro del clamp inferior 96px. */
  width: clamp(96px, 9vw, 136px);
  height: clamp(96px, 9vw, 136px);
  flex-shrink: 0;
  overflow: visible;
}

/* ============================================================
   wordmark hero (solo en home) — tipografía monocroma
   Tres pesos / dos tonos:
     bold + fg (kaizen, brand)
     light fantasma + fg-dim (build, conectivo)
     bold + fg-dim (os, sufijo silencioso)
   El verde NO es protagonista — se reserva para señales funcionales.
   En la home cockpit la atención va al SALUDO, no a la marca.
   ============================================================ */

.wordmark-hero {
  font-size: clamp(0.9375rem, 1.3vw, 1.125rem);
  font-weight: 400;
  letter-spacing: -0.045em;
  line-height: 0.92;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.18em;
  margin: 0;
}
.wordmark-hero .w-bold   { font-weight: 700; color: var(--fg); }
.wordmark-hero .w-light  { font-weight: 200; color: var(--fg-dim); letter-spacing: -0.025em; }
/* "os" como sufijo silencioso: medium en lugar de bold, color
   secundario. Bold con --fg-dim daba masa visual cercana a "kaizen"
   (bold + --fg) y rompía la jerarquía "kaizen domina la marca,
   build conecta, os cierra". Medium baja peso sin perder identidad.
   Verde se reserva para señales funcionales (focus, hover, submit). */
.wordmark-hero .w-accent { font-weight: 500; color: var(--fg-dim); }

/* Label tipo "ESTADO ACTUAL" pequeño en caps gray */
.section-label {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* Headline editorial (primera línea del estado actual = ### en markdown) */
.md-editorial h3 {
  font-size: clamp(1.375rem, 3.2vw, 2rem);
  font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--fg);
  line-height: 1.08;
  margin: 0.15rem 0 0.6rem;
}
.md-editorial p {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  line-height: 1.5;
  margin: 0 0 0.5rem;
  max-width: 60ch;
}
.md-editorial p:last-child { margin-bottom: 0; }
/* En home compactamos para fit sin scroll — solo mostramos los primeros 2 párrafos */
.home-estado .md-editorial p ~ p ~ p { display: none; }

/* ============================================================
   topbar (sticky global — todas las páginas excepto /login)
   ============================================================ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1rem;
  height: var(--topbar-h);
  /* Shell persistence (WO 2026-05-19): en `body.page-activity` el body
     pasa a `display: flex; flex-direction: column; height: 100vh`. Como
     flex item del body, el topbar por defecto tiene `flex-shrink: 1`,
     que en viewport corto (mobile 375x812) lo comprime ~13px para
     repartir espacio con activity-toolbar + activity-frame. Bloqueamos
     el shrink: la cabecera SIEMPRE mide --topbar-h, y el resto del
     viewport se reparte entre toolbar y frame. Aplica a TODOS los
     contextos, no solo activity (defensivo). */
  flex-shrink: 0;
  /* Sub-fase 4A (2026-05-18) + 5B: márgenes laterales coherentes con
     .stage-container. El logo se separa del borde izquierdo y el nav
     del derecho — coherente con linear.app donde el topbar respeta el
     mismo padding lateral del contenido. Subido a max 9rem (144px). */
  padding: 0 clamp(1.5rem, 10vw, 9rem);
  /* Refactor 2026-05-18 (inspiración Linear "calmer interface"):
     topbar atenuada — fondo --bg-2 (un par de tonos más oscuro que el body)
     para que la estructura se SIENTA sin VERSE. El contenido toma precedencia.
     El blur saturate se conserva: cuando algún elemento pasa por debajo del
     sticky, mantiene la sensación de profundidad. */
  background: color-mix(in srgb, var(--bg-2) 92%, transparent);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
}
@supports not (backdrop-filter: blur(1px)) {
  .topbar { background: var(--bg-2); }
}

.topbar-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--fg);
  text-decoration: none;
  transition: opacity var(--t-fast) var(--ease);
  margin-right: auto;
}
.topbar-brand:hover { opacity: 0.72; }
.topbar-mark {
  width: 20px;
  height: 20px;
  display: block;
}
.topbar-name {
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--fg);
}

/* Sub-fase 4B (2026-05-18): nav estilo Linear — sentence case en lugar
   de uppercase, tamaño más legible (text-sm), letter-spacing normal. La
   tipografía de las labels deja de gritar y se siente editorial. */
.topbar-nav {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;          /* 14px, igual orden que Linear nav */
  letter-spacing: -0.005em;     /* normal-tight, sin tracking forzado */
  text-transform: none;         /* quitar uppercase forzado */
  color: var(--fg-faint);
  font-weight: 500;             /* medium — coherente con Linear */
}
/* Sub-fase 3A (2026-05-18): hover pill estilo Linear.
   Antes: cambio de color de texto al hover. Ahora: pill con background
   sutil + border-radius (capsule shape). El texto sigue cambiando de
   color pero también gana un "halo" rectangular redondeado de fondo,
   como Linear hace en su nav. */
.topbar-link {
  color: var(--fg-faint);
  text-decoration: none;
  padding: 0.4rem 0.7rem;
  border-radius: 999px;       /* pill shape */
  transition:
    color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease);
}
.topbar-link:hover {
  color: var(--fg);
  background-color: color-mix(in srgb, var(--fg) 8%, transparent);
}
.topbar-link.is-active {
  color: var(--fg);
  background-color: color-mix(in srgb, var(--fg) 6%, transparent);
}
/* Underline lima del item activo conservado pero ajustado al pill */
.topbar-link.is-active::after {
  content: "";
  position: absolute;
  bottom: -4px;
  left: 50%;
  transform: translateX(-50%);
  width: 16px;
  height: 1px;
  background: var(--lime);
}
.topbar-link.is-active { position: relative; }

.topbar-user {
  color: var(--fg-dim);
  font-weight: 500;
}
.topbar-sep { color: var(--fg-faint); }

.topbar-logout-form { display: inline-flex; }
/* Sub-fase 3A: 'salir' también hereda el patrón pill del .topbar-link
   para coherencia visual con resto de items del topbar. */
.topbar-link-btn {
  color: var(--fg-faint);
  background: transparent;
  border: none;
  padding: 0.4rem 0.7rem;
  border-radius: 999px;
  font: inherit;
  font-size: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition:
    color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease);
}
.topbar-link-btn:hover {
  color: var(--fg);
  background-color: color-mix(in srgb, var(--fg) 8%, transparent);
}

/* Presence stack ahora vive dentro del topbar (NO flotante) */
.topbar .presence-stack {
  position: static;
  display: inline-flex;
  gap: 0.4rem;
  pointer-events: auto;
  margin-left: 0.35rem;
}
.topbar .presence-avatar {
  width: 26px;
  height: 26px;
  font-size: var(--text-xs);
}
.topbar .presence-pip {
  width: 10px;
  height: 10px;
  bottom: -1px;
  right: -1px;
}

@media (max-width: 520px) {
  .topbar { padding: 0 0.875rem; gap: 0.6rem; }
  .topbar-name { display: none; }  /* solo brand mini en móvil */
  .topbar-nav { font-size: var(--text-xs); gap: 0.3rem; }
  .topbar .presence-avatar { width: 24px; height: 24px; }
}

/* ============================================================
   enso + wordmark (la marca)
   ============================================================ */

.enso {
  width: 140px;
  height: 140px;
  display: block;
  overflow: visible;
}
.enso-sm { width: 120px; height: 120px; }

.ring {
  fill: none;
  stroke: var(--fg);
  stroke-width: 5;
  stroke-linecap: round;
}

.dot {
  fill: var(--accent);
  transform-box: view-box;
  transform-origin: 151.5px 36px;
  animation: dot-pulse var(--pulse-duration) ease-in-out infinite;
  transition: fill var(--t-slow) var(--ease), filter var(--t-slow) var(--ease);
  filter: drop-shadow(0 0 6px var(--glow));
}
.dot-static {
  fill: var(--fg-dim);
  animation: dot-pulse 2.4s ease-in-out infinite;
  transform-box: view-box;
  transform-origin: 151.5px 36px;
}

@keyframes dot-pulse {
  /* 2026-05-18 fix(home): heartbeat sutil — antes el dot iba 0.72→1.18 +
     opacity 0.5→1, que se leía como parpadeo, no como pulso vital.
     Ahora variación pequeña: scale ±6%, opacity 0.8→1 → respira vivo
     sin distraer. La transición pop-in (termina en scale 1, opacity 1)
     → dot-pulse start (scale 0.95, opacity 0.8) ya no produce salto
     visible. Se aplica a login (.dot, .dot-static), home (.hero-enso
     .dot) y a cualquier otra superficie que use dot-pulse. */
  0%, 100% { transform: scale(0.95); opacity: 0.8; }
  50%      { transform: scale(1.08); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .dot, .dot-static, .led { animation: none; opacity: 1; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* Wordmark */
.wordmark {
  font-size: var(--text-lg);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1;
  display: flex;
  gap: 0.55em;
  margin-top: -0.25rem;
}
.wordmark .thin {
  font-weight: 300;
  color: var(--fg-dim);
}

/* ============================================================
   greeting (home, primera vista por sesión)
   ============================================================ */

.greeting {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  letter-spacing: 0.01em;
  margin-top: -1rem;
  height: 1.4em;
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease);
  pointer-events: none;
}
.greeting.is-visible { opacity: 1; }
.greeting.is-fading  { opacity: 0; }

/* ============================================================
   meta (tabla de telemetría)
   ============================================================ */

.meta {
  width: 100%;
  border-top: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
  display: grid;
}
.row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  padding: 0.8125rem 0.25rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--text-sm);
}
.row:last-child { border-bottom: none; }
.row dt {
  text-align: left;
  color: var(--fg-dim);
  font-weight: 400;
  letter-spacing: 0.02em;
}
.row dd {
  text-align: right;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
}
.mono { font-family: var(--font-mono); font-size: var(--text-sm); }
.unit { color: var(--fg-dim); }

.led {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0.4rem var(--glow);
  animation: dot-pulse var(--pulse-duration) ease-in-out infinite;
  transform-origin: center;
  transition: background var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease);
}

/* ============================================================
   login form
   ============================================================ */

/* Stage del login: columna estrecha centrada (Linear signup ≈290px;
   subimos a 340 para acomodar 2 inputs + labels con respiración). */
.stage-login {
  max-width: 340px;
  gap: 1.25rem;
  align-items: stretch;
  text-align: left;
}

/* Marca arriba: contenedor centrado para el enso. */
.login-mark {
  display: flex;
  justify-content: center;
  margin-bottom: 0.25rem;
}
.login-mark .enso {
  /* Más pequeño que el hero de home (120px allí), pero con peso suficiente
     para anclar la columna en el login. Linear usa ≈80px de logo en
     /signup; nos alineamos para no quedarnos en "logo de aviso". */
  width: 80px;
  height: 80px;
  color: var(--fg);
}
.login-mark .enso .ring {
  stroke: currentColor;
}
.login-mark .enso .dot,
.login-mark .enso .dot-static {
  fill: currentColor;
}

/* Título central. Sentence case (Ibon), ~22px, medio peso. */
.login-title {
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 0 0 0.75rem;
  text-align: center;
}

/* Form: inputs respirados con pill-ish radius. */
.auth {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  text-align: left;
}
.field-label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--fg-dim);
  letter-spacing: 0;
  text-transform: none;
}

.auth input {
  width: 100%;
  padding: 0.75rem 0.95rem;
  /* Dark login: input bg = --bg-2 (más claro que el body --bg, da
     "tarjeta dentro de superficie"). Border --line-strong para que
     el campo no enfocado sea claramente perceptible (kaizen-ux-auditor
     marcó que `--line` tenue dejaba `contraseña` casi invisible sobre
     `--bg-2`). */
  background: var(--bg-2);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  font-size: 0.9375rem;
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  transition: border-color 160ms var(--ease),
              box-shadow 160ms var(--ease),
              background 160ms var(--ease);
  appearance: none;
  -webkit-appearance: none;
}
.auth input::placeholder {
  color: var(--fg-faint);
}
.auth input:hover {
  border-color: color-mix(in srgb, var(--fg) 35%, var(--line-strong));
}
.auth input:focus {
  outline: none;
  border-color: var(--fg-dim);
  background: color-mix(in srgb, var(--bg-2) 92%, var(--fg) 8%);
  /* Focus ring claro sobre dark: halo lima 22% como acento Kaizen. */
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lime) 22%, transparent);
}
.auth input:focus-visible {
  outline: none;
}
.auth input:-webkit-autofill {
  -webkit-text-fill-color: var(--fg);
  -webkit-box-shadow: 0 0 0px 1000px var(--bg-2) inset;
  transition: background-color 5000s ease-in-out 0s;
}

.auth-error {
  font-size: 0.8125rem;
  color: #FCA5A5;  /* red-300 sobre dark — accesible WCAG sobre #131316 */
  background: color-mix(in srgb, var(--err) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--err) 32%, transparent);
  border-radius: 8px;
  padding: 0.55rem 0.7rem;
  margin: 0;
  text-align: left;
}

/* Botón primario "entrar": pill premium en dark.
   Fill blanco-marfil --fg sobre fondo dark = máximo contraste,
   no gritón (no usamos lime — lime se reserva para señales
   urgentes per PRODUCT_INVARIANTS). Texto --bg (oscuro). */
.auth-submit {
  width: 100%;
  padding: 0.85rem 1rem;
  margin-top: 0.5rem;
  background: var(--fg);
  color: var(--bg);
  border: 1px solid var(--fg);
  border-radius: 999px;
  font-size: 0.9375rem;
  font-family: var(--font-sans);
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
  transition: opacity 140ms var(--ease),
              transform 140ms var(--ease),
              box-shadow 140ms var(--ease);
  box-shadow:
    0 1px 2px rgb(0 0 0 / 0.40),
    0 0 0 1px color-mix(in srgb, var(--fg) 8%, transparent);
}
.auth-submit:hover  {
  transform: translateY(-1px);
  box-shadow:
    0 4px 12px rgb(0 0 0 / 0.50),
    0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent);
}
.auth-submit:active { transform: scale(0.985) translateY(0); }
.auth-submit:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 3px;
}

/* Footer mínimo bajo el form: marca tipográfica, sin link. */
.login-foot {
  margin: 1rem 0 0;
  font-size: 0.75rem;
  color: var(--fg-faint);
  letter-spacing: 0.02em;
  text-align: center;
}

/* ============================================================
   presencia (avatar stack — vive dentro del topbar)
   ============================================================ */

.presence-stack {
  display: inline-flex;
  gap: 0.4rem;
}

.presence-avatar {
  position: relative;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--avatar-bg, var(--line-strong));
  color: #FFFFFF;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  cursor: default;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-slow) var(--ease);
  user-select: none;
}
.presence-avatar:hover { transform: scale(1.08); }
.presence-avatar[data-online="0"] { opacity: 0.55; }

.presence-pip {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.presence-pip::after {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--fg-faint);
  box-sizing: border-box;
  transition: background var(--t-slow) var(--ease), border-color var(--t-slow) var(--ease);
}
.presence-avatar[data-online="1"] .presence-pip::after {
  background: var(--fg);
  border-color: var(--fg);
}

/* ============================================================
   página /actividad — app-shell layout (Gmail/Slack/Linear style)
   Body ocupa 100vh sin scroll. Topbar + toolbar fijos arriba.
   El resto es un frame con 2 paneles que scrollean independiente.
   ============================================================ */

body.page-activity {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
  padding: 0;
}

/* Shell persistence (WO 2026-05-19): el wrapper `.stage-container` se añadió
   a actividad.html para que la estructura sea idéntica a /home y /tablero
   (sino, al swappar de /home → /actividad el wrapper del body.page-home
   persistía en DOM con padding/grid de home, dejando un dead-band ~230px
   entre topbar y toolbar). En activity reseteamos a `display: contents`
   para que el contenedor desaparezca del layout y main quede como flex
   item directo del body.page-activity (app-shell intacto). */
body.page-activity > .stage-container {
  display: contents;
}

/* Shell persistence (WO 2026-05-19): /actividad ahora envuelve toolbar+frame
   en <main id="kaizen-main" class="stage-activity"> para que htmx swappee
   sólo ese contenedor entre navegaciones. El main hereda el flex column del
   body.page-activity: ocupa el espacio bajo el topbar, hace de container
   para toolbar (flex-shrink:0) + frame (flex:1 1 auto) sin alterar el
   app-shell layout. min-height:0 evita que el frame interno se desborde
   cuando el feed crece. */
body.page-activity #kaizen-main.stage-activity {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
}

/* Toolbar de filtros: fijo bajo el topbar, ancho consistente */
/* Sub-fase 6A (2026-05-18): padding lateral consistente con .stage-container.
   2026-05-19: subido `padding-top` de 0.85rem a 1.25rem para igualar la
   "respiración" arriba que tienen body.page-home .stage-container y
   body.page-kanban — antes la toolbar se pegaba al topbar y daba la
   percepción de cabecera comprimida en /actividad. Ahora el aire
   topbar→contenido es coherente entre páginas. min-height subido de
   56 a 64 para mantener guarda mínima coherente con el padding total
   (1.25 + ~text + 0.85 ≈ 64) — defensivo por si la toolbar quedara
   sin pills (no debería pasar en estado normal). */
.activity-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1.25rem clamp(1.5rem, 10vw, 9rem) 0.85rem;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  min-height: 64px;
  box-sizing: border-box;
}

/* Frame que ocupa el espacio restante del viewport */
.activity-frame {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  padding: 0 clamp(1.5rem, 10vw, 9rem);
}

/* Empty state cuando no hay events: centrado vertical en el frame */
.feed-empty-container {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}

/* ============================================================
   feed master-detail (lista izquierda + panel derecho)
   Ambos paneles ocupan toda la altura del frame con scroll propio.
   ============================================================ */

.feed-layout {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(300px, 460px) 1fr;
  gap: 0;
  height: 100%;
}

.feed-master {
  min-width: 0;
  height: 100%;
  overflow-y: auto;
  scrollbar-gutter: stable;       /* ancho consistente con/sin scrollbar */
  padding: 1.25rem 1.5rem 2rem;
  border-right: 1px solid var(--line);
}

.feed-detail {
  height: 100%;
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding: 1.75rem 2rem 2rem;
  min-width: 0;
  background: color-mix(in srgb, var(--bg) 98%, transparent);
}

.feed-detail-back {
  display: none;  /* Solo visible en móvil */
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--fg-dim);
  padding: 0.4rem 0.75rem;
  border-radius: 999px;
  font: inherit;
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  cursor: pointer;
  margin-bottom: 1rem;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.feed-detail-back:hover { color: var(--fg); border-color: var(--fg-dim); }

.feed-detail-empty {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--fg-dim);
}
.feed-detail-empty-text {
  font-size: var(--text-md);
  color: var(--fg-dim);
  margin: 0 0 0.4rem;
  letter-spacing: -0.01em;
}
.feed-detail-empty-sub {
  font-size: var(--text-xs);
  color: var(--fg-faint);
  margin: 0;
  letter-spacing: 0.04em;
}

/* Animación de entrada al cambiar selección (spring) */
.feed-detail-content.is-animating {
  animation: detail-in 380ms var(--ease-spring) backwards;
}
@keyframes detail-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.feed-detail-head {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  margin-bottom: 1.25rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.feed-detail-avatar {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--avatar-bg, var(--line-strong));
  color: #FFFFFF;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-base);
  font-weight: 600;
  text-transform: uppercase;
}
.feed-detail-head-text { min-width: 0; flex: 1; }
.feed-detail-title {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--fg);
  margin: 0 0 0.4rem;
  letter-spacing: -0.015em;
  line-height: 1.3;
  word-break: break-word;
}
.feed-detail-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--text-xs);
  color: var(--fg-dim);
  letter-spacing: 0.02em;
}
.feed-detail-meta-extra {
  margin-left: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--fg-faint);
}
.feed-detail-meta-extra:not(:empty)::before {
  content: "·";
  margin-right: 0.4rem;
  color: var(--fg-faint);
}
.feed-detail-sep { color: var(--fg-faint); }

/* Cuerpo markdown del detalle */
.feed-detail-body {
  font-size: var(--text-sm);
  color: var(--fg);
  line-height: 1.6;
}
.feed-detail-body p          { margin: 0 0 0.85rem; }
.feed-detail-body p:last-child { margin-bottom: 0; }
.feed-detail-body strong     { color: var(--fg); font-weight: 600; }
.feed-detail-body em         { color: var(--fg); font-style: italic; }
.feed-detail-body a {
  color: var(--fg);
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
}
.feed-detail-body a:hover { border-color: var(--lime); }
.feed-detail-body ul,
.feed-detail-body ol {
  padding-left: 1.4rem;
  margin: 0 0 0.85rem;
}
.feed-detail-body li { margin: 0.2rem 0; }
.feed-detail-body code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  padding: 0.08em 0.4em;
  border-radius: 3px;
}
.feed-detail-body pre {
  margin: 0 0 0.85rem;
  padding: 0.75rem 1rem;
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border-radius: 4px;
  overflow-x: auto;
}
.feed-detail-body pre code {
  background: transparent;
  padding: 0;
  font-size: 0.78rem;
  line-height: 1.5;
}

/* Feed-item con detalle: indicador visual (>) + cursor pointer */
.feed-item {
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
  border-radius: 4px;
}
.feed-item:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); }
.feed-item {
  position: relative;
}
.feed-item.is-selected {
  background: color-mix(in srgb, var(--lime) 7%, transparent);
}
.feed-item.is-selected::after {
  /* Barra lateral lime con animación spring de entrada */
  content: "";
  position: absolute;
  left: 0;
  top: 8%;
  bottom: 8%;
  width: 2.5px;
  background: var(--lime);
  border-radius: 999px;
  animation: select-bar-in 360ms var(--ease-spring) backwards;
}
@keyframes select-bar-in {
  from { transform: scaleY(0.3); opacity: 0; }
  to   { transform: scaleY(1);   opacity: 1; }
}
.feed-item:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: -2px;
}
.feed-item.has-detail::before {
  content: "▸";
  position: absolute;
  margin-left: -0.95rem;
  margin-top: 0.15rem;
  font-size: 0.6rem;
  color: var(--fg-faint);
}
.feed-item.has-detail { position: relative; }

/* Responsive: en móvil, lista y detalle se intercambian (no coexisten) */
@media (max-width: 900px) {
  .feed-layout {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .feed-master {
    border-right: none;
  }
  .feed-layout:not(.has-selection) .feed-detail { display: none; }
  .feed-layout.has-selection .feed-master { display: none; }
  .feed-layout.has-selection .feed-detail-back { display: inline-block; }
  .activity-toolbar { padding: 0.75rem 1rem; }
}

/* Feed compacto: grid 3 columnas (avatar | texto | hora). Una línea por evento. */
.feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.feed-item {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: start;             /* hora siempre en primera línea, no centrada */
  gap: 0.7rem;
  padding: 0.55rem 0.25rem;
  border-bottom: 1px solid var(--line);
}
.feed-item .avatar { align-self: start; margin-top: 0.05rem; }
.feed-item .feed-time { align-self: start; margin-top: 0.2rem; }
.avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--avatar-bg, var(--line-strong));
  color: #FFFFFF;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0;
}
.feed-text {
  font-size: var(--text-sm);
  color: var(--fg);
  margin: 0;
  line-height: 1.35;
  word-break: break-word;
}
.feed-time {
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.03em;
  text-transform: lowercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Contador de eventos agrupados (×N) */
.feed-count {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  font-size: 0.66rem;
  color: var(--fg-dim);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

/* empty states (con voz) */
.feed-empty {
  padding: 3.5rem 0;
  text-align: center;
  color: var(--fg-dim);
  font-size: var(--text-sm);
  font-style: italic;
  letter-spacing: 0.01em;
}
.feed-empty .feed-empty-sub {
  display: block;
  margin-top: 0.5rem;
  font-style: normal;
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.05em;
}

/* ============================================================
   home: estado actual + proyectos (versión editorial)
   ============================================================ */

.home-estado, .home-projects {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  text-align: left;
}
.home-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}
.home-section-link {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-faint);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease);
}
.home-section-link:hover { color: var(--fg); }

/* ============================================================
   módulos circulares (home) — launchpad con hover vanguardista
   ============================================================ */

.home-modules {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  text-align: left;
}

.home-estado, .home-projects {
  gap: 0.4rem;
}

.module-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 3vw, 2rem);
}

.module-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
}

/* Disco principal — círculo con inicial */
.module-disc {
  position: relative;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: var(--disc-bg, var(--line-strong));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  text-decoration: none;
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-transform: lowercase;
  overflow: visible;
  cursor: pointer;
  isolation: isolate;
  /* Sombra base sutil */
  box-shadow:
    0 1px 2px rgb(0 0 0 / 0.10),
    0 0 0 0 transparent;
  transition:
    transform   320ms var(--ease-spring),
    box-shadow  320ms var(--ease);
}

/* Sub-fase 5A (2026-05-18): hover de orbes calmado, Linear-style.
   Antes: halo lime + ring lime grosero que daba feel "cyberpunk".
   Ahora: halo gris muy sutil + ring blanco translúcido. Identidad visual
   sin grito cromático. El lime se reserva para el enso y para señales
   urgentes en el signal panel. */
.module-disc::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--fg) 14%, transparent) 0%,
    transparent 65%);
  opacity: 0;
  transition: opacity 280ms var(--ease);
  z-index: -1;
  pointer-events: none;
}

.module-disc::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1px solid transparent;
  transition: border-color 280ms var(--ease), transform 360ms var(--ease-spring);
  pointer-events: none;
}

.module-initials {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.15);
  z-index: 1;
}

/* Imagen personalizada del orbe (sustituye iniciales si el backend
   resolvió image_url). La imagen se clipea al círculo via border-radius
   sin tocar el overflow del disco — así los pseudo-elementos ::before
   (halo lime hover) y ::after (ring hover) siguen pudiendo extenderse
   fuera. La imagen va en z-index 0 para quedar DEBAJO del .module-pulse
   (dot blanco identidad, z-index implícito mayor). */
.module-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  z-index: 0;
  /* Sin transition: la imagen es identidad, no microinteracción. */
}
/* Orbe con imagen subida — WO 2026-05-19 (5ª iter): approach híbrido
   robusto contra LOS DOS edge cases (logo blanco sobre transparente
   y logo oscuro sobre fondo blanco).

   Iteraciones previas: soft-light = demasiado sutil. multiply puro =
   logos blancos invisibles. luminosity puro = disco negro con
   imágenes con áreas transparentes (Chrome trata RGB(0,0,0) de
   pixel transparente como contribución de luminancia 0 al blend,
   ennegreciendo el resultado).

   Approach final — 3 capas independientes que NO se eliminan
   mutuamente:
     1. `background: var(--disc-bg)`: disc entero pinta del color.
     2. `.module-image`: pintada sobre el color, SIN mix-blend-mode.
        Las áreas transparentes dejan ver el color directamente; el
        logo opaco (blanco o oscuro) se muestra encima sin distorsión.
     3. `.module-tint`: overlay del color con `mix-blend-mode: multiply`
        @ opacity 0.55, ENCIMA de la imagen.
        - Áreas blancas del logo: white × color = color → se tintan
          al color elegido (logo blanco no desaparece, se vuelve
          una versión más clara del color).
        - Áreas oscuras del logo: dark × color = oscuro → preservado
          legible.
        - Áreas color (lo que ya era color, donde imagen es
          transparente): color × color = color más oscuro → la
          esfera entera mantiene el carácter cromático.
        Opacity 0.55 mantiene tanto el original como el tinte
        balanceados.
   Resultado neto: TODO el disco tiene el color, el logo (blanco u
   oscuro) sigue legible. */
.module-disc.has-image {
  /* WO BUGFIX IMAGEN SHELL (2026-05-22, 2ª iter): el `background:
     transparent` del fix anterior hacía DESAPARECER el cuerpo del
     orbe cuando la imagen era pequeña, transparente o con áreas
     vacías — el orbe quedaba como puntito. Ahora shell neutra
     visible (var(--line-strong)), igual que un orbe sin imagen +
     sin color. La imagen sigue mandando ENCIMA del shell. El color
     elegido NO rellena el disco (ignoramos --disc-bg en background),
     solo alimenta glow exterior via orb-breath box-shadow que sí
     lee --disc-bg. */
  background: var(--line-strong);
  box-shadow:
    /* Ring fino para definir borde del disc — micro-detalle. */
    0 0 0 1px color-mix(in srgb, var(--fg) 10%, transparent),
    0 1px 2px rgb(0 0 0 / 0.10);
}
.module-disc.has-image .module-image {
  /* SIN mix-blend-mode: la imagen va literal sobre el disc bg.
     Áreas transparentes dejan pasar el color de fondo. */
  z-index: 0;
}

/* Tinte sobre imagen (5ª iter): vuelve a estar VIVO. Multiply
   overlay del color encima de la imagen para teñir tanto las áreas
   blancas del logo (white × color = color, evita logo invisible)
   como las áreas con color de fondo (color × color = color más
   oscuro, refuerza identidad cromática). */
/* WO BUGFIX IMAGEN-MANDA (2026-05-22): el tinte multiply 0.55 que
   antes "bañaba" la imagen con el color elegido queda completamente
   suprimido. La imagen ya no se reviste. El color solo aporta glow
   exterior via orb-breath. .module-tint queda en DOM por compat con
   applyOrbCellUpdate (no rompe nada), pero invisible. */
.module-tint,
.module-disc.has-image .module-tint {
  display: none;
}

/* Cuando hay imagen, el dot blanco del pulse necesita garantía de
   legibilidad sobre cualquier color de imagen subida (saturada, oscura
   o clara). Subimos opacidad y reforzamos el contorno oscuro para que
   el dot mantenga identidad visual sin importar el fondo. */
.module-disc.has-image .module-pulse {
  background: rgb(255 255 255 / 0.95);
  box-shadow:
    0 0 0 1px rgb(0 0 0 / 0.25),
    0 0 6px rgb(0 0 0 / 0.4);
}

/* Dot pulsante interno (identidad enso) — visible siempre, sutil */
.module-pulse {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.9);
  box-shadow: 0 0 6px rgb(255 255 255 / 0.4);
  animation: module-pulse 2.4s ease-in-out infinite;
}
@keyframes module-pulse {
  0%, 100% { opacity: 0.5; transform: scale(0.7); }
  50%      { opacity: 1;   transform: scale(1.15); }
}

/* Sub-fase 5A (2026-05-18): hover del orbe — Linear-style.
   Refactor del antiguo hover lime ("cyberpunk verdoso") por un patrón
   más editorial: scale + lift sutil + box-shadow neutra + ring blanco
   translúcido (no lime). El verde queda solo en señales urgentes y en
   el enso (identidad). */
.module-cell:hover .module-disc {
  transform: scale(1.06) translateY(-5px);
  box-shadow:
    0 22px 48px -14px rgb(0 0 0 / 0.55),
    0 6px 16px -6px rgb(0 0 0 / 0.35),
    0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent);
}
.module-cell:hover .module-disc::before { opacity: 1; }
.module-cell:hover .module-disc::after  {
  border-color: color-mix(in srgb, var(--fg) 28%, transparent);
  transform: scale(1.05);
}

.module-disc:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--fg) 40%, transparent);
  outline-offset: 6px;
}

.module-label {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-faint);
  transition: color 200ms var(--ease);
  font-weight: 500;
}
.module-cell:hover .module-label { color: var(--fg); }

/* HOME_IMPACT_SYSTEM_V1.5 — orbe quieto.
   Threshold: >=7 días sin event en audit con board_slug.
   Variable única para no inventar alfabeto privado: opacidad del
   disco entero (no del dot del pulse) + pulse estático. Identidad
   enso/dot preservada — el dot sigue ahí, solo no late. Hover
   recupera contraste completo: el orbe sigue siendo navegable. */
.module-cell.is-dormant .module-disc {
  opacity: 0.55;
  transition: opacity var(--t-base) var(--ease),
              transform 320ms var(--ease-spring),
              box-shadow 320ms var(--ease);
}
.module-cell.is-dormant:hover .module-disc {
  opacity: 1;
}
.module-cell.is-dormant .module-pulse {
  animation: none;
  opacity: 0.35;
}
.module-cell.is-dormant .module-label {
  color: var(--fg-faint);
}

/* Señal de estado real del proyecto (desde la bitácora). Microtexto de
   UNA línea bajo el nombre: comunica «dónde estamos» sin abrir tooltip y
   sin convertir el orbe en una tarjeta pesada. Altura mínima fija para
   que la rejilla no salte entre orbes con/ sin estado. */
.module-state {
  max-width: 9.5rem;
  min-height: 1.05em;
  margin-top: -0.35rem;
  font-size: 0.625rem;
  line-height: 1.05;
  letter-spacing: 0.01em;
  text-align: center;
  color: var(--fg-soft, var(--fg-faint));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Fallback honesto: sin fuente real → se lee como «aún no hay estado»,
   no como contenido. Más tenue y en cursiva, deliberadamente discreto. */
.module-state[data-state-source="none"] {
  color: var(--fg-faint);
  font-style: italic;
  opacity: 0.7;
}

/* Tooltip flotante — emerge ARRIBA del disco al hover (no debajo, para no
   forzar scroll si el módulo está cerca del bottom del viewport) */
.module-tooltip {
  position: absolute;
  bottom: calc(100% + 0.75rem);
  left: 50%;
  width: clamp(220px, 28vw, 280px);
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  border: 1px solid var(--line-strong);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  box-shadow:
    0 18px 48px -12px rgb(0 0 0 / 0.4),
    0 4px 12px -4px rgb(0 0 0 / 0.2);
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(8px) scale(0.94);
  transform-origin: bottom center;
  transition:
    opacity     220ms var(--ease),
    transform   320ms var(--ease-spring),
    visibility 0s linear 220ms;
  /* Tooltip interactivo: el cursor debe poder entrar (clic en
     ABRIR TABLERO, hover sobre lápiz de editar). Antes era
     pointer-events:none → la tarjeta era informativa y NO se podía
     accionar. Ver hover-bridge `::before` más abajo para que el cursor
     viaje del disco al tooltip sin perder el estado :hover. */
  pointer-events: auto;
  z-index: 20;
}
/* Hover bridge: el gap entre disco y tooltip (0.75rem) es zona muerta
   donde el cursor pierde :hover si pasa muy lento. Este pseudo-elemento
   transparente cubre ese gap como hit area, de modo que el cursor
   "viaja por el puente" y la tarjeta sigue abierta. Hereda :hover del
   ancestor .module-cell.

   WO KAIZEN HOME ORBS (2026-05-22, crit fix [BAJO]): tras esta WO el
   tooltip en home vuelve a emerger ARRIBA del disco (per WO MICRO-LAYOUT
   + WO ORBS). El bridge en `bottom: -0.85rem` cubre exactamente la zona
   correcta — entre el borde inferior del tooltip y el borde superior del
   disco. No requiere override scoped a .home-orbs. */
.module-tooltip::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.85rem;
  height: 0.85rem;
}
.module-tooltip::after {
  /* Triángulo apuntando hacia ABAJO (al disco) */
  content: "";
  position: absolute;
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  width: 10px;
  height: 10px;
  background: inherit;
  border-right: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
}
.module-cell:hover .module-tooltip,
.module-cell:focus-within .module-tooltip {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0) scale(1);
  transition-delay: 0s;
}

.module-tooltip-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.01em;
  /* Reserva espacio para el lápiz top-right (WO #1 Fase 2) — evita que
     nombres largos se metan debajo del icono. */
  padding-right: 2rem;
}
.module-tooltip-rule {
  height: 1.5px;
  width: 28px;
  /* WO BUGFIX NO-COLOR (2026-05-22): fallback neutro (no --lime) cuando
     no hay color elegido — coherente con el principio "no color = neutro,
     no verde". --rule-bg solo se setea cuando el proyecto tiene color
     override real. */
  background: var(--rule-bg, color-mix(in srgb, var(--fg) 18%, transparent));
  margin: 0.5rem 0 0.6rem;
  border-radius: 999px;
}
.module-tooltip-desc {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  line-height: 1.5;
  margin: 0 0 0.7rem;
}
/* El footer del tooltip puede ser <div> informativo o <a class="...-action">
   clicable (parche WO #1 fase 1 — abre el kanban). Estilo base compartido;
   la variante `.module-tooltip-action` añade hover + cursor pointer. */
.module-tooltip-foot {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
/* Variante clicable del footer (parche 2026-05-18): es un <a> que abre el
   kanban. Estilo Linear: padding leve, radius pequeño, fondo hover discreto.
   No grita; sigue siendo footer del tooltip. */
a.module-tooltip-action {
  text-decoration: none;
  padding: 0.32rem 0.42rem;
  /* Margin horizontal contenido — sin extender el hit area fuera del
     tooltip por abajo (evita atrapar clicks de salida hacia el
     triángulo `::after`). */
  margin: 0.45rem -0.42rem 0;
  border-radius: 6px;
  cursor: pointer;
  transition: background 180ms var(--ease), color 180ms var(--ease);
}
a.module-tooltip-action:hover,
a.module-tooltip-action:focus-visible {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
  outline: none;
}
a.module-tooltip-action:focus-visible {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--lime) 70%, transparent);
}
.module-tooltip-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 6px var(--lime-soft);
  animation: module-pulse 1.8s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .module-pulse, .module-tooltip-dot { animation: none; opacity: 1; }
  .module-disc { transition: box-shadow 160ms ease; }
  .module-cell:hover .module-disc { transform: none; }
}

@media (max-width: 520px) {
  .module-disc { width: 76px; height: 76px; font-size: 1.125rem; }
  .module-pulse { top: 10px; right: 10px; }
  .module-tooltip { width: 200px; top: calc(76px + 0.7rem + 1.5rem); }
  /* WO HOME ORB POPUP POLISH (2026-05-22): en mobile el popup emerge
     DEBAJO del orbe para no competir con el hero (enso + wordmark +
     greeting). En desktop sigue arriba — solo cambia mobile. Fondo
     más opaco (95%) que en desktop para ganar contraste sobre el bg
     oscuro sin perder identidad dark-glass. Arrow y bridge se
     reposicionan para apuntar HACIA ARRIBA (al orbe). */
  .home-orbs .module-tooltip {
    top: calc(100% + 0.75rem);
    bottom: auto;
    width: clamp(220px, 90vw, 260px);
    background: color-mix(in srgb, var(--bg) 95%, transparent);
    transform-origin: top center;
    transform: translateX(-50%) translateY(-8px) scale(0.94);
  }
  .home-orbs .module-cell:hover .module-tooltip,
  .home-orbs .module-cell:focus-within .module-tooltip {
    transform: translateX(-50%) translateY(0) scale(1);
  }
  /* Bridge: cubre el gap entre orb-bottom y tooltip-top */
  .home-orbs .module-tooltip::before {
    bottom: auto;
    top: -0.85rem;
  }
  /* Flecha al borde SUPERIOR del tooltip, apuntando hacia el orbe */
  .home-orbs .module-tooltip::after {
    top: -5px;
    bottom: auto;
    border: 0;
    border-left: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
    background: color-mix(in srgb, var(--bg) 95%, transparent);
  }
}

/* WO HOTFIX MOBILE TOUCH POPUP (2026-05-22): en dispositivos sin hover
   real (touch/mobile, @media (hover: none)) el popup del orbe queda
   suprimido para no tapar la fila 2 cuando :hover se simula sticky en
   touchstart o el tap dispara :focus-within. Scoped a .home-orbs para
   no afectar otras pantallas (ej. /organizacion). El link <a href> del
   orbe sigue navegando al tablero como antes. Va DESPUÉS del @media
   (max-width: 520px) anterior para ganar en source-order ante misma
   especificidad. */
@media (hover: none) {
  .home-orbs .module-cell:hover .module-tooltip,
  .home-orbs .module-cell:focus-within .module-tooltip {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateX(-50%) translateY(-8px) scale(0.94);
  }
}

/* ============================================================
   Home cockpit V1 — cabecera centrada + orbes de proyecto.
   Reutiliza el sistema .module-* para los orbes (sin sistema
   paralelo). Solo añade: cabecera centrada, línea de contexto,
   afford. "nuevo proyecto", empty state y meta del tooltip.
   ============================================================ */

.home-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.4rem;
  width: 100%;
}
.home-head .hero-lockup { justify-content: center; }
.home-head .wordmark-hero { justify-content: center; }
.home-head .greeting { margin: 0.2rem 0 0; }

.home-context {
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  margin: 0;
}
/* HOME_IMPACT_SYSTEM_V1.5 — línea editorial con criterio.
   La línea sustituye al contexto fijo "tus proyectos · mayo 2026"
   cuando hay datos reales. Cuando kind=dormant lleva además un link
   inline "Retomar →" que abre el board del proyecto quieto. */
.home-editorial-line {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.4em;
  /* Cuando hay criterio (dormant) elevar levemente el contraste para
     que la frase se LEA como información, no como subtítulo decorativo.
     El resto de casos (narrativa, fallback) hereda fg-faint del padre. */
}
/* Información con dato real (proyecto nombrado o conteo concreto) sube
   un tier de contraste para que el dato se LEA, no se diluya. Solo
   no_activity/empty se quedan en fg-faint heredado (señal baja). */
.home-editorial-line[data-editorial-kind="dormant"] .home-editorial-text,
.home-editorial-line[data-editorial-kind="activity_today"] .home-editorial-text,
.home-editorial-line[data-editorial-kind="all_active_week"] .home-editorial-text {
  color: var(--fg-dim);
}
.home-editorial-link {
  font-size: inherit;
  letter-spacing: inherit;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition:
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
}
.home-editorial-link:hover {
  color: var(--lime);
  border-color: var(--lime);
}
.home-editorial-link:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 3px;
  border-color: transparent;
}

.home-orbs {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
}
.home-orbs .module-grid {
  justify-content: center;
}

/* En la home la cabecera (enso + wordmark, identidad protegida) está
   centrada justo ENCIMA de la fila de orbes. El tooltip global emerge
   hacia ARRIBA y taparía el enso/"kaizen". Fix SCOPED a .home-orbs:
   el tooltip emerge hacia ABAJO (lejos de la marca), nunca la solapa.
   Vale para CUALQUIER orbe de la fila, no es un hack para uno solo. No
   cambia el sistema .module-* global ni /organizacion; no desplaza
   layout (no-scroll intacto). Fondo opaco + sombra fuerte se mantienen
   para legibilidad. */
/* WO KAIZEN HOME ORBS (2026-05-22): tras MICRO-LAYOUT el enso es marca
   discreta arriba y deja espacio sobrado por encima de los orbes (que
   ahora viven en el centro vertical). El popup vuelve a emerger ARRIBA
   del orbe (per WO), con estética dark-glass más densa: backdrop blur
   reforzado, sombra más larga y borde más sutil. */
.home-orbs .module-tooltip {
  /* posición arriba del disco — hereda el `bottom: calc(100% + 0.75rem)` base */
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-color: color-mix(in srgb, var(--fg) 14%, transparent);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  box-shadow:
    0 24px 60px -12px rgb(0 0 0 / 0.55),
    0 6px 16px -4px rgb(0 0 0 / 0.35);
  z-index: 40;
}
/* Flecha hereda la base (apunta abajo, hacia el disco). Pintar el fondo
   semi-translúcido para que case con el glass del tooltip. */
.home-orbs .module-tooltip::after {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  border-color: color-mix(in srgb, var(--fg) 14%, transparent);
}

/* Meta honesta del tooltip (creador · fecha). Solo se renderiza si el
   dato existe; nunca telemetría inventada. */
.module-tooltip-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  margin: 0 0 0.6rem;
}
/* WO KAIZEN HOME ORBS (2026-05-22): fallbacks honestos cuando no hay
   estado / actividad. Cursiva + opacidad reducida los diferencia de
   datos reales sin gritarlo. No se inventa nada. */
.module-tooltip-desc-empty,
.module-tooltip-meta-empty {
  font-style: italic;
  opacity: 0.7;
}

/* WO KAIZEN HOME ORBS (2026-05-22, crit fix [ALTO]+[MEDIO]): respiración
   muy sutil del orbe.
   - Glow exterior: 10px de blur sin spread (antes 18px+2px), color a 14%
     del bg del orbe (antes 22%). Bajada de amplitud tras feedback del
     critic — "subtle" como métrica no como esperanza.
   - Cadencia lenta (5.4s ease-in-out).
   - `animation-delay: 2.8s` para arrancar DESPUÉS del stagger-in cinematic
     V1 (que termina ~2.7s tras load). Así no hay desfase entre orbes en
     frames invisibles: todos los breaths arrancan coordinados cuando el
     orbe ya es visible.
   - Aplica SOLO a orbes de proyectos activos en home (no .is-dormant,
     no .orb-create-cell).
   - Identidad y color originales intactos: la animación afecta solo a
     box-shadow. No escala, no rota, no tinta el orbe.
   - Reduced-motion la neutraliza completamente (cell + disc). */
@keyframes orb-breath {
  0%, 100% {
    box-shadow:
      0 1px 2px rgb(0 0 0 / 0.10),
      0 0 0 0 transparent;
  }
  50% {
    box-shadow:
      0 1px 2px rgb(0 0 0 / 0.10),
      0 0 10px 0 color-mix(in srgb, var(--disc-bg, var(--fg)) 14%, transparent);
  }
}
.home-orbs .module-cell:not(.is-dormant):not(.orb-create-cell) .module-disc {
  animation: orb-breath 5.4s ease-in-out 2.8s infinite;
}
/* En hover anulamos la animación (no solo la pausamos): así la box-shadow
   del frame queda fuera de la cascada y el shadow del hover lift se
   aplica limpio (crit fix [MEDIO]). El `transition: box-shadow` en
   .module-disc (línea ~1396) garantiza una transición suave de glow
   pulsado → shadow de hover sin saltos visibles. */
.home-orbs .module-cell:not(.is-dormant):not(.orb-create-cell):hover .module-disc {
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  /* Cubre tanto .module-disc (orb-breath) como .module-cell
     (home-orb-rise) en home — el cinematic V1 ya tiene su propia
     guarda en otra regla, pero reforzamos aquí para no depender de
     orden de cascade. */
  .home-orbs .module-cell,
  .home-orbs .module-cell .module-disc { animation: none !important; }
}

/* Disco "+ nuevo proyecto": mismo tamaño que un orbe, contorno punteado */
.orb-create-disc {
  position: relative;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px dashed var(--line-strong);
  color: var(--fg-faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 220ms var(--ease), color 220ms var(--ease),
              transform 320ms var(--ease-spring);
}
.orb-create-plus {
  font-size: 1.75rem;
  font-weight: 300;
  line-height: 1;
}
/* Sub-fase 5A (2026-05-18): hover del "+ nuevo proyecto" sin lime grueso.
   Border pasa de dashed-tenue a solid blanco-translúcido. Calmar el feel
   "cyberpunk" del verde de bloque, mantener la sensación de invitación. */
.orb-create-cell:hover .orb-create-disc,
.orb-create-disc:focus-visible {
  border-style: solid;
  border-color: color-mix(in srgb, var(--fg) 36%, transparent);
  color: var(--fg);
  transform: scale(1.05);
}
.orb-create-disc:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 6px;
}

.orb-create-form {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.6rem;
}
.orb-create-form[hidden] { display: none; }
.orb-create-input {
  font: inherit;
  font-size: var(--text-sm);
  padding: 0.4rem 0.6rem;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  width: 12rem;
  max-width: 60vw;
}
.orb-create-input:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}
.orb-create-submit {
  font: inherit;
  font-size: var(--text-sm);
  padding: 0.4rem 0.9rem;
  background: var(--lime);
  color: #0a0a0a;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.orb-create-error {
  font-size: var(--text-xs);
  color: var(--err);
  margin-top: 0.5rem;
}
.orb-create-error[hidden] { display: none; }

.home-orbs-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 1.5rem 0;
  text-align: center;
}
.home-orbs-empty-line {
  font-size: clamp(1.125rem, 2.6vw, 1.5rem);
  font-weight: 500;
  color: var(--fg);
  margin: 0;
}
.home-orbs-empty-sub {
  font-size: var(--text-sm);
  color: var(--fg-faint);
  margin: 0 0 0.6rem;
}

@media (prefers-reduced-motion: reduce) {
  .orb-create-disc { transition: border-color 160ms ease, color 160ms ease; }
  .orb-create-cell:hover .orb-create-disc { transform: none; }
}

/* ============================================================
   home-pulse — banda viva con status real de /status.
   Discreta, lowercase, sin hashes ni latency crudos.
   ============================================================ */

.home-pulse {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: -0.5rem;
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.02em;
  opacity: 0;
  transition: opacity 260ms ease;
}
.home-pulse[hidden] { display: none; }
.home-pulse.is-revealed { opacity: 1; }
.home-pulse-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  flex-shrink: 0;
  transition: background 220ms ease;
}
html[data-state="ok"]       .home-pulse-dot { background: var(--lime); }
html[data-state="degraded"] .home-pulse-dot { background: var(--warn); }
html[data-state="down"]     .home-pulse-dot { background: var(--err); }
.home-pulse-text {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
}
.home-pulse-sep {
  color: var(--fg-faint);
  opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
  .home-pulse { transition: none; opacity: 1; }
  .home-pulse-dot { transition: none; }
}

/* ============================================================
   página /organizacion (placeholder hasta que se construya Trello)
   ============================================================ */

.stage-modulo {
  max-width: 580px;
  gap: 2rem;
  align-items: stretch;
  text-align: left;
}
.modulo-header { display: flex; flex-direction: column; gap: 0.5rem; }
.modulo-title {
  font-size: var(--text-xl);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0;
}
.modulo-sub {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  margin: 0;
  line-height: 1.5;
}
.modulo-placeholder {
  border-left: 2px solid var(--line-strong);
  padding-left: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.placeholder-headline {
  font-size: clamp(1.5rem, 4vw, 2.25rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--fg);
  line-height: 1.1;
  margin: 0;
}
.placeholder-text {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  line-height: 1.55;
  margin: 0;
  max-width: 56ch;
}

/* Proyectos en home — entradas grandes con hover spring (legacy, no se usa ahora) */
.home-projects-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.home-project {
  border-bottom: 1px solid var(--line);
}
.home-project:last-child { border-bottom: none; }
.home-project-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 0.1rem;
  color: var(--fg);
  text-decoration: none;
  transition: transform var(--t-base) var(--ease-spring),
              color     var(--t-fast) var(--ease);
}
.home-project-link:hover {
  transform: translateX(6px);
  color: var(--lime);
}
.home-project-name {
  font-size: clamp(1.125rem, 2.5vw, 1.5rem);
  font-weight: 500;
  letter-spacing: -0.02em;
}
.home-project-meta {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.home-project-status {
  color: var(--fg-dim);
}
.home-project-link::after {
  content: "→";
  margin-left: 0.5rem;
  color: var(--fg-faint);
  transition: transform var(--t-base) var(--ease-spring), color var(--t-fast) var(--ease);
}
.home-project-link:hover::after {
  transform: translateX(4px);
  color: var(--lime);
}

/* ============================================================
   página /proyectos (lista)
   ============================================================ */

.stage-projects {
  max-width: 720px;
  gap: 1.25rem;
  align-items: stretch;
  text-align: left;
}

.page-header {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.5rem;
}
.page-title {
  font-size: var(--text-lg);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin: 0;
}
.page-sub {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  letter-spacing: 0.02em;
  margin: 0;
}

.project-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0.875rem;
}
.project-card {
  position: relative;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  transition: border-color var(--t-base) var(--ease),
              transform     var(--t-base) var(--ease-spring),
              box-shadow    var(--t-base) var(--ease-spring);
  background: color-mix(in srgb, var(--fg) 1%, transparent);
}
.project-card:hover {
  border-color: var(--lime-soft);
  transform: translateY(-3px) scale(1.005);
  box-shadow: 0 12px 28px -8px rgb(0 0 0 / 0.25);
}
.project-card-link {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 1rem 1.1rem 0.9rem;
  color: var(--fg);
  text-decoration: none;
  height: 100%;
}
.project-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.project-card-name {
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.project-card-type,
.project-type {
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  padding: 0.15rem 0.4rem;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
}
.type-external { color: var(--fg-dim); }
.type-internal { color: var(--fg-dim); }
.type-aggregator { color: var(--fg-dim); }

.project-card-desc {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  line-height: 1.4;
  margin: 0;
}
.project-card-estado {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  line-height: 1.45;
  margin: 0;
  padding-top: 0.3rem;
  border-top: 1px dashed var(--line);
}
.project-card-foot {
  margin-top: auto;
  display: flex;
  gap: 0.5rem;
  align-items: center;
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.04em;
  padding-top: 0.25rem;
}
.project-card-status { color: var(--fg-dim); }
.project-card-owner::before { content: "·"; margin-right: 0.3rem; color: var(--fg-faint); }
.project-card-meta::before { content: "·"; margin-right: 0.3rem; color: var(--fg-faint); }

.project-card-external {
  position: absolute;
  top: 0.55rem;
  right: 0.65rem;
  color: var(--fg-faint);
  text-decoration: none;
  font-size: 0.85rem;
  padding: 0.1rem 0.3rem;
  border-radius: 2px;
  transition: color var(--t-fast) var(--ease);
}
.project-card-external:hover { color: var(--fg); }

/* ============================================================
   página /proyectos/<slug> (detalle)
   ============================================================ */

/* Sub-fase 3A (2026-05-18): max-width Linear-like. La página del proyecto
   antes era 680px (compacto). Subimos a 880px para dar respiro al hero
   editorial nuevo (.project-hero clamp 2-3.25rem) y dejar márgenes
   laterales perceptibles en pantallas anchas. */
.stage-project {
  max-width: 880px;
  gap: 1.75rem;
  align-items: stretch;
  text-align: left;
}

.project-header {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.project-meta-row {
  display: flex;
  gap: 0.45rem;
  align-items: center;
  flex-wrap: wrap;
}
.project-status,
.project-owner {
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-dim);
  padding: 0.15rem 0.4rem;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
}
/* Sub-fase 2C (2026-05-18): hero editorial estilo Linear.
   Nombre del proyecto + descripción se renderizan inline como un único
   párrafo grande con contraste fuerte/débil. El `<h1>` tiene tamaño hero
   y letter-spacing negativo apretado (Linear typography rule: -0.5 a -2px
   en headings ≥32px). Geist 600 vs 400 produce el contraste sin migrar a
   Inter — preserva identidad. */
.project-hero {
  font-size: clamp(2rem, 4.4vw, 3.25rem);
  font-weight: 400;
  letter-spacing: -0.028em;
  line-height: 1.1;
  margin: 0.5rem 0 0;
  max-width: 56rem;
}
.project-hero-strong {
  font-weight: 600;
  color: var(--fg);
}
.project-hero-soft {
  font-weight: 400;
  color: var(--fg-dim);
}
/* Sin .project-description ni .project-title independientes: vivían
   en --text-xl y --text-sm separados (sin patrón editorial). Reemplazados
   por .project-hero + spans para producir el efecto Linear (mismo párrafo
   visual, contraste interno). */
.project-url a {
  font-size: var(--text-sm);
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition: border-color var(--t-fast) var(--ease);
}
.project-url a:hover { border-color: var(--fg); }

.estado-actual,
.bitacora {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.section-title {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--line-strong);
}

.md-body {
  font-size: var(--text-sm);
  color: var(--fg);
  line-height: 1.55;
}
.md-body p { margin: 0 0 0.6rem; }
.md-body p:last-child { margin-bottom: 0; }
.md-body strong { font-weight: 600; }
.md-body a { color: var(--fg); border-bottom: 1px solid var(--line-strong); text-decoration: none; }
.md-body a:hover { border-color: var(--fg); }
.md-body ul, .md-body ol { padding-left: 1.25rem; margin: 0 0 0.6rem; }
.md-body code { font-family: var(--font-mono); font-size: 0.85em; background: var(--line); padding: 0.05em 0.3em; border-radius: 2px; }
.md-body-compact { font-size: var(--text-sm); }

.bitacora-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.bitacora-entry {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--line);
}
.bitacora-entry:last-child { border-bottom: none; padding-bottom: 0; }
.bitacora-entry-head {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  flex-wrap: wrap;
}
.bitacora-date {
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bitacora-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--fg);
}
.bitacora-body { font-size: var(--text-sm); }

/* ============================================================
   filtros (filter-pills dentro de .activity-toolbar fija)
   ============================================================ */

.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
}

/* Estado activo de filtros: bg sólido + texto contrastante.
   Patrón Linear/Stripe/Figma/GitHub. NO usamos glow blur por 3 razones
   verificables:
   1. WCAG 2.2 SC 1.4.11: el glow no contribuye al cálculo de contraste UI.
      Solo cuenta el cambio sólido de bg/fg (ratio mínimo 3:1 para UI, 4.5:1
      para texto). bg=var(--fg) sobre color=var(--bg) cumple AAA siempre.
   2. Apple HIG: "Use color and shape to clearly indicate state; avoid subtle
      visual effects that may be missed or confusing".
   3. El blur radius es fijo (px) pero el pill es variable (depende del texto).
      Resultado: en pills anchos el halo invade más espacio que en cortos,
      generando percepción de espaciado distinto entre filtros. Inconsistencia
      visual real, no imaginada. */
.filter-pill {
  display: inline-flex;
  align-items: center;
  position: relative;
  padding: 0.32rem 0.75rem;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg-faint);
  text-decoration: none;
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  transform-origin: center;
  transition:
    color        160ms var(--ease),
    background   160ms var(--ease),
    border-color 160ms var(--ease),
    transform    240ms var(--ease-spring);
}
.filter-pill:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border-color: transparent;
  transform: translateY(-1px);
}
.filter-pill:active { transform: translateY(0) scale(0.96); }

.filter-pill.is-active {
  color: var(--bg);
  background: var(--fg);
  border-color: var(--fg);
}
.filter-pill.is-active:hover {
  /* No cambiar nada en hover si ya está activo — evita doble feedback */
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
  transform: none;
}
.filter-pill.is-active::after { content: none; }

.filter-pill.is-back {
  color: var(--fg);
  border-color: var(--fg-dim);
  margin-left: auto;
}

/* ============================================================
   día (agrupación)
   ============================================================ */

.day-group {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.day-header {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 0.25rem;
  padding-top: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line-strong);
}
.day-group:first-of-type .day-header { padding-top: 0; }

/* ============================================================
   paginación (cursor "ver más antiguos")
   ============================================================ */

.pager {
  display: flex;
  justify-content: center;
  margin-top: 0.5rem;
}
.pager-link {
  display: inline-block;
  padding: 0.625rem 1.25rem;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  color: var(--fg-dim);
  text-decoration: none;
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.pager-link:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
}

/* ============================================================
   animaciones de entrada (login + welcome a home)
   ============================================================ */

/* Login: traza enso + fade-up stagger */
body.login-page .enso .ring {
  stroke-dasharray: 300;
  stroke-dashoffset: 300;
  animation: enso-draw 800ms var(--ease) 100ms forwards;
}
body.login-page .enso .dot,
body.login-page .enso .dot-static {
  opacity: 0;
  animation: pop-in 240ms var(--ease-spring) 700ms forwards,
             dot-pulse var(--pulse-duration) ease-in-out 940ms infinite;
}
/* Login stagger reducido (kaizen-critic: 1300ms del footer era largo
   para re-login frecuente). Bajamos los tiempos manteniendo orden:
   título 500ms → field1 600ms → field2 700ms → submit 800ms → foot 900ms.
   El enso ya termina su traza en 100+800=900ms; la columna entra
   justo después, no se queda esperando. */
body.login-page .login-title  { opacity: 0; animation: fade-up var(--t-slow) var(--ease)  500ms forwards; }
body.login-page .auth .field:nth-of-type(1) { opacity: 0; animation: fade-up var(--t-slow) var(--ease)  600ms forwards; }
body.login-page .auth .field:nth-of-type(2) { opacity: 0; animation: fade-up var(--t-slow) var(--ease)  700ms forwards; }
body.login-page .auth-submit   { opacity: 0; animation: fade-up var(--t-slow) var(--ease)  800ms forwards; }
body.login-page .login-foot    { opacity: 0; animation: fade-up var(--t-slow) var(--ease)  900ms forwards; }

@keyframes enso-draw {
  to { stroke-dashoffset: 0; }
}
@keyframes pop-in {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes fade-up {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Welcome a home (post-login): trazo del enso + expansión del stage.
   La clase 'just-logged-in' la pone JS al detectar ?welcome=1, y NO se
   aplica en refreshes normales. Por eso solo se ve UNA vez al día. */
body.just-logged-in .stage {
  animation: stage-welcome 500ms var(--ease) backwards;
}
body.just-logged-in .enso .ring {
  stroke-dasharray: 300;
  stroke-dashoffset: 300;
  animation: enso-draw 900ms var(--ease) 150ms forwards;
}
body.just-logged-in .enso .dot {
  opacity: 0;
  animation: pop-in 280ms var(--ease-spring) 850ms forwards,
             dot-pulse var(--pulse-duration) ease-in-out 1130ms infinite;
}

@keyframes stage-welcome {
  from { opacity: 0; transform: scale(0.97); }
  to   { opacity: 1; transform: scale(1); }
}

/* ============================================================
   KAIZEN_CINEMATIC_ENTRY_V1 — entrada secuencial de la home cockpit.

   No bloquea contenido: la página queda visible enseguida, los
   elementos aparecen con ritmo (~2.4s total). Sin loader, sin video,
   sin WebGL, sin partículas. Premium sobrio:
     enso se traza → kaizen → build → os → SALUDO (protagonista)
     → contexto → orbes (stagger) → estado heredado (tenue).

   Verde nunca protagonista (--lime se reserva para focus/hover/submit).
   Saludo manda por encima de la marca: ese es el momento humano.
   ============================================================ */

/* Estado inicial: hijos del hero ocultos hasta que la animación los
   revele. Usar selector específico para no afectar otras vistas. */
body.page-home .hero-enso .ring {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: home-enso-draw 720ms cubic-bezier(0.65, 0, 0.35, 1) 100ms forwards;
}
body.page-home .hero-enso .dot {
  opacity: 0;
  /* WO BRAND MOTION (2026-05-22): el dot deja de hacer dot-pulse (que
     vivía pulsando en sitio) y ahora orbita alrededor del centro del
     enso. El latido se delega al SVG entero (.hero-enso enso-breath
     más arriba). Pop-in conserva la entrada del cinematic V1. */
  transform-box: view-box;
  transform-origin: 100px 100px;
  animation: pop-in 280ms var(--ease-spring) 680ms forwards,
             dot-orbit 6.4s linear 1.2s infinite;
  /* Hero monocromo: el dot NO hereda --accent/--glow del [data-state]
     global (que lo pintaría lime cuando /status=ok). El verde no es
     protagonista del hero — se reserva para señales funcionales
     (focus, hover, submit, halo del orb, [data-state=ok] en
     topbar/avatar) fuera del hero. El dot sigue siendo identidad
     visual: SOLO cambia de RITMO con la actividad real, nunca de
     color. Los orbes de proyecto y el avatar del topbar mantienen su
     color asignado por backend — no son hero. */
  fill: var(--fg-dim);
  filter: none;
}

body.page-home .wordmark-hero .w-bold,
body.page-home .wordmark-hero .w-light,
body.page-home .wordmark-hero .w-accent {
  opacity: 0;
  transform: translateY(14px);
  animation: home-word-rise 480ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
body.page-home .wordmark-hero .w-bold   { animation-delay: 700ms; }
body.page-home .wordmark-hero .w-light  { animation-delay: 940ms; }
body.page-home .wordmark-hero .w-accent { animation-delay: 1140ms; }

/* Saludo: PROTAGONISTA visual (no solo temporal).
   Sin esto el saludo queda en text-sm fg-dim por la base .greeting;
   el wordmark lo aplastaría incluso después de entrar. Ahora el saludo
   gana tamaño medio (entre body y wordmark) y color pleno: el ojo lo
   reconoce como el momento humano y no como pie de página de la marca.
   El wordmark sigue siendo marca; el saludo es lo que se le dice a
   Ibon cada vez que vuelve a su cockpit. */
body.page-home .greeting {
  /* WO HOME PRESENCE LAYOUT REBALANCE (2026-05-22): saludo más
     contenido — pasa de hero gigante a "saludo humano" de tamaño
     medio. 18-24px desktop. Mobile baja a 16px por override más
     abajo. */
  font-size: clamp(1.125rem, 2vw, 1.5rem);
  color: var(--fg);
  font-weight: 400;
  letter-spacing: -0.015em;
  line-height: 1.2;
  height: auto;
  margin-top: 0.25rem;
  opacity: 0;
  transform: translateY(18px);
  /* Sub-fase 2A (2026-05-18) + WO GREETING PERSISTENT ROTATION (2026-05-24):
     saludo persistente — RISE entra una vez (520ms a 1340ms), y se queda
     visible. El DISPERSE anterior se retira: el saludo permanece y
     rota cada 10 min via home.js. La transición de cross-fade entre
     frases la maneja `.greeting--rotating` (opacity + translateY corto). */
  animation: home-greeting-rise 520ms cubic-bezier(0.2, 0.7, 0.2, 1) 1340ms forwards;
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
/* WO GREETING PERSISTENT ROTATION (2026-05-24): clase aplicada por JS
   durante el cross-fade entre frases. Subida ligera + opacity 0 para
   sensación de "se va por arriba"; cuando JS quita la clase tras
   cambiar el texto, vuelve a opacity 1 / translateY 0 con la transition
   declarada arriba. La animation `home-greeting-rise` ya terminó
   ('forwards'), así que no compite con la transition. */
body.page-home .greeting.greeting--rotating {
  opacity: 0;
  transform: translateY(-4px);
}
/* Anular toggling de visibilidad heredado: en home la keyframe es la
   única fuente de verdad. Estas clases existen por compat con
   pulse.js anterior; aquí no hacen nada. */
body.page-home .greeting.is-visible,
body.page-home .greeting.is-fading { opacity: inherit; }

body.page-home .home-context {
  opacity: 0;
  animation: home-fade-in 400ms var(--ease) 1620ms forwards;
}

body.page-home .home-orbs .module-cell {
  opacity: 0;
  transform: translateY(10px);
  animation: home-orb-rise 360ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
/* WO CREATE-FLOW-FIX (2026-05-23): tras ~3s home.js añade body.cinematic-done.
   Suprime el home-orb-rise (que se RE-EJECUTA en Chromium cuando una sibling
   nueva se inserta y cambia nth-child de las celdas hermanas) para que
   crear un proyecto NO haga "reset visual" de 2.7s en toda la grilla.
   Inline-style en buildOrb refuerza para celdas insertadas. */
body.cinematic-done.page-home .home-orbs .module-cell {
  opacity: 1 !important;
  transform: none !important;
  animation: none !important;
}
/* HOME FUTURE CORE V2: orbes entran +500ms para dejar espacio a
   `.next-line` (1900ms) y `.greeting-context` (1600ms) que comunican
   el momento operativo. Los orbes son contexto, no protagonistas. */
body.page-home .home-orbs .module-cell:nth-child(1) { animation-delay: 2200ms; }
body.page-home .home-orbs .module-cell:nth-child(2) { animation-delay: 2260ms; }
body.page-home .home-orbs .module-cell:nth-child(3) { animation-delay: 2320ms; }
body.page-home .home-orbs .module-cell:nth-child(4) { animation-delay: 2380ms; }
body.page-home .home-orbs .module-cell:nth-child(5) { animation-delay: 2440ms; }
body.page-home .home-orbs .module-cell:nth-child(6) { animation-delay: 2500ms; }
body.page-home .home-orbs .module-cell:nth-child(7) { animation-delay: 2560ms; }
body.page-home .home-orbs .module-cell:nth-child(8) { animation-delay: 2620ms; }
body.page-home .home-orbs .module-cell:nth-child(n+9) { animation-delay: 2680ms; }

/* Empty state también stagger-in (cuando no hay proyectos).
   V2: alineado con los orbes (2200ms) para entrar coordinado. */
body.page-home .home-orbs-empty {
  opacity: 0;
  animation: home-fade-in 460ms var(--ease) 2200ms forwards;
}

/* home-estado heredado: OCULTO en la home cockpit/cinematic. El
   titular editorial del meta-panel ahora vive en el saludo personal
   ("Seguimos donde lo dejamos, Ibon."); el bloque legacy "Estado
   actual / esperando proyectos." competía visualmente con la
   escena cinematográfica y rompía la sensación de cockpit. La
   bitácora completa SIGUE EXISTIENDO en backend (estado_actual_html
   se renderiza pero queda fuera del flow visual vía CSS). El
   invariante editorial de CLAUDE.md L165-168 sobre "/proyectos/<slug>"
   no se ve afectado — ese template usa otra estructura
   (.md-body sin .home-estado) y el scope `body.page-home` aísla
   este ocultamiento solo a la home.

   ACCESIBILIDAD: `display: none` también oculta el bloque a
   lectores de pantalla. Decisión consciente — el contenido actual
   ("esperando proyectos.") no aporta señal informativa a usuarios
   con AT que el saludo ya no cubra. Si en el futuro el bloque
   contiene actividad real (ej. "guti cerró 3 tareas hoy."), habrá
   que reabrir la WO con técnica accesible (aria-live, clip-path,
   etc.) — Y reañadir el selector a la lista del @media reduced-
   motion más abajo. */
/* Bloque legacy `home-estado` (markdown crudo de bitácora meta) sigue
   oculto en home — el bloque editorial se ha sustituido por la capa
   `.home-memory` (headline corto reabierto en B'). El template aún
   renderiza estado_actual_html por compat con otras vistas; aquí se
   oculta para no duplicar narrativa. */
body.page-home .home-estado { display: none; }

/* ============================================================
   HOME_B'_LABORATORIO_OPERATIVO — capas editoriales + banda + memoria
   Cero datos inventados: cada capa es opcional según dato real.
   ============================================================ */

/* Editorial stack (3 capas verticales bajo el saludo).
   L1 contexto: bajo contraste, siempre visible.
   L2 pulso del día (actor_pulse): contraste medio. Hidden si no hay datos.
   L3 acción editorial: contraste pleno, link inline si aplica.
   Cada capa es una <p> propia → si una se oculta, las otras no saltan. */
.home-editorial {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  margin: 0.4rem 0 0;
}
.home-editorial-context {
  font-size: var(--text-xs);
  color: var(--fg-faint);
  letter-spacing: 0.04em;
  margin: 0;
}
.home-editorial-pulse {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  letter-spacing: -0.005em;
  margin: 0;
  font-weight: 400;
}
.home-editorial-pulse[hidden] { display: none; }
.home-editorial-action {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.4em;
  font-size: var(--text-sm);
  margin: 0.05rem 0 0;
  color: var(--fg-faint);
}
.home-editorial-action[data-editorial-kind="dormant"] .home-editorial-text,
.home-editorial-action[data-editorial-kind="activity_today"] .home-editorial-text,
.home-editorial-action[data-editorial-kind="all_active_week"] .home-editorial-text {
  color: var(--fg-dim);
}
.home-editorial-action .home-editorial-link {
  font-size: inherit;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.home-editorial-action .home-editorial-link:hover {
  color: var(--lime);
  border-color: var(--lime);
}
.home-editorial-action .home-editorial-link:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 3px;
  border-color: transparent;
}

/* Halo orbe — actividad real <5 min. JS añade .has-recent al .module-cell
   cuando el slug aparece en /api/home/pulse → active_slugs. El halo es un
   anillo lime que late suave. Calm tech: solo aparece con dato real;
   `prefers-reduced-motion` desactiva la pulsación, no el halo. */
.module-halo {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 1.5px solid transparent;
  pointer-events: none;
  opacity: 0;
  z-index: -1;
  transition: opacity 480ms var(--ease);
}
.module-cell.has-recent .module-halo {
  opacity: 1;
  border-color: var(--lime);
  box-shadow: 0 0 24px 4px color-mix(in srgb, var(--lime) 30%, transparent);
  animation: home-orb-halo-pulse 2.4s ease-in-out infinite;
}
@keyframes home-orb-halo-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.035); }
}
@media (prefers-reduced-motion: reduce) {
  .module-cell.has-recent .module-halo { animation: none; opacity: 1; }
}

/* Dot lime de agencia — Ibon tiene cards asignadas en este board.
   Coexiste con el dot blanco identitario. Forma distinta: anillo abierto
   (no punto sólido) para no confundirse con la identidad enso. */
.module-agency {
  position: absolute;
  bottom: 10px;
  left: 10px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid var(--lime);
  background: transparent;
  box-shadow: 0 0 6px color-mix(in srgb, var(--lime) 60%, transparent);
  opacity: 0;
  transition: opacity 240ms var(--ease);
  pointer-events: none;
}
.module-cell.has-agency .module-agency { opacity: 1; }

/* Home: señales verdes secundarias del orbe (no pedidas, molestan sobre
   el orbe con imagen): el circulito de agencia (abajo-izquierda) y el
   anillo lime de actividad reciente (halo). Se retiran solo en la home.
   NO afecta .module-image, .module-pulse (dot blanco superior) ni el
   tooltip. Scope acotado a .home-orbs. */
.home-orbs .module-agency,
.home-orbs .module-halo { display: none; }

/* Banda "qué se mueve ahora".
   Oculta por defecto (template tiene hidden). JS quita hidden cuando hay
   recent_events. Densidad baja: máximo 3 lines + label + link "ver todo".
   Calm: el conjunto es lateral al hero, no protagonista. */
.home-recent {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  border-top: 1px solid color-mix(in srgb, var(--fg-faint) 25%, transparent);
  padding-top: 0.65rem;
}
.home-recent[hidden] { display: none; }
.home-recent-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}
.home-recent-label {
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  font-weight: 500;
}
.home-recent-more {
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease);
}
.home-recent-more:hover { color: var(--fg); }
.home-recent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.home-recent-item {
  display: flex;
  gap: 0.55rem;
  align-items: baseline;
  font-size: var(--text-sm);
  color: var(--fg-dim);
  line-height: 1.35;
}
.home-recent-when {
  font-size: var(--text-xs);
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  letter-spacing: 0.02em;
}
.home-recent-text { color: var(--fg-dim); }

/* Memoria del meta-panel + última lección IA — dos capas hermanas
   debajo de la banda recent. Mismo lenguaje visual (compacto, centered,
   border-top sutil). Solo headline + label, sin bloque markdown.
   Cualquiera se oculta si su dato no existe (DATA_NOT_AVAILABLE) —
   nunca placeholder ni "próximamente". */
.home-memory,
.home-lesson {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  border-top: 1px solid color-mix(in srgb, var(--fg-faint) 25%, transparent);
  padding-top: 0.65rem;
  text-align: center;
}
.home-memory-label,
.home-lesson-label {
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-faint);
  font-weight: 500;
}
.home-memory-headline,
.home-lesson-headline {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  margin: 0;
  letter-spacing: -0.005em;
  line-height: 1.4;
}

/* Reduced-motion para nuevas capas: estado final inmediato, sin retardo
   añadido por las animaciones del cinematic V1. */
@media (prefers-reduced-motion: reduce) {
  body.page-home .home-editorial,
  body.page-home .home-recent,
  body.page-home .home-memory,
  body.page-home .home-lesson { opacity: 1; }
}

/* ============================================================
   HOME B' Hero amplificado — enso protagonista
   Sube el piso del clamp para que el enso pese más en la composición
   sin desplazar el wordmark (que sigue siendo identidad textual).
   El cinematic V1 (style.css §KAIZEN_CINEMATIC_ENTRY_V1) sigue rigiendo
   la entrada animada — esto solo cambia el TAMAÑO base.
   ============================================================ */
body.page-home .hero-enso {
  width: clamp(48px, 4.5vw, 64px);
  height: clamp(48px, 4.5vw, 64px);
  /* WO BRAND MOTION (2026-05-22): el SVG entero hace latido sutil
     (escala 1 → 1.04). Origen en el centro del viewBox (50%). */
  transform-origin: center;
  animation: enso-breath 2.6s ease-in-out 1.2s infinite;
}

/* WO BRAND MOTION (2026-05-22): latido del enso — escala muy leve,
   ritmo calmado. NO afecta al dot orbital (.dot transforma sobre
   transform-origin 100,100 dentro del SVG; el latido escala el SVG
   externo). Reduced-motion lo neutraliza en regla existente que
   resetea opacity+animation-delay (line ~3104). */
@keyframes enso-breath {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

/* WO BRAND MOTION (2026-05-22): órbita del dot alrededor del centro
   del enso. El dot parte de (151.5, 36) — endpoint de la curva ring.
   Rotación 360° con transform-origin en (100, 100) hace que el dot
   recorra el perímetro del ring a radio 82 (idéntico al ring) — la
   tinta "viaja" por la curva. 6.4s linear es ritmo sereno, no spinner.
   Delay 1.2s para arrancar después del pop-in (680+280=960ms). */
@keyframes dot-orbit {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ============================================================
   WO_SHIP_SAFE PRESENCE SYSTEM (2026-05-22) — DECORATIVO.
   Bloque entre wordmark y greeting que comunica "presencia viva"
   sin ser marca. NO es marca. NO depende del SVG enso (que se
   retiró del hero). Identidad propia, separada.

   En producción se renderizan tres capas:
     - .kp-core: núcleo central con latido sutil (kp-core-pulse 2.4s).
     - .kp-orbit-system: planeta sistema orbitando siempre (14s).
     - .kp-orbit-user-slot-1..4: 4 slots estables siempre orbitando.
       El planeta interior se muestra/oculta vía `[data-empty]` desde
       JS; el wrapper orbital nunca se elimina, así la animación CSS
       no se reinicia en cada poll.
   ============================================================ */

.kz-presence-system {
  /* WO HOME PRESENCE LAYOUT REBALANCE (2026-05-22): tamaño medio
     comparable a un orbe (orbe disco 80px, módulo-cell completo
     ~110px). 120×120 entra en el rango 0.8-1.2× del cell. Margen:
     top grande (push down del wordmark), bottom corto (cerca del
     saludo). */
  position: relative;
  width: 120px;
  height: 120px;
  margin: clamp(2rem, 4vw, 3rem) auto 0.4rem;
  flex-shrink: 0;
}

/* Núcleo central — late suave. NO es marca: vive en su propio
   bloque, lejos del enso de la cabecera. */
.kp-core {
  position: absolute;
  top: 50%; left: 50%;
  width: 10px; height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--fg) 90%, transparent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--fg) 35%, transparent);
  animation: kp-core-pulse 2.4s ease-in-out infinite;
}
@keyframes kp-core-pulse {
  0%, 100% { transform: scale(0.92); opacity: 0.85; }
  50%      { transform: scale(1.08); opacity: 1; }
}

/* Wrapper orbital — rota; el planeta dentro va anchored al top
   center. Variar el tamaño del wrapper varía el radio del orbit. */
.kp-orbit {
  position: absolute;
  top: 50%; left: 50%;
  transform-origin: center;
  pointer-events: none;
  animation: kp-orbit-spin var(--kp-duration, 12s) linear infinite;
  animation-delay: var(--kp-delay, 0s);
}
@keyframes kp-orbit-spin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

.kp-planet {
  position: absolute;
  top: -4px;       /* centra el planeta en el borde superior del wrapper */
  left: 50%;
  margin-left: -4px;
  width: 8px; height: 8px;
  border-radius: 50%;
}

/* ---- Planeta sistema: siempre presente ---- */
.kp-orbit-system {
  width: 56px; height: 56px;
  --kp-duration: 14s;
}
.kp-planet-system {
  background: color-mix(in srgb, var(--fg) 75%, transparent);
  box-shadow: 0 0 6px color-mix(in srgb, var(--fg) 28%, transparent);
}

/* WO PRESENCE STABLE ORBIT SLOTS (2026-05-24): 4 slots fijos always-on
   para usuarios. El wrapper orbital (`.kp-orbit-user-slot-N`) está
   siempre en DOM y siempre rotando — la animación de spin no se
   reinicia porque el nodo no se recrea. El planeta interior se muestra
   u oculta vía opacity controlada por `data-empty` en el wrapper.
   `data-self="true"` overridea el color para el current_user (usa fg
   con más brillo, sin --peer-color). */
.kp-orbit-user-slot-1 { width:  72px; height:  72px; --kp-duration: 11s; --kp-delay: -1.5s; }
.kp-orbit-user-slot-2 { width:  88px; height:  88px; --kp-duration: 13s; --kp-delay: -4s;   }
.kp-orbit-user-slot-3 { width: 104px; height: 104px; --kp-duration: 15s; --kp-delay: -7s;   }
.kp-orbit-user-slot-4 { width: 116px; height: 116px; --kp-duration: 17s; --kp-delay: -10s;  }

.kp-planet-user-slot {
  background: color-mix(in srgb, var(--peer-color, var(--fg)) 60%, transparent);
  box-shadow: 0 0 5px color-mix(in srgb, var(--peer-color, var(--fg)) 30%, transparent);
  opacity: 0;
  transform: scale(0.5);
  transition: opacity 360ms var(--ease), transform 360ms var(--ease);
}
.kp-orbit-user-slot[data-empty="false"] .kp-planet-user-slot {
  opacity: 0.9;
  transform: scale(1);
}
.kp-orbit-user-slot[data-self="true"] .kp-planet-user-slot {
  /* Current_user: más brillante que peers, sin tinte de color (90% fg). */
  background: color-mix(in srgb, var(--fg) 90%, transparent);
  box-shadow: 0 0 6px color-mix(in srgb, var(--fg) 32%, transparent);
}

@media (max-width: 520px) {
  .kp-orbit-user-slot-1 { width: 60px; height: 60px; }
  .kp-orbit-user-slot-2 { width: 72px; height: 72px; }
  .kp-orbit-user-slot-3 { width: 84px; height: 84px; }
  .kp-orbit-user-slot-4 { width: 92px; height: 92px; }
}

/* Helper sr-only para el summary accesible del presence-system. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Línea de órbita del sistema — muy tenue, ayuda a leer "esto ES un
   sistema solar" sin gritarlo. Variante A ya elegida en ship-safe;
   sin attribute selector. */
.kp-orbit-system::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px dashed color-mix(in srgb, var(--fg) 10%, transparent);
  border-radius: 50%;
}

/* Mobile: presence-system compacto pero no diminuto. 96px se mantiene
   en rango 0.8-1.2× del orbe mobile (módulo-cell ~98px). */
@media (max-width: 520px) {
  .kz-presence-system {
    width: 96px;
    height: 96px;
    margin: clamp(1.4rem, 6vw, 2rem) auto 0.35rem;
  }
  .kp-orbit-system { width: 40px; height: 40px; }
}

/* Reduced-motion: nada de loops, nada de spinning. Sistema queda como
   composición estática reveladora (núcleo + planeta sistema visibles;
   los user-slots ocupados se muestran al 0.9 sin orbitar; los vacíos
   permanecen al 0). */
@media (prefers-reduced-motion: reduce) {
  .kp-core,
  .kp-orbit {
    animation: none !important;
  }
  .kp-orbit { transform: translate(-50%, -50%) !important; }
  .kp-core { transform: translate(0, 0) !important; }
  /* WO PRESENCE STABLE ORBIT SLOTS: planetas user-slot sin transición y
     visibles estaticos cuando hay usuario. Sin loop. */
  .kp-planet-user-slot { transition: none !important; }
  .kp-orbit-user-slot[data-empty="false"] .kp-planet-user-slot {
    opacity: 0.9 !important;
    transform: scale(1) !important;
  }
  .kp-orbit-user-slot[data-empty="true"] .kp-planet-user-slot {
    opacity: 0 !important;
  }
}

/* WO KAIZEN HOME MICRO-LAYOUT (2026-05-22): la capa .kz-live-system
   (3 cards "lo que se mueve / próxima decisión / última lección") se
   oculta visualmente para que los orbes pasen a ocupar el centro de la
   home. El template y el backend siguen calculando los datos (no se
   tocan); solo cambia su presentación. Reversible con quitar esta regla. */
body.page-home .kz-live-system { display: none; }

/* Home compactada: logo arriba, orbes en el centro visual.
   stage-container deja de centrar verticalmente para que el contenido
   fluya desde arriba; stage-home estira a viewport y .home-orbs usa
   margin auto para ocupar el centro respecto al hueco libre debajo
   del lockup pequeño. */
body.page-home .stage-container {
  align-items: start;
  padding-top: clamp(1.5rem, 4vh, 3rem);
}
body.page-home .stage-home {
  /* WO HOME PRESENCE LAYOUT REBALANCE (2026-05-22): el stage deja de
     estirar a viewport con margin auto en orbs (esquema "orbes al
     centro vertical") y vuelve a flujo natural: home-head con su
     respiración interna define la composición; orbes caen justo
     debajo del saludo con un gap moderado. Resultado: saludo y orbes
     "cerca" como pide la WO. */
  min-height: auto;
  justify-content: flex-start;
  gap: 0;
}
body.page-home .home-head { margin: 0; }
body.page-home .home-orbs { margin: clamp(2.75rem, 6vh, 4rem) 0 0; }

/* WO HOME PROJECT ORBS SCALE + LOWER REBALANCE (2026-05-22):
   Reduce el peso visual de los orbes de proyecto SOLO en home
   (~15% más pequeños: 80→68px desktop, 76→64px mobile) y baja el
   bloque .home-orbs (margin-top arriba) para dejar más aire al
   presence-system. Sin tocar /organizacion (sigue 80px), sin tocar
   hit area mínima del .module-cell. Por accesibilidad táctil, 68/64
   sigue por encima del mínimo 44px iOS/Android. */
body.page-home .home-orbs .module-disc,
body.page-home .home-orbs .orb-create-disc {
  width: 68px;
  height: 68px;
  font-size: 1.2rem;
}
@media (max-width: 520px) {
  body.page-home .home-orbs .module-disc,
  body.page-home .home-orbs .orb-create-disc {
    width: 64px;
    height: 64px;
    font-size: 1rem;
  }
  body.page-home .home-orbs { margin: clamp(2rem, 5vh, 2.75rem) 0 0; }
}

/* Cinematic V1 + capas B': contexto/pulso/acción siguen el fade-in del
   contexto (1620ms del cinematic V1) para entrar coordinados, no como
   bloque suelto. Reduced-motion ya las pone opacity:1 arriba. */
body.page-home .home-editorial {
  opacity: 0;
  animation: home-fade-in 400ms var(--ease) 1620ms forwards;
}
body.page-home .home-recent,
body.page-home .home-memory,
body.page-home .home-lesson {
  opacity: 0;
  animation: home-fade-in 480ms var(--ease) 2240ms forwards;
}
@media (prefers-reduced-motion: reduce) {
  body.page-home .home-editorial,
  body.page-home .home-recent,
  body.page-home .home-memory,
  body.page-home .home-lesson {
    animation-delay: 0ms !important;
    animation-duration: 0.01ms !important;
    opacity: 1 !important;
  }
}

@keyframes home-enso-draw {
  to { stroke-dashoffset: 0; }
}
@keyframes home-word-rise {
  to { opacity: 1; transform: translateY(0); }
}
@keyframes home-greeting-rise {
  to { opacity: 1; transform: translateY(0); }
}
/* Sub-fase 2A (2026-05-18): el saludo se disuelve hacia arriba para ceder
   el protagonismo al panel y los orbes. NO desaparece de golpe — translate
   ligero hacia arriba + fade gradual da sensación de "se va flotando". */
@keyframes home-greeting-disperse {
  to { opacity: 0; transform: translateY(-8px); }
}
/* Sub-fase 3B (2026-05-18) + WO GREETING PERSISTENT ROTATION (2026-05-24):
   si la pestaña ya saludó esta sesión, el saludo aparece sin la
   animación cinemática inicial (sería ruidoso re-ejecutarla en cada
   navegación de vuelta a la home). Pero NO se oculta: el saludo es
   persistente, rota cada 10 min, debe estar visible siempre.
   La memoria `.greeting-context` sí sigue oculta por sesión (su
   propósito sí era "primera vez de la sesión").
   La detección la hace un inline-script en index.html antes de que
   CSS pinte, evitando flash. */
html.is-session-greeted body.page-home .greeting {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}
html.is-session-greeted body.page-home .greeting-context {
  display: none !important;
}
@keyframes home-orb-rise {
  to { opacity: 1; transform: translateY(0); }
}
@keyframes home-fade-in {
  to { opacity: 1; }
}
@keyframes home-fade-dim {
  to { opacity: 0.55; }
}

/* Reduced-motion: todo visible instantáneamente, sin secuencia.
   La regla global de prefers-reduced-motion ya reduce duration a 0.01ms,
   pero los delays NO se reducen — bajo reduced-motion los elementos se
   verían aparecer "de golpe" con retraso. Forzar estado final + delay 0
   para que todo esté presente al primer frame. */
@media (prefers-reduced-motion: reduce) {
  /* home-estado ya no aparece en la lista — ahora está display:none
     en page-home, sin animación ni delay que resetear. */
  body.page-home .hero-enso .ring,
  body.page-home .hero-enso .dot,
  body.page-home .wordmark-hero .w-bold,
  body.page-home .wordmark-hero .w-light,
  body.page-home .wordmark-hero .w-accent,
  body.page-home .greeting,
  body.page-home .home-context,
  body.page-home .home-orbs .module-cell,
  body.page-home .home-orbs-empty {
    animation-delay: 0ms !important;
    opacity: 1 !important;
    transform: none !important;
    stroke-dashoffset: 0 !important;
  }
  /* WO BRAND MOTION (2026-05-22): explícitamente paramos las loops del
     brand para no quemar CPU con animaciones de transform: none
     congeladas. enso-breath y dot-orbit se quedan en frame estático. */
  body.page-home .hero-enso,
  body.page-home .hero-enso .dot {
    animation: none !important;
  }
}

/* ============================================================
   responsive
   ============================================================ */

@media (max-width: 480px) {
  /* 2026-05-19: body padding mobile ELIMINADO. Antes era `1.5rem` en
     ambos ejes; intento intermedio fue `1.5rem 0` (solo vertical).
     Ambos creaban inconsistencia con `body.page-activity` (que NUNCA
     tuvo padding por diseño app-shell 100vh):
     - Lateral: topbar saltaba 24px en x al navegar home/kanban → actividad.
     - Vertical: topbar saltaba 24px en y (con `1.5rem 0`).
     Ahora todas las páginas mobile tienen topbar a (0,0). El respiro
     vertical y lateral lo gestionan los wrappers internos: `.topbar`
     con su propio `padding: 0 clamp(1.5rem, 10vw, 9rem)`, `.stage-
     container` con `padding: 2rem clamp(1.5rem, 10vw, 9rem)` (32px
     top), `.activity-toolbar` y `.activity-frame` con `clamp(1.5rem,
     10vw, 9rem)`. Verificado por scripts/e2e_navigation_outcome.py
     (topbar delta = 0px entre las 3 navegaciones mobile). */
  .stage { gap: 1.5rem; }
  .enso { width: 150px; height: 150px; }
  .enso-compact { width: 44px; height: 44px; }
  /* body.page-activity en móvil: NO padding (rompería el app-shell flex height:100vh) */
  .stage-activity { gap: 1rem; }
  .presence-stack { top: 0.875rem; right: 0.875rem; gap: 0.3rem; }
  .presence-avatar { width: 28px; height: 28px; font-size: var(--text-xs); }
  .presence-pip { width: 10px; height: 10px; }
}

/* === /organizacion (kanban) ===
   Sistema de medidas calibrado:
     - Spacing escala 4-base (4/8/12/16/24/32px)
     - Tipografía 3 niveles operacionales
     - Sombras 3 niveles
   Nada inventado a ojo. Todo desde estos tokens. */

.page-kanban {
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;

  --shadow-low:  0 1px 2px rgba(0,0,0,0.05);
  --shadow-med:  0 4px 12px rgba(0,0,0,0.10);
  --shadow-high: 0 20px 50px rgba(0,0,0,0.18);

  --radius-card: 8px;
  --radius-pill: 999px;
  --radius-modal: 12px;

  --col-width:     288px;
  --col-radius:    10px;
  --col-bg:        color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
}

/* layout principal */
/* Sub-fase 6A (2026-05-18): kanban hereda el padding lateral clamp del
   default — antes tenía s-5 fijo (~24px) que rompía la consistencia con
   home/proyecto. Solo ajustamos vertical + place-items. */
body.page-kanban .stage-container {
  place-items: start stretch;
  padding-top: var(--s-4);
  padding-bottom: var(--s-5);
}
.stage-kanban {
  width: 100%;
  max-width: none;
  align-items: stretch;
  text-align: left;
  gap: var(--s-3);
}

/* --- header del board --- */
.kanban-header { margin-bottom: var(--s-3); }
.kanban-header-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

/* Refactor 2026-05-18: bloques eliminados (~190 líneas):
   - .kanban-board-selector{,-button,-menu,-item,-new,-sep,-archive} y .kanban-caret
     (dropdown multi-board obsoleto: cada tablero es estanco).
   - .kanban-archive-confirm{,-overlay,-dialog,-title,-body,-actions,-cancel,-go,-feedback}
     (modal huérfano: trigger eliminado, decisión sobre archivar pendiente). */
.kanban-board-name {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--fg);
  line-height: 1.1;
}

.kanban-new-card-button {
  margin-left: auto;
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: var(--s-2) var(--s-3);
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-weight: 500;
  font-size: 0.875rem;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  transition:
    opacity var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring),
    box-shadow var(--t-fast) var(--ease);
}
.kanban-new-card-button svg { width: 14px; height: 14px; }
.kanban-new-card-button:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-med);
}
.kanban-new-card-button:active { transform: translateY(0); }

/* --- board / columnas --- */
/* WO BOARD WALLPAPER BACKGROUND (2026-05-24): wrap con imagen de fondo
   opcional. data-has-bg="false" -> fondo negro (--bg) actual.
   data-has-bg="true" -> imagen via custom property --board-bg-url +
   overlay oscuro fijo (::after) que preserva legibilidad de columnas
   y tarjetas. Position relative para que columns (z-index:1) queden
   sobre el overlay. */
.kanban-board-wrap {
  position: relative;
  isolation: isolate;
  /* Reserva el ancho del scroller para que el bg no se corte si las
     columnas hacen overflow-x. min-height generosa para que el
     wallpaper se sienta atmosférico (no recortado bajo las columnas
     cortas). Alineado con max-height de columnas (var(--topbar-h)+7rem). */
  min-width: 0;
  min-height: calc(100vh - var(--topbar-h) - 9rem);
  display: flex;
  flex-direction: column;
}
.kanban-board-wrap > .kanban-board {
  flex: 1 0 auto;
}
.kanban-board-wrap[data-has-bg="true"]::before {
  content: "";
  position: absolute;
  inset: calc(var(--s-3) * -1);
  background-image: var(--board-bg-url);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: var(--col-radius);
  z-index: 0;
  pointer-events: none;
}
.kanban-board-wrap[data-has-bg="true"]::after {
  content: "";
  position: absolute;
  inset: calc(var(--s-3) * -1);
  /* Overlay oscuro 62% + blur sutil del backdrop (la imagen ::before).
     El blur reduce competencia entre patrones de la imagen y la
     separación de columnas; mantiene la imagen reconocible pero
     atmosférica, no protagonista. Si backdrop-filter no es soportado,
     el overlay solo (sin blur) sigue preservando legibilidad. */
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  border-radius: var(--col-radius);
  z-index: 0;
  pointer-events: none;
}
.kanban-board-wrap[data-has-bg="true"] > .kanban-board {
  position: relative;
  z-index: 1;
}

.kanban-board {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  overflow-x: auto;
  padding-bottom: var(--s-2);
}

/* Feedback del upload del wallpaper (gemelo de orb-image-feedback). */
.kanban-bg-image-feedback {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  align-self: center;
}
.kanban-bg-image-feedback[data-kind="error"] {
  color: color-mix(in srgb, #e08585 70%, var(--fg));
}

.kanban-column {
  background: var(--col-bg);
  border-radius: var(--col-radius);
  min-width: var(--col-width);
  max-width: var(--col-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - var(--topbar-h) - 7rem);
}
.kanban-column-header {
  padding: var(--s-3) var(--s-3) var(--s-2);
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
  cursor: grab;
  user-select: none;
  border-radius: var(--col-radius) var(--col-radius) 0 0;
  transition: background var(--t-fast) var(--ease);
}
.kanban-column-header:hover {
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}
.kanban-column-header:active { cursor: grabbing; }
.kanban-column-name {
  flex: 1;
  letter-spacing: -0.005em;
}
.kanban-column-count {
  background: color-mix(in srgb, var(--fg-dim) 18%, transparent);
  color: var(--fg-dim);
  font-size: 0.6875rem;
  font-weight: 500;
  padding: 2px var(--s-2);
  border-radius: var(--radius-pill);
  min-width: 20px;
  text-align: center;
}
.kanban-column-menu {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  padding: 2px var(--s-1);
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.kanban-column-menu svg { width: 14px; height: 14px; }
.kanban-column-menu:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
}
.kanban-column-header { position: relative; }
.kanban-column-menu-popup {
  position: absolute;
  top: 100%;
  right: var(--s-2);
  margin-top: 4px;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: var(--s-1);
  min-width: 180px;
  z-index: 25;
  box-shadow: var(--shadow-med);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kanban-column-menu-section {
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.08em;
  padding: 4px var(--s-2);
  font-weight: 500;
}
.kanban-column-menu-popup button {
  background: transparent;
  border: none;
  text-align: left;
  padding: 6px var(--s-2);
  font: inherit;
  font-size: 0.8125rem;
  color: var(--fg);
  cursor: pointer;
  border-radius: 4px;
}
.kanban-column-menu-popup button:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.kanban-column-body {
  padding: 0 var(--s-2);
  overflow-y: auto;
  flex: 1;
  min-height: 8px;
}
.kanban-column-add-card {
  margin: var(--s-1) var(--s-2) var(--s-2);
  background: transparent;
  border: none;
  color: var(--fg-dim);
  padding: var(--s-2) var(--s-3);
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  width: calc(100% - var(--s-4));
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.kanban-column-add-card:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
}

/* "+ columna" — más sutil, no ocupa una columna entera */
.kanban-column-add {
  min-width: 160px;
  align-self: flex-start;
  background: transparent;
  border: 1px dashed color-mix(in srgb, var(--fg-dim) 40%, transparent);
  color: var(--fg-dim);
  padding: var(--s-2) var(--s-3);
  border-radius: 8px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
  margin-top: var(--s-3);
}
.kanban-column-add:hover {
  background: color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
  color: var(--fg);
  border-color: var(--fg-dim);
}

/* --- card-front --- */
.kanban-card {
  background: var(--bg);
  border: none;
  border-radius: var(--radius-card);
  margin-bottom: var(--s-2);
  cursor: pointer;
  position: relative;
  box-shadow: var(--shadow-low);
  overflow: hidden;
  transition:
    transform var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    background var(--t-fast) var(--ease);
}
.kanban-card:hover {
  transform: translateY(-2px);
  background: color-mix(in srgb, var(--fg) 2%, var(--bg));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--fg-dim) 38%, transparent),
    var(--shadow-med),
    0 1px 2px rgba(0,0,0,0.06);
}
.kanban-card:active {
  transform: translateY(0);
  transition-duration: 80ms;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--fg-dim) 38%, transparent),
    0 1px 2px rgba(0,0,0,0.06);
}
.kanban-card:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px var(--fg),
    var(--shadow-med);
}
.kanban-card.is-saving { opacity: 0.55; pointer-events: none; }
.kanban-card.is-dragging {
  transform: rotate(3deg);
  box-shadow: 0 14px 28px rgba(0,0,0,0.22);
  cursor: grabbing;
}
.kanban-card.is-chosen { cursor: grabbing; }
.kanban-card.is-ghost {
  opacity: 0.45;
  background: color-mix(in srgb, var(--fg-dim) 12%, var(--bg));
}
.kanban-card.is-selected {
  box-shadow:
    0 0 0 3px var(--fg),
    var(--shadow-med);
  transform: translateY(-1px);
}

/* --- bulk action bar (selección múltiple) --- */
.kanban-bulk-bar {
  position: fixed;
  bottom: var(--s-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 250;
  background: var(--fg);
  color: var(--bg);
  border-radius: var(--radius-pill);
  padding: var(--s-2) var(--s-3);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: 0.875rem;
  font-weight: 500;
  box-shadow: var(--shadow-high);
  animation: kanban-toast-slide 200ms var(--ease-spring);
}
.kanban-bulk-count {
  padding: 0 var(--s-1);
}
.kanban-bulk-bar button {
  background: transparent;
  color: var(--bg);
  border: 1px solid color-mix(in srgb, var(--bg) 30%, transparent);
  padding: 4px var(--s-3);
  border-radius: var(--radius-pill);
  cursor: pointer;
  font: inherit;
  font-size: 0.8125rem;
  transition: background var(--t-fast) var(--ease);
}
.kanban-bulk-bar button:hover {
  background: color-mix(in srgb, var(--bg) 18%, transparent);
}

.kanban-card-cover {
  height: 32px;
  margin: 0;
}
.kanban-card-content {
  padding: var(--s-2) var(--s-3) var(--s-3);
}
.kanban-card-has-cover .kanban-card-content {
  padding-top: var(--s-2);
}
.kanban-card-title {
  font-size: 0.9rem;
  font-weight: 400;
  margin: 0;
  line-height: 1.4;
  color: var(--fg);
  word-break: break-word;
}
.kanban-card-meta {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.kanban-card-badges {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 1;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--fg-dim);
}
.kanban-card-badge {
  display: inline-flex;
  align-items: center;
  color: var(--fg-faint);
}
.kanban-card-badge svg { width: 12px; height: 12px; }

.kanban-card-due {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px var(--s-2);
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 500;
  background: color-mix(in srgb, var(--fg-dim) 12%, transparent);
  color: var(--fg-dim);
}
.kanban-card-due-icon { width: 11px; height: 11px; flex-shrink: 0; }
.kanban-card-due[data-state="soon"] {
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn);
}
.kanban-card-due[data-state="overdue"] {
  background: color-mix(in srgb, var(--err) 22%, transparent);
  color: var(--err);
}
.kanban-card-due[data-state="overdue-old"] {
  background: color-mix(in srgb, var(--err) 14%, transparent);
  color: color-mix(in srgb, var(--err) 70%, var(--fg-dim));
}

.kanban-card-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 600;
  flex-shrink: 0;
  box-shadow: 0 0 0 2px var(--bg);
}

/* Stack de avatares (apilados con overlap negativo, último delante).
   Usado en card-front y en meta-row del modal. */
.kanban-avatar-stack {
  display: inline-flex;
  align-items: center;
  flex-direction: row-reverse;
  /* row-reverse para que el primer avatar quede DELANTE visualmente
     (z-index implícito por orden inverso del DOM en flexbox). */
}
.kanban-avatar-stack > * {
  margin-left: -7px;
}
.kanban-avatar-stack > *:last-child {
  margin-left: 0;
}

.kanban-card-labels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  margin-bottom: var(--s-2);
}
.kanban-card-label {
  --label-color: var(--fg-dim);
  background: color-mix(in srgb, var(--label-color) 22%, transparent);
  color: color-mix(in srgb, var(--label-color) 78%, var(--fg));
  border: none;
  font-size: 0.68rem;
  font-weight: 500;
  padding: 2px var(--s-2);
  border-radius: var(--radius-pill);
  line-height: 1.4;
  letter-spacing: 0.01em;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- composers inline --- */
.kanban-card-composer {
  background: var(--bg);
  border-radius: var(--radius-card);
  padding: var(--s-2);
  margin-bottom: var(--s-2);
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
.kanban-card-composer-input {
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: none;
  outline: none;
  resize: none;
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.4;
  padding: 2px;
}
.kanban-card-composer-input::placeholder { color: var(--fg-faint); }
.kanban-card-composer-actions {
  display: flex;
  gap: var(--s-2);
  align-items: center;
  margin-top: var(--s-2);
}
.kanban-card-composer-add {
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: var(--s-2) var(--s-3);
  border-radius: 4px;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  box-shadow: var(--shadow-low);
  transition:
    opacity var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring);
}
.kanban-card-composer-add:hover { transform: translateY(-1px); opacity: 0.92; }
.kanban-card-composer-add:active { transform: translateY(0); }
.kanban-card-composer-cancel {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px var(--s-2);
  cursor: pointer;
}
.kanban-card-composer-cancel:hover { color: var(--fg); }

.kanban-column-composer {
  background: var(--col-bg);
  border-radius: var(--col-radius);
  padding: var(--s-3);
  min-width: var(--col-width);
  max-width: var(--col-width);
}
.kanban-column-composer-input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: var(--s-2);
  font: inherit;
  font-size: 0.875rem;
  margin-bottom: var(--s-2);
}
.kanban-column-composer-input:focus { outline: none; border-color: var(--fg-dim); }
.kanban-column-composer-actions { display: flex; gap: var(--s-2); align-items: center; }
.kanban-column-composer-add {
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: var(--s-2) var(--s-3);
  border-radius: 4px;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
}
.kanban-column-composer-cancel {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px var(--s-2);
  cursor: pointer;
}

/* Refactor 2026-05-18: .kanban-board-composer{,-input,-add} eliminados.
   El composer vivía dentro del dropdown selector, que ya no existe. */


/* =========================================================================
   MODAL — sistema calibrado al milímetro
   - Dialog: 720px, max-height calc(100vh - 64px)
   - Sidebar: 224px (suficiente para cover-colors 4cols sin overflow)
   - Main: el resto
   - Sidebar y main stretch a la misma altura via grid default
   ========================================================================= */

/* CRITICAL: [hidden] siempre gana. */
.kanban-modal [hidden],
.bell-modal [hidden] { display: none !important; }

.kanban-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--s-5) var(--s-4);
  overflow-y: auto;
}
.kanban-modal[hidden] { display: none !important; }
.kanban-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
.kanban-modal-dialog {
  position: relative;
  background: var(--bg);
  border-radius: var(--radius-modal);
  width: min(720px, 95vw);
  max-height: calc(100vh - var(--s-6) * 2);
  padding: 0;
  box-shadow: var(--shadow-high);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.kanban-modal-cover {
  height: 96px;
  width: 100%;
  flex-shrink: 0;
}

.kanban-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5) var(--s-3);
  flex-shrink: 0;
}
.kanban-modal-header-text { flex: 1; min-width: 0; }
.kanban-modal-breadcrumb {
  display: block;
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--s-1);
}
.kanban-modal-title {
  flex: 1;
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
  outline: none;
  border-radius: 4px;
  padding: 2px 6px;
  margin-left: -6px;
  line-height: 1.3;
}
.kanban-modal-title:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); }
.kanban-modal-title:focus {
  background: var(--line);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.kanban-modal-close {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  width: 28px;
  height: 28px;
  border-radius: 6px;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring);
}
.kanban-modal-close svg { width: 14px; height: 14px; }
.kanban-modal-close:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
  transform: scale(1.08);
}
.kanban-modal-close:active { transform: scale(0.94); }

/* Si hay cover, el close button está visualmente "sobre" el cover en la
   esquina (gracias al position absolute), con color blanco para
   contrastar con el color de portada. */
.kanban-modal.has-cover .kanban-modal-cover { position: relative; }
.kanban-modal.has-cover .kanban-modal-header { position: relative; }
.kanban-modal.has-cover .kanban-modal-close {
  position: absolute;
  top: var(--s-2);
  right: var(--s-2);
  background: rgba(0,0,0,0.35);
  color: #fff;
  z-index: 5;
}
.kanban-modal.has-cover .kanban-modal-close:hover {
  background: rgba(0,0,0,0.55);
}

.kanban-modal-body {
  display: grid;
  grid-template-columns: 1fr 224px;
  gap: var(--s-5);
  padding: 0 var(--s-5) var(--s-5);
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
@media (max-width: 640px) {
  .kanban-modal-body { grid-template-columns: 1fr; gap: var(--s-4); }
}

.kanban-modal-main {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
}

/* Sidebar con bg sutil que llena la "cell" del grid (default align: stretch).
   Así el gap blanco cuando termina antes que main se convierte en area
   coherente. */
.kanban-modal-sidebar {
  background: color-mix(in srgb, var(--fg-dim) 4%, var(--bg));
  border-radius: 8px;
  padding: var(--s-3);
}

/* --- meta inline (avatar + fecha + etiquetas, sin labels redundantes) --- */
.kanban-modal-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: 0;
  margin: 0;
}
.kanban-modal-meta-assignee {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
}
.kanban-modal-meta-due {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-1) var(--s-2);
  border-radius: 4px;
  background: color-mix(in srgb, var(--fg-dim) 12%, transparent);
  color: var(--fg-dim);
  font-size: 0.8125rem;
  font-weight: 500;
}
.kanban-modal-meta-due svg { width: 12px; height: 12px; }
.kanban-modal-meta-due[data-state="soon"] {
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn);
}
.kanban-modal-meta-due[data-state="overdue"] {
  background: color-mix(in srgb, var(--err) 22%, transparent);
  color: var(--err);
}
.kanban-modal-meta-due[data-state="overdue-old"] {
  background: color-mix(in srgb, var(--err) 14%, transparent);
  color: color-mix(in srgb, var(--err) 70%, var(--fg-dim));
}
.kanban-modal-meta-labels {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s-1);
}

/* --- secciones --- */
.kanban-modal-section { padding: 0; }
.kanban-modal-section-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.kanban-modal-section-icon {
  width: 16px;
  height: 16px;
  color: var(--fg-dim);
}
.kanban-modal-section-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--fg);
  margin: 0;
  letter-spacing: -0.005em;
}

/* --- descripción --- */
.kanban-modal-description-wrap { margin-left: var(--s-5); }
.kanban-modal-description-preview {
  background: color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
  border-radius: 6px;
  padding: var(--s-2) var(--s-3);
  min-height: 36px;
  cursor: text;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--fg);
  transition: background var(--t-fast) var(--ease);
  position: relative;
}
.kanban-modal-description-preview:hover {
  background: color-mix(in srgb, var(--fg-dim) 14%, var(--bg));
}
.kanban-modal-description-preview:focus {
  outline: 2px solid var(--fg-dim);
  outline-offset: 1px;
}
.kanban-modal-description-text {
  white-space: pre-wrap;
  word-break: break-word;
}
.kanban-modal-description-placeholder {
  color: var(--fg-faint);
  font-style: italic;
}
.kanban-modal-description-edit {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.kanban-modal-description {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.55;
  resize: vertical;
  min-height: 4rem;
}
.kanban-modal-description:focus {
  outline: none;
  border-color: var(--fg-dim);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fg-dim) 18%, transparent);
}
.kanban-modal-description-actions {
  display: flex;
  gap: var(--s-2);
}

.kanban-modal-btn-primary {
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: var(--s-2) var(--s-3);
  border-radius: 5px;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition:
    opacity var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring);
}
.kanban-modal-btn-primary:hover { transform: translateY(-1px); opacity: 0.9; }
.kanban-modal-btn-primary:active { transform: translateY(0); }
.kanban-modal-btn-ghost {
  background: transparent;
  color: var(--fg-dim);
  border: none;
  padding: var(--s-2) var(--s-3);
  border-radius: 5px;
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.kanban-modal-btn-ghost:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
}

/* --- comments --- */
.kanban-modal-comment-compose {
  display: flex;
  gap: var(--s-2);
  margin-left: var(--s-5);
  margin-bottom: var(--s-3);
}
.kanban-modal-comment-compose-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  flex-shrink: 0;
  margin-top: 1px;
}
.kanban-modal-comment-form {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.kanban-modal-comment-input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.5;
  resize: none;
  min-height: 36px;
  box-shadow: var(--shadow-low);
}
.kanban-modal-comment-input:focus {
  outline: none;
  border-color: var(--fg-dim);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--fg-dim) 18%, transparent);
}
.kanban-modal-comment-actions { display: flex; gap: var(--s-2); }

.kanban-modal-comments {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-left: var(--s-5);
}
.kanban-modal-comments-loading,
.kanban-modal-comments-empty {
  font-size: 0.8125rem;
  color: var(--fg-faint);
  font-style: italic;
  margin: 0;
}
.kanban-modal-comment {
  display: flex;
  gap: var(--s-2);
  align-items: flex-start;
}
.kanban-modal-comment-body {
  flex: 1;
  background: color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
  border-radius: 6px;
  padding: var(--s-2) var(--s-3);
  min-width: 0;
}
.kanban-modal-comment-meta {
  font-size: 0.75rem;
  color: var(--fg-dim);
  margin-bottom: 2px;
  display: flex;
  gap: var(--s-2);
  align-items: baseline;
}
.kanban-modal-comment-meta strong { font-weight: 600; color: var(--fg); }
.kanban-modal-comment-when { color: var(--fg-faint); }
.kanban-modal-comment-text {
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--fg);
  white-space: pre-wrap;
  word-break: break-word;
}
.kanban-modal-comment-text strong { font-weight: 600; }
.kanban-modal-comment-text em { font-style: italic; }
.kanban-modal-comment-text code {
  background: color-mix(in srgb, var(--fg-dim) 15%, transparent);
  border-radius: 3px;
  padding: 1px 4px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.8125rem;
}
.kanban-modal-comment-text a {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.kanban-mention {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--fg);
  padding: 1px 5px;
  border-radius: 3px;
  font-weight: 500;
}

/* --- checklists dentro del modal --- */
.kanban-modal-checklists {
  margin-left: var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.kanban-modal-checklist {
  background: color-mix(in srgb, var(--fg-dim) 6%, var(--bg));
  border-radius: 6px;
  padding: var(--s-3);
}
.kanban-modal-checklist-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.kanban-modal-checklist-title {
  flex: 1;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
}
.kanban-modal-checklist-progress {
  font-size: 0.6875rem;
  color: var(--fg-dim);
  font-weight: 500;
}
.kanban-modal-checklist-del {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px var(--s-1);
  cursor: pointer;
  border-radius: 4px;
}
.kanban-modal-checklist-del:hover { color: var(--err); }

.kanban-modal-checklist-bar {
  height: 4px;
  background: color-mix(in srgb, var(--fg-dim) 20%, transparent);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-bottom: var(--s-2);
}
.kanban-modal-checklist-bar > span {
  display: block;
  height: 100%;
  background: var(--lime);
  transition: width var(--t-base) var(--ease);
}

.kanban-modal-checklist-items {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kanban-modal-checklist-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 4px var(--s-1);
  border-radius: 4px;
}
.kanban-modal-checklist-item:hover {
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}
.kanban-modal-checklist-item-text {
  flex: 1;
  font-size: 0.875rem;
  color: var(--fg);
}
.kanban-modal-checklist-item.is-done .kanban-modal-checklist-item-text {
  color: var(--fg-faint);
  text-decoration: line-through;
}
.kanban-modal-checklist-item-del {
  background: transparent;
  border: none;
  color: transparent;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0 var(--s-1);
  border-radius: 4px;
}
.kanban-modal-checklist-item:hover .kanban-modal-checklist-item-del {
  color: var(--fg-faint);
}
.kanban-modal-checklist-item-del:hover {
  color: var(--err) !important;
}

.kanban-modal-checklist-item-new {
  margin-top: var(--s-2);
}
.kanban-modal-checklist-item-new input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  padding: 4px var(--s-2);
  font: inherit;
  font-size: 0.8125rem;
}
.kanban-modal-checklist-item-new input:focus {
  outline: none;
  border-color: var(--fg-dim);
}

.kanban-modal-checklist-new {
  margin-left: var(--s-5);
  margin-top: var(--s-3);
}
.kanban-modal-checklist-new-input {
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: 1px dashed var(--line-strong);
  border-radius: 4px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  font-size: 0.875rem;
}
.kanban-modal-checklist-new-input:focus {
  outline: none;
  border-color: var(--fg-dim);
  background: var(--bg);
}

/* Card-front checklist badge */
.kanban-card-checklist-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.75rem;
  color: var(--fg-faint);
}
.kanban-card-checklist-badge svg { width: 12px; height: 12px; }
.kanban-card-checklist-badge.is-complete {
  background: color-mix(in srgb, var(--lime) 22%, transparent);
  color: color-mix(in srgb, var(--lime) 70%, var(--fg));
  padding: 1px var(--s-2);
  border-radius: 4px;
}

/* --- activity log dentro del modal (historial de eventos) --- */
.kanban-modal-activity-log {
  margin-left: var(--s-5);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
.kanban-modal-activity-log:empty { display: none; }
.kanban-modal-activity-header {
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--s-1);
  font-weight: 500;
}
.kanban-modal-activity-item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
  font-size: 0.75rem;
  color: var(--fg-dim);
  line-height: 1.5;
  padding: 2px 0;
}
.kanban-modal-activity-text {
  flex: 1;
  word-break: break-word;
}
.kanban-modal-activity-when {
  color: var(--fg-faint);
  flex-shrink: 0;
  font-size: 0.6875rem;
}

/* --- avatar genérico --- */
.kanban-avatar {
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 600;
  flex-shrink: 0;
  user-select: none;
}

/* --- sidebar --- */
.kanban-modal-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
.kanban-modal-sidebar-title {
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.1em;
  margin: 0 0 var(--s-1) 0;
  font-weight: 600;
}
.kanban-modal-sidebar-title-actions { margin-top: var(--s-3); }

.kanban-modal-sidebar-btn {
  background: color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
  color: var(--fg);
  border: none;
  border-radius: 6px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
  text-align: left;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  transition: background var(--t-fast) var(--ease);
}
.kanban-modal-sidebar-btn svg { width: 14px; height: 14px; color: var(--fg-dim); flex-shrink: 0; }
.kanban-modal-sidebar-btn:hover {
  background: color-mix(in srgb, var(--fg-dim) 16%, var(--bg));
}
.kanban-modal-sidebar-btn.is-open {
  background: color-mix(in srgb, var(--fg-dim) 16%, var(--bg));
}
.kanban-modal-archive { color: var(--fg-dim); }
.kanban-modal-archive:hover {
  color: var(--err);
  background: color-mix(in srgb, var(--err) 10%, var(--bg));
}
.kanban-modal-archive:hover svg { color: var(--err); }

.kanban-modal-watch { color: var(--fg-dim); }
.kanban-modal-watch.is-watching {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg));
  color: var(--fg);
}
.kanban-modal-watch.is-watching svg { color: var(--accent); }

/* --- paneles (colapsables, dentro de sidebar) --- */
.kanban-modal-panel {
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: var(--s-3);
  margin: var(--s-1) 0 var(--s-2);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  box-shadow: var(--shadow-low);
}
.kanban-modal-panel-help {
  margin: 0;
  font-size: 0.75rem;
  color: var(--fg-faint);
  line-height: 1.4;
}
.kanban-modal-panel-link {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  font: inherit;
  font-size: 0.75rem;
  text-align: left;
  padding: 4px 0;
  cursor: pointer;
  text-decoration: underline;
}
.kanban-modal-panel-link:hover { color: var(--fg); }

/* --- members panel --- */
.kanban-modal-members-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kanban-modal-member {
  background: transparent;
  border: none;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  color: var(--fg);
}
.kanban-modal-member:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.kanban-modal-member.is-active {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.kanban-modal-member .kanban-avatar { width: 24px; height: 24px; font-size: 0.7rem; }
.kanban-modal-member-name { flex: 1; }
.kanban-modal-member-check { width: 14px; height: 14px; color: var(--fg); }

/* --- labels picker --- */
.kanban-modal-labels-picker {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kanban-modal-label-picker-item {
  background: transparent;
  border: none;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
}
.kanban-modal-label-picker-item:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.kanban-modal-label-pill {
  width: 28px;
  height: 14px;
  border-radius: 3px;
  flex-shrink: 0;
}
.kanban-modal-label-pickname { flex: 1; color: var(--fg); }
.kanban-modal-label-check { width: 14px; height: 14px; color: var(--fg); }

.kanban-modal-label-new {
  background: transparent;
  border: 1px dashed var(--line-strong);
  color: var(--fg-dim);
  padding: 6px var(--s-2);
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.75rem;
  text-align: center;
  margin-top: var(--s-1);
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.kanban-modal-label-new:hover { border-color: var(--fg-dim); color: var(--fg); }

.kanban-modal-label-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-1);
  padding: var(--s-2);
  background: color-mix(in srgb, var(--fg-dim) 8%, var(--bg));
  border-radius: 6px;
}
.kanban-modal-label-form-input {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  padding: 6px var(--s-2);
  font: inherit;
  font-size: 0.875rem;
}
.kanban-modal-label-form-input:focus { outline: none; border-color: var(--fg-dim); }

/* Grid 4x2 para 8 colores. min-width:0 evita el desbordamiento clásico
   de items de grid con contenido mínimo browser-default. */
.kanban-modal-label-form-colors {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-1);
}
.kanban-modal-label-form-colors button {
  min-width: 0;
  width: 100%;
  aspect-ratio: 1;
  border: 2px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  padding: 0;
  transition: transform var(--t-fast) var(--ease-spring);
}
.kanban-modal-label-form-colors button:hover { transform: scale(1.1); }
.kanban-modal-label-form-colors button.is-picked {
  border-color: var(--fg);
  box-shadow: 0 0 0 2px var(--bg) inset;
}
.kanban-modal-label-form-actions {
  display: flex;
  gap: var(--s-2);
}

/* --- due picker --- */
.kanban-modal-due {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  padding: 6px var(--s-2);
  font: inherit;
  font-size: 0.875rem;
}
.kanban-modal-due:focus { outline: none; border-color: var(--fg-dim); }
.kanban-modal-reminder {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  padding: 6px var(--s-2);
  font: inherit;
  font-size: 0.875rem;
  width: 100%;
}
.kanban-modal-reminder:focus { outline: none; border-color: var(--fg-dim); }

/* --- cover colors picker (4x2 sin clear, clear como link debajo) --- */
.kanban-modal-cover-colors {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-1);
}
.kanban-modal-cover-colors button {
  min-width: 0;
  width: 100%;
  aspect-ratio: 1;
  border: 2px solid transparent;
  border-radius: 5px;
  cursor: pointer;
  padding: 0;
  transition: transform var(--t-fast) var(--ease-spring);
}
.kanban-modal-cover-colors button:hover { transform: scale(1.08); }
.kanban-modal-cover-colors button:active { transform: scale(0.92); }
.kanban-modal-cover-colors button.is-active {
  border-color: var(--fg);
  box-shadow: 0 0 0 2px var(--bg) inset;
}

/* --- filters bar --- */
.kanban-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-3);
  margin-bottom: var(--s-3);
  background: color-mix(in srgb, var(--fg-dim) 6%, var(--bg));
  border-radius: 8px;
  align-items: center;
}
.kanban-filters[hidden] { display: none !important; }
.kanban-filters-search {
  flex: 1 1 220px;
  min-width: 160px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  padding: var(--s-2) var(--s-3);
  font: inherit;
  font-size: 0.875rem;
}
.kanban-filters-search:focus {
  outline: none;
  border-color: var(--fg-dim);
}
.kanban-filters-group {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.kanban-filters-label {
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}
.kanban-filters-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
}
.kanban-filters-pill {
  background: var(--bg);
  border: 1px solid var(--line-strong);
  color: var(--fg-dim);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font: inherit;
  font-size: 0.75rem;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.kanban-filters-pill:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
}
.kanban-filters-pill.is-active {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
.kanban-filters-pill-color {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: inline-block;
}
.kanban-filters-clear {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
  text-decoration: underline;
  padding: 4px 8px;
}
.kanban-filters-clear:hover { color: var(--fg); }

.kanban-header-link-count {
  background: var(--fg);
  color: var(--bg);
  font-size: 0.65rem;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  min-width: 16px;
  text-align: center;
  margin-left: var(--s-1);
}

/* --- header link (archivo) --- */
.kanban-header-link {
  margin-left: auto;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid var(--line-strong);
  padding: var(--s-2) var(--s-3);
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  text-decoration: none;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
}
.kanban-header-link svg { width: 14px; height: 14px; }
.kanban-header-link:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}
/* Cuando hay archivo link + nueva tarjeta, no aplicar el margin-left:auto a este último */
.kanban-header-link + .kanban-new-card-button { margin-left: 0; }
/* Solo el PRIMER link de la cadena absorbe el espacio a la izquierda;
   los siguientes (imagen / quitar imagen) se acoplan con el gap del
   flex parent. Sin esto cada link con margin-left:auto compite por
   espacio y rompe la cabecera del board. */
.kanban-header-link ~ .kanban-header-link { margin-left: 0; }
/* El atributo HTML `hidden` debería ocultar el elemento, pero
   display: inline-flex lo sobrescribe — restituir comportamiento. */
.kanban-header-link[hidden] { display: none; }
/* WO BOARD WALLPAPER BACKGROUND (2026-05-24): separador visual sutil
   antes del control "fondo del tablero" para no confundirlo con
   "imagen del orbe" (controles vecinos, capacidades distintas). */
.kanban-header-bg-image {
  margin-left: var(--s-2) !important;
  border-left: 1px solid color-mix(in srgb, var(--fg-dim) 30%, transparent);
  padding-left: calc(var(--s-3) + var(--s-2));
}

/* Control "cambiar imagen del orbe": discreto pero ACTIVO. Usa
   fg-dim (mismo tier que filtros/archivo) en lugar de fg-faint —
   con fg-faint se leía como deshabilitado. Hover roja para indicar
   acción destructiva sin chillar en reposo. */
.kanban-header-orb-image-remove {
  color: var(--fg-dim);
}
.kanban-header-orb-image-remove:hover {
  color: var(--err);
  border-color: var(--err);
  background: color-mix(in srgb, var(--err) 6%, transparent);
}
/* Feedback humano post-upload/delete: pequeño, junto a los controles.
   data-kind="error" lo tinta de --err sin chillar. Auto-hide tras 3s
   lo gestiona JS — sin animation infinita. */
.kanban-orb-image-feedback {
  font-size: 0.8125rem;
  color: var(--fg-dim);
  padding: var(--s-1) var(--s-2);
  line-height: 1;
  white-space: nowrap;
}
.kanban-orb-image-feedback[data-kind="error"] {
  color: var(--err);
}
.kanban-orb-image-feedback[hidden] { display: none; }

/* --- vista de archivo --- */
.kanban-archivo-back {
  color: var(--fg-dim);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: 0.875rem;
  padding: var(--s-1) var(--s-2);
  border-radius: 6px;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.kanban-archivo-back svg { width: 14px; height: 14px; }
.kanban-archivo-back:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.kanban-archivo-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--fg);
  line-height: 1.1;
}
.kanban-archivo-help {
  margin: var(--s-2) 0 0;
  font-size: 0.8125rem;
  color: var(--fg-dim);
  max-width: 60ch;
  line-height: 1.5;
}

.kanban-archivo-section {
  margin-bottom: var(--s-5);
  max-width: 760px;
}
.kanban-archivo-section-title {
  margin: 0 0 var(--s-2);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.005em;
  text-transform: lowercase;
}

.kanban-archivo-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  max-width: 760px;
}

.kanban-archivo-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: var(--s-3);
  box-shadow: var(--shadow-low);
  transition:
    transform var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
  overflow: hidden;
  position: relative;
}
.kanban-archivo-item:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-med);
}
.kanban-archivo-cover {
  width: 4px;
  align-self: stretch;
  border-radius: 2px;
  flex-shrink: 0;
}
.kanban-archivo-body {
  flex: 1;
  min-width: 0;
}
.kanban-archivo-card-title {
  margin: 0 0 var(--s-1) 0;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--fg);
  line-height: 1.3;
  word-break: break-word;
}
.kanban-archivo-meta {
  font-size: 0.75rem;
  color: var(--fg-faint);
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.kanban-archivo-meta strong {
  color: var(--fg-dim);
  font-weight: 500;
}
.kanban-archivo-sep { color: var(--line-strong); }

.kanban-archivo-restore {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--line-strong);
  padding: var(--s-2) var(--s-3);
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  flex-shrink: 0;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring);
}
.kanban-archivo-restore:hover:not(:disabled) {
  color: var(--fg);
  border-color: var(--fg);
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}
.kanban-archivo-restore:active:not(:disabled) {
  transform: scale(0.96);
}
.kanban-archivo-restore:disabled {
  opacity: 0.5;
  cursor: wait;
}

.kanban-archivo-empty {
  color: var(--fg-faint);
  font-style: italic;
  margin: var(--s-3) 0;
  font-size: 0.875rem;
}

/* --- atajos de teclado overlay --- */
.kanban-shortcuts-help {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-4);
  animation: kanban-fade-in var(--t-fast) var(--ease);
}
.kanban-shortcuts-help-card {
  background: var(--bg);
  border-radius: var(--radius-modal);
  padding: var(--s-5);
  max-width: 480px;
  width: 100%;
  box-shadow: var(--shadow-high);
  animation: kanban-modal-pop 180ms var(--ease-spring);
}
.kanban-shortcuts-help-card h2 {
  margin: 0 0 var(--s-3) 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--fg);
}
.kanban-shortcuts-help-card table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
}
.kanban-shortcuts-help-card th {
  text-align: left;
  padding: var(--s-2) 0 var(--s-1);
  font-size: 0.6875rem;
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.08em;
  font-weight: 500;
  border-bottom: 1px solid var(--line);
}
.kanban-shortcuts-help-card td {
  padding: 4px 0;
  color: var(--fg-dim);
}
.kanban-shortcuts-help-card td:first-child {
  width: 35%;
  white-space: nowrap;
}
.kanban-shortcuts-help-card kbd {
  background: color-mix(in srgb, var(--fg-dim) 12%, var(--bg));
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  padding: 2px 6px;
  font-family: ui-monospace, monospace;
  font-size: 0.75rem;
  color: var(--fg);
  margin-right: 2px;
}
.kanban-shortcuts-help-close {
  margin-top: var(--s-3);
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: var(--s-2) var(--s-4);
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
}

/* --- toast --- */
.kanban-toast {
  position: fixed;
  bottom: var(--s-4);
  left: 50%;
  transform: translateX(-50%);
  background: var(--err);
  color: #fff;
  padding: var(--s-2) var(--s-4);
  border-radius: 6px;
  font-size: 0.875rem;
  font-weight: 500;
  z-index: 200;
  box-shadow: var(--shadow-high);
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}
.kanban-toast[hidden] { display: none !important; }
.kanban-toast[data-type="warn"] { background: var(--warn); }
.kanban-toast[data-type="info"] { background: var(--accent); }

.kanban-toast-action {
  background: transparent;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.45);
  padding: 2px 10px;
  border-radius: 4px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-transform: lowercase;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.kanban-toast-action:hover {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.7);
}
.kanban-toast-action:active {
  background: rgba(255, 255, 255, 0.25);
}
.kanban-toast-action:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}


/* =========================================================================
   Microinteracciones — feedback en TODO clicable
   ========================================================================= */

.kanban-modal:not([hidden]) .kanban-modal-overlay {
  animation: kanban-fade-in var(--t-fast) var(--ease);
}
.kanban-modal:not([hidden]) .kanban-modal-dialog {
  animation: kanban-modal-pop 180ms var(--ease-spring);
}
@keyframes kanban-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes kanban-modal-pop {
  from { opacity: 0; transform: scale(0.96) translateY(-12px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

.kanban-toast:not([hidden]) {
  animation: kanban-toast-slide 220ms var(--ease-spring);
}
@keyframes kanban-toast-slide {
  from { opacity: 0; transform: translate(-50%, 24px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

.kanban-modal-panel:not([hidden]) {
  animation: kanban-panel-pop 160ms var(--ease);
}
@keyframes kanban-panel-pop {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.kanban-card-composer { animation: kanban-fade-in var(--t-fast) var(--ease); }

/* topbar bell + links — definidos en bell.css / topbar block original, pero
   añadimos feedback consistente aquí */
.topbar-bell {
  transition:
    color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring),
    background var(--t-fast) var(--ease);
  border-radius: 6px;
}
.topbar-bell:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  transform: scale(1.08);
}
.topbar-bell:active { transform: scale(0.94); }

.topbar-link::after {
  content: "";
  position: absolute;
  bottom: -2px;
  left: var(--s-1);
  right: var(--s-1);
  height: 1px;
  background: var(--fg);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-fast) var(--ease);
}
.topbar-link { position: relative; }
.topbar-link:hover { color: var(--fg); }
.topbar-link:hover::after { transform: scaleX(1); }
.topbar-link.is-active::after { background: var(--lime); transform: scaleX(1); }

/* sidebar buttons subtle feedback */
.kanban-modal-sidebar-btn:hover { transform: translateX(2px); transition-property: background, transform; }
.kanban-modal-sidebar-btn:active { transform: translateX(0) scale(0.98); transition-duration: 60ms; }
.kanban-modal-sidebar-btn { transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); }

/* avatares hover */
.kanban-modal-member .kanban-avatar {
  transition: transform var(--t-fast) var(--ease-spring);
}
.kanban-modal-member:hover .kanban-avatar { transform: scale(1.08); }
.kanban-modal-member:active { transform: scale(0.98); transition-duration: 60ms; }

/* label pills hover */
.kanban-modal-label-picker-item {
  transition: background var(--t-fast) var(--ease);
}
.kanban-modal-label-picker-item:hover .kanban-modal-label-pill {
  transform: scale(1.08);
}
.kanban-modal-label-pill { transition: transform var(--t-fast) var(--ease-spring); }

/* comments hover */
.kanban-modal-comment {
  border-radius: 6px;
  padding: 4px;
  margin: -4px;
  transition: background var(--t-fast) var(--ease);
}
.kanban-modal-comment:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); }
.kanban-modal-comment .kanban-avatar { transition: transform var(--t-fast) var(--ease-spring); }
.kanban-modal-comment:hover .kanban-avatar { transform: scale(1.05); }

/* desc preview hint */
.kanban-modal-description-preview::after {
  content: "click para editar";
  position: absolute;
  right: var(--s-3);
  top: var(--s-2);
  font-size: 0.6875rem;
  color: var(--fg-faint);
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
  pointer-events: none;
}
.kanban-modal-description-preview:hover::after { opacity: 1; }

/* respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .kanban-modal:not([hidden]) .kanban-modal-dialog,
  .kanban-modal:not([hidden]) .kanban-modal-overlay,
  .kanban-toast:not([hidden]),
  .kanban-modal-panel:not([hidden]),
  .kanban-card-composer { animation: none; }
  .kanban-card, .kanban-modal-sidebar-btn, .kanban-modal-member,
  .topbar-bell, .kanban-new-card-button, .kanban-card-composer-add,
  .kanban-column-composer-add,
  .kanban-modal-btn-primary, .kanban-modal-close, .kanban-column-add {
    transition: none !important;
    animation: none !important;
  }
  .kanban-card:hover, .kanban-card:active { transform: none; }
}

/* --- index --- */
.kanban-index-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 0.5rem;
}
.kanban-index-list li a {
  display: block; padding: 0.5rem 0.75rem;
  background: var(--line);
  border: 1px solid var(--line-strong); border-radius: 6px;
  color: var(--fg); text-decoration: none;
}
.kanban-index-list li a:hover { background: var(--bg); }

/* =========================================================
   Bell modal — alertas por correo
   ========================================================= */

.topbar-bell {
  background: transparent;
  border: none;
  padding: 0.3rem 0.4rem;
  margin: 0;
  cursor: pointer;
  color: var(--fg-faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast) var(--ease);
  line-height: 0;
}
.topbar-bell:hover { color: var(--fg-dim); }
.topbar-bell:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
  border-radius: 4px;
}
.topbar-bell svg {
  width: 16px;
  height: 16px;
  display: block;
}

.bell-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 6rem 1rem 2rem;
  overflow-y: auto;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
.bell-modal[hidden] { display: none; }
.bell-modal:not([hidden]) {
  animation: kanban-fade-in var(--t-fast) var(--ease);
}
.bell-modal:not([hidden]) .bell-modal-card {
  animation: kanban-modal-pop 180ms var(--ease-spring);
}
body.bell-modal-open { overflow: hidden; }

.bell-modal-card {
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  width: 100%;
  max-width: 420px;
  padding: 1.5rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}

.bell-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.bell-modal-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--fg);
  letter-spacing: -0.01em;
}
.bell-modal-close {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  border-radius: 4px;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.bell-modal-close:hover {
  color: var(--fg);
  background: var(--line);
}

.bell-modal-help {
  margin: 0 0 1rem 0;
  font-size: var(--text-sm);
  color: var(--fg-dim);
  line-height: 1.5;
}

.bell-modal-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-height: 220px;
  overflow-y: auto;
}
.bell-modal-empty {
  font-size: var(--text-sm);
  color: var(--fg-faint);
  font-style: italic;
  padding: 0.5rem 0;
}
.bell-modal-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.6rem;
  background: var(--line);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
}
.bell-modal-email {
  font-size: var(--text-sm);
  color: var(--fg);
  font-family: inherit;
  word-break: break-all;
  flex: 1 1 auto;
}
.bell-modal-remove {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.2rem 0.45rem;
  cursor: pointer;
  border-radius: 4px;
  flex: 0 0 auto;
  transition:
    color var(--t-fast) var(--ease),
    background var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring);
}
.bell-modal-remove:hover {
  color: var(--err);
  background: color-mix(in srgb, var(--err) 15%, transparent);
  transform: scale(1.1);
}
.bell-modal-remove:active {
  transform: scale(0.9);
  transition-duration: 60ms;
}

.bell-modal-form {
  display: flex;
  gap: 0.5rem;
}
.bell-modal-input {
  flex: 1 1 auto;
  padding: 0.5rem 0.75rem;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  font: inherit;
  font-size: var(--text-sm);
  transition: border-color var(--t-fast) var(--ease);
}
.bell-modal-input::placeholder { color: var(--fg-faint); }
.bell-modal-input:focus {
  outline: none;
  border-color: var(--fg-dim);
}
.bell-modal-add {
  padding: 0.5rem 1rem;
  background: var(--fg);
  color: var(--bg);
  border: none;
  border-radius: 6px;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition:
    opacity var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease-spring),
    box-shadow var(--t-fast) var(--ease);
  box-shadow: 0 2px 4px rgba(0,0,0,0.08);
}
.bell-modal-add:hover {
  opacity: 0.92;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.bell-modal-add:active {
  transform: translateY(0);
  transition-duration: 60ms;
}

.bell-modal-error {
  margin-top: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: color-mix(in srgb, var(--err) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--err) 30%, transparent);
  color: var(--err);
  font-size: var(--text-sm);
  border-radius: 6px;
}

/* ===== confirmación in-app (sustituye window.confirm) ===== */
.kanban-confirm {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-4);
}
.kanban-confirm-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  cursor: pointer;
}
.kanban-confirm-dialog {
  position: relative;
  background: var(--bg);
  border-radius: var(--radius-modal);
  width: min(420px, 95vw);
  padding: var(--s-5);
  box-shadow: var(--shadow-high);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.kanban-confirm-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--fg);
}
.kanban-confirm-message {
  margin: 0;
  color: var(--fg-dim);
  font-size: 0.875rem;
  line-height: 1.5;
}
.kanban-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.kanban-modal-btn-primary.is-danger {
  background: var(--err);
  color: #fff;
}
.kanban-modal-btn-primary.is-danger:hover {
  opacity: 0.92;
}

/* ============================================================
   HOME FUTURE CORE V2 — F1 MEMORIA + F4 PRÓXIMO + F2 PULSO
   WO HOME-FUTURE-CORE-V2-IMPLEMENT-2026-05-17

   Tres adiciones a la home cockpit, cero capas debajo de los orbes:
     1) .greeting-context: micro-señal bajo el saludo (F1 MEMORIA).
        "↳ estabas en marketing." — fade-in 1600ms.
     2) .next-line: UNA frase con el próximo movimiento (F4 PRÓXIMO),
        microcopy variable por data-next-kind. fade-in 1900ms.
     3) `--heartbeat-duration` controla el ritmo del dot del enso
        hero (F2 PULSO) — la variable se setea en :root, JS la modula
        tras polling. Reduced-motion la deja estable.

   NO se reabren halos/dots lime sobre orbes. NO hay pie de latido.
   NO hay banda "qué se mueve ahora". Cero capa nueva debajo de orbes.
   ============================================================ */

.greeting-context {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  letter-spacing: 0.01em;
  line-height: 1.5;
  margin: 0.4rem 0 0;
  text-align: center;
  opacity: 0;
  /* Sub-fase 2A (2026-05-18): memoria transitoria, en cascada con .greeting.
     Se va ~120ms más tarde que el saludo para dar sensación de orden. */
  animation:
    home-fade-in 380ms var(--ease) 1600ms forwards,
    home-greeting-disperse 1200ms cubic-bezier(0.4, 0, 0.2, 1) 4980ms forwards;
}

/* `.next-line` — la frase del próximo movimiento. UNA línea. */
.next-line {
  width: 100%;
  max-width: 720px;
  margin: 1.2rem auto 0;
  text-align: center;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--fg);
  letter-spacing: -0.005em;
  opacity: 0;
  animation: home-fade-in 460ms var(--ease) 1900ms forwards;
  /* Permitir wrap a 2 líneas sin romper layout en viewports estrechos. */
  display: block;
  word-break: normal;
  overflow-wrap: break-word;
}

/* Cuerpo de la frase: color condicional por kind.
   kinds con dato accionable (overdue/mention/due_soon) → --fg pleno.
   kinds informativos (risk/active_now) → --fg-dim (señal media).
   calm → --fg-faint (señal baja, "todo al día"). */
.next-line .next-text {
  display: inline;
}
.next-line[data-next-kind="overdue"] .next-text,
.next-line[data-next-kind="mention"] .next-text,
.next-line[data-next-kind="due_soon"] .next-text {
  color: var(--fg);
}
.next-line[data-next-kind="risk"] .next-text,
.next-line[data-next-kind="active_now"] .next-text {
  color: var(--fg-dim);
}
.next-line[data-next-kind="calm"] .next-text {
  color: var(--fg-faint);
}

/* CTA inline: action_text + flecha. Border-bottom sutil, hover lime
   con transición rápida. El verde se reserva como señal funcional
   (focus/hover/submit) consistente con resto de Kaizen. */
.next-line .next-action {
  margin-left: 0.55em;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition:
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
  white-space: nowrap;
}
.next-line .next-action:hover {
  color: var(--lime);
  border-color: var(--lime);
}
.next-line .next-action:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 3px;
  border-color: transparent;
}

/* Mobile (≤520px): la frase puede wrappear; reservamos algo de aire.
   No-scroll plan: orbes 76px + state 12px + label 16px = ~104px; con
   greeting-context 16px + next-line ~2 líneas = ~36px + spacing ~24px,
   sobra margen en mobile típico 620-700px de viewport útil. */
@media (max-width: 520px) {
  .next-line {
    font-size: var(--text-sm);
    margin-top: 1rem;
    padding: 0 0.5rem;
  }
  .next-line .next-action {
    display: inline-block;
    margin-top: 0.15rem;
  }
}

/* Reduced-motion: entradas directas + heartbeat estable.
   Apple HIG / WCAG 2.3.3: multi-speed motion (heartbeat 1.4-3.6s)
   debe desactivarse con prefers-reduced-motion. El dot ya está
   cubierto arriba (animation: none en bloque reduced-motion global).
   Aquí garantizamos que greeting-context y next-line aparecen sin
   animación y que el heartbeat dinámico no se aplica (JS guarda
   matchMedia). */
@media (prefers-reduced-motion: reduce) {
  .greeting-context, .next-line {
    animation: none;
    opacity: 1;
  }
  /* Si JS no respetara la guardia, el CSS forzaría 2.4s neutro. */
  body.page-home .hero-enso .dot {
    animation-duration: 2.4s;
  }
}


/* ============================================================
   HOME FUTURE EXPERIENCE V3.1 PORT (2026-05-18, WO V3-1-PORT)
   Solo aplica a body.page-home. Capa aditiva sobre V2:
   - enso pasa de logo a sensor (focus marker, amber arc, build arc,
     anillo terciario, spark, halo);
   - .next-line se transforma en signal panel con axis vertical + label
     uppercase derivado de data-next-kind por CSS (sin tocar texto);
   - feed-line descendente cuando hay href de decisión;
   - .is-next-target halo en el orbe cuyo href coincide;
   - continuity glyph violeta como hilo de memoria;
   - reduced-motion: sin motion variable; el color y la forma siguen
     comunicando estado.
   Estado visual: html[data-next-state] ∈ decision|risk|active|calm|empty.
   ============================================================ */

body.page-home {
  /* tokens V3.1 — se mantienen en ambos color schemes (cyan/amber/violet
     calibrados para legibilidad sobre --bg light y dark). */
  --v31-signal-cyan:    #0E7490;
  --v31-signal-cyan-soft: rgba(14, 116, 144, 0.18);
  --v31-evidence-amber: #B45309;
  --v31-evidence-amber-soft: rgba(180, 83, 9, 0.18);
  --v31-memory-violet:  #6D28D9;
  --v31-memory-violet-soft: rgba(109, 40, 217, 0.16);
  --v31-build-graphite: #3F3F46;
  --v31-build-graphite-soft: rgba(63, 63, 70, 0.22);
  --v31-rail-default:   var(--fg);
}
@media (prefers-color-scheme: dark) {
  body.page-home {
    --v31-signal-cyan:    #22D3EE;
    --v31-signal-cyan-soft: rgba(34, 211, 238, 0.18);
    --v31-evidence-amber: #F59E0B;
    --v31-evidence-amber-soft: rgba(245, 158, 11, 0.18);
    --v31-memory-violet:  #A78BFA;
    --v31-memory-violet-soft: rgba(167, 139, 250, 0.18);
    --v31-build-graphite: #A1A1AA;
    --v31-build-graphite-soft: rgba(161, 161, 170, 0.22);
  }
}

/* ---------- enso reactor (V3.1) ---------- */

body.page-home .hero-enso .enso-ring-secondary {
  stroke: var(--line-strong);
  stroke-width: 0.6;
  opacity: 0.55;
  transition: stroke 400ms var(--ease), opacity 400ms var(--ease);
}
body.page-home .hero-enso .enso-ring-tertiary {
  stroke: var(--line-strong);
  stroke-width: 0.5;
  stroke-dasharray: 1 4;
  opacity: 0.35;
  transition: opacity 400ms var(--ease);
}
body.page-home .hero-enso .enso-spark {
  fill: var(--fg);
  opacity: 0;
  transition: opacity 500ms var(--ease);
}
body.page-home .hero-enso .enso-ring-risk-segment {
  stroke: var(--v31-evidence-amber);
  stroke-width: 4.5;
  stroke-linecap: round;
  stroke-dasharray: 22 100;
  stroke-dashoffset: -58;
  opacity: 0;
  transition: opacity 500ms var(--ease);
  filter: drop-shadow(0 0 5px rgba(180, 83, 9, 0.45));
}
body.page-home .hero-enso .enso-ring-build-arc {
  stroke: var(--v31-build-graphite);
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-dasharray: 32 100;
  stroke-dashoffset: -2;
  opacity: 0;
  transition: opacity 500ms var(--ease);
}
body.page-home .hero-enso .enso-dot-halo {
  stroke: var(--v31-signal-cyan);
  stroke-width: 1.1;
  opacity: 0;
  transition: opacity 500ms var(--ease);
}
body.page-home .hero-enso .enso-focus-marker {
  fill: var(--v31-signal-cyan);
  opacity: 0;
  transition: opacity 500ms var(--ease);
  filter: drop-shadow(0 0 5px rgba(14, 116, 144, 0.55));
}

/* visible state por html[data-next-state]. ENSO=instrumento, no logo. */
html[data-next-state="decision"] body.page-home .hero-enso .enso-focus-marker { opacity: 1; }
html[data-next-state="decision"] body.page-home .hero-enso .enso-dot-halo     { opacity: 0.75; }

html[data-next-state="risk"] body.page-home .hero-enso .enso-ring-risk-segment { opacity: 1; }

html[data-next-state="active"] body.page-home .hero-enso .enso-ring-build-arc { opacity: 1; }

html[data-next-state="empty"] body.page-home .hero-enso .enso-spark { opacity: 1; }
html[data-next-state="empty"] body.page-home .hero-enso .ring { stroke-opacity: 0.55; }

/* ---------- signal panel (axis + body) ---------- */
/* sobreescribe .next-line V2 (text centrado plano) por composición rail+body */
body.page-home section.next-line[data-next-line] {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 16px;
  width: 100%;
  max-width: 640px;
  margin: 1.2rem auto 0;
  padding: 0;
  text-align: left;
  /* preserva fade-in V2 (animation: home-fade-in 460ms ease 1900ms) */
}
body.page-home .next-line .signal-axis {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 14px;
  min-height: 70px;
}
body.page-home .next-line .signal-axis-top {
  width: 1px;
  height: 14px;
  background: linear-gradient(180deg, transparent 0%, var(--v31-rail-default) 100%);
  opacity: 0.4;
  transition: background 400ms var(--ease);
}
body.page-home .next-line .signal-axis-rail {
  width: 3px;
  flex: 1;
  background: var(--v31-rail-default);
  border-radius: 2px;
  min-height: 56px;
  transition: background 400ms var(--ease), box-shadow 400ms var(--ease);
}
body.page-home .next-line .signal-axis-bottom {
  width: 1px;
  height: 14px;
  background: linear-gradient(180deg, var(--v31-rail-default) 0%, transparent 100%);
  opacity: 0.4;
  transition: background 400ms var(--ease);
}

body.page-home .next-line .signal-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 1px 0 1px 0;
  min-width: 0;
}

/* label uppercase mono pequeño. Texto inyectado por CSS según state
   (no se toca el JSON del backend; el texto operativo sigue siendo
   data-next-text). Permite que la columna lea como instrumento sin
   replicar copy en HTML/JS. */
body.page-home .next-line .signal-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--v31-rail-default);
  margin-bottom: 2px;
  transition: color 400ms var(--ease);
}
body.page-home .next-line .signal-label::before { content: ""; }
html[data-next-state="decision"] body.page-home .next-line .signal-label::before { content: "Próximo movimiento"; }
html[data-next-state="risk"]     body.page-home .next-line .signal-label::before { content: "Señal abierta"; }
html[data-next-state="active"]   body.page-home .next-line .signal-label::before { content: "Actividad ahora"; }
html[data-next-state="calm"]     body.page-home .next-line .signal-label::before { content: "Sistema en calma"; }

/* texto principal (next-text) sube en peso y tamaño respecto a V2 */
body.page-home .next-line .next-text {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: 1.3;
  font-weight: 500;
  color: var(--fg);
  letter-spacing: -0.015em;
}
@media (max-width: 720px) {
  body.page-home .next-line .next-text { font-size: var(--text-base); }
}
/* sobrescribe el color condicional por kind del bloque V2; en V3.1 el
   texto principal SIEMPRE es --fg para máxima legibilidad — la jerarquía
   de severidad se comunica por el rail/label/halo, no por desaturar texto. */
body.page-home .next-line[data-next-kind="risk"]     .next-text,
body.page-home .next-line[data-next-kind="active_now"] .next-text,
body.page-home .next-line[data-next-kind="calm"]     .next-text {
  color: var(--fg);
}

/* action chip — V3.1 redondea, hover ya define V2 */
body.page-home .next-line .next-action {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 0 0;
  padding: 4px 12px;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--fg);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
  border-radius: 999px;
  white-space: nowrap;
}
body.page-home .next-line .next-action:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  border-color: var(--fg);
}

/* rail color por state */
html[data-next-state="decision"] body.page-home .next-line .signal-axis-rail,
html[data-next-state="decision"] body.page-home .next-line .signal-axis-top,
html[data-next-state="decision"] body.page-home .next-line .signal-axis-bottom {
  background: var(--v31-signal-cyan);
}
html[data-next-state="decision"] body.page-home .next-line .signal-axis-rail {
  box-shadow: 0 0 12px var(--v31-signal-cyan-soft);
}
html[data-next-state="decision"] body.page-home .next-line .signal-label { color: var(--v31-signal-cyan); }

html[data-next-state="risk"] body.page-home .next-line .signal-axis-rail,
html[data-next-state="risk"] body.page-home .next-line .signal-axis-top,
html[data-next-state="risk"] body.page-home .next-line .signal-axis-bottom {
  background: var(--v31-evidence-amber);
}
html[data-next-state="risk"] body.page-home .next-line .signal-axis-rail {
  box-shadow: 0 0 14px var(--v31-evidence-amber-soft);
}
html[data-next-state="risk"] body.page-home .next-line .signal-label { color: var(--v31-evidence-amber); }

html[data-next-state="active"] body.page-home .next-line .signal-axis-rail,
html[data-next-state="active"] body.page-home .next-line .signal-axis-top,
html[data-next-state="active"] body.page-home .next-line .signal-axis-bottom {
  background: var(--v31-build-graphite);
}
html[data-next-state="active"] body.page-home .next-line .signal-label { color: var(--v31-build-graphite); }

html[data-next-state="calm"] body.page-home .next-line .signal-axis-rail,
html[data-next-state="calm"] body.page-home .next-line .signal-axis-top,
html[data-next-state="calm"] body.page-home .next-line .signal-axis-bottom {
  background: var(--fg);
}
html[data-next-state="calm"] body.page-home .next-line .signal-label { color: var(--fg); }

/* ---------- signal feed-line (signal → orbe activo) ---------- */
body.page-home .signal-feed-line {
  width: 1px;
  height: 32px;
  margin: 4px auto 0;
  background: repeating-linear-gradient(
    180deg,
    var(--v31-rail-default) 0,
    var(--v31-rail-default) 3px,
    transparent 3px,
    transparent 7px
  );
  opacity: 0;
  transition: background 400ms var(--ease), opacity 400ms var(--ease);
}
/* mostrar feed-line solo si hay decisión accionable con href */
html[data-next-state="decision"] body.page-home section.next-line[data-next-href]:not([data-next-href=""]) ~ .signal-feed-line,
html[data-next-state="risk"]     body.page-home section.next-line[data-next-href]:not([data-next-href=""]) ~ .signal-feed-line {
  opacity: 0.85;
}
/* color del feed-line por estado */
html[data-next-state="decision"] body.page-home .signal-feed-line {
  background: repeating-linear-gradient(
    180deg,
    var(--v31-signal-cyan) 0,
    var(--v31-signal-cyan) 3px,
    transparent 3px,
    transparent 7px
  );
}
html[data-next-state="risk"] body.page-home .signal-feed-line {
  background: repeating-linear-gradient(
    180deg,
    var(--v31-evidence-amber) 0,
    var(--v31-evidence-amber) 3px,
    transparent 3px,
    transparent 7px
  );
}

/* ---------- continuity glyph (greeting-context) ---------- */
body.page-home .greeting-context-glyph {
  display: inline-block;
  width: 22px;
  height: 1.5px;
  background: var(--v31-memory-violet);
  position: relative;
  opacity: 0.9;
  margin-right: 8px;
  vertical-align: middle;
  border-radius: 1px;
}
body.page-home .greeting-context-glyph::before {
  content: "";
  position: absolute;
  left: -3px;
  top: -2.5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--v31-memory-violet);
}
body.page-home .greeting-context-glyph::after {
  content: "";
  position: absolute;
  right: -4px;
  top: -2.5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1.5px solid var(--v31-memory-violet);
  background: var(--bg);
}
body.page-home .greeting-context-text {
  /* texto preservado, color V2 ya aplicado por .greeting-context base */
}

/* ---------- .is-next-target halo en el orbe activo ---------- */
body.page-home .module-cell.is-next-target .module-disc {
  position: relative;
  transition: box-shadow 400ms var(--ease);
}
body.page-home .module-cell.is-next-target .module-disc::after {
  /* override del ::after V2 con un ring de estado V3.1 */
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 1.5px solid transparent;
  opacity: 1;
  pointer-events: none;
  transition: border-color 400ms var(--ease), box-shadow 400ms var(--ease);
}
html[data-next-state="decision"] body.page-home .module-cell.is-next-target .module-disc::after {
  border-color: var(--v31-signal-cyan);
  box-shadow: 0 0 0 4px var(--v31-signal-cyan-soft);
}
html[data-next-state="risk"] body.page-home .module-cell.is-next-target .module-disc::after {
  border-color: var(--v31-evidence-amber);
  box-shadow: 0 0 0 4px var(--v31-evidence-amber-soft);
}
html[data-next-state="active"] body.page-home .module-cell.is-next-target .module-disc::after {
  border-color: var(--v31-build-graphite);
  box-shadow: 0 0 0 3px var(--v31-build-graphite-soft);
}

/* ---------- mobile (520x800) — signal panel tighter ---------- */
@media (max-width: 520px) {
  body.page-home section.next-line[data-next-line] {
    grid-template-columns: 10px 1fr;
    gap: 12px;
    padding: 0 0.5rem;
  }
  body.page-home .next-line .signal-axis { width: 10px; min-height: 56px; }
  body.page-home .next-line .signal-axis-rail { width: 2.5px; min-height: 40px; }
  body.page-home .next-line .signal-label { font-size: 9.5px; letter-spacing: 0.18em; }
  body.page-home .signal-feed-line { height: 22px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  body.page-home .hero-enso .enso-ring-risk-segment,
  body.page-home .hero-enso .enso-ring-build-arc,
  body.page-home .hero-enso .enso-focus-marker,
  body.page-home .hero-enso .enso-dot-halo,
  body.page-home .hero-enso .enso-spark {
    /* sin transición — instantáneo. Estado por color/forma, no por motion. */
    transition: none !important;
  }
  body.page-home .next-line .signal-axis-rail,
  body.page-home .next-line .signal-axis-top,
  body.page-home .next-line .signal-axis-bottom,
  body.page-home .next-line .signal-label,
  body.page-home .signal-feed-line,
  body.page-home .module-cell.is-next-target .module-disc::after {
    transition: none !important;
  }
}

/* ============================================================
   HOME FUTURE EXPERIENCE V3.1 PORT FIX (2026-05-18, WO V3-1-PORT-FIX)
   Cierra 3 blockers Brain:
   1. ENSO AUTHORITY → resuelto arriba en .hero-enso (clamp 96-136).
   2. CALM SYSTEM PRESENCE → secondary ring más presente en calm,
      feed-line stub tenue, signal-tagline «Kaizen sigue observando»
      visible solo en calm. Backend copy intacto (sin tocar projects.py).
   3. ENTRY TIMING → ver bloque «cinematic entry restoration» abajo
      (corrección 2026-05-18 ronda 2: el signal panel a 300ms destruía
      la jerarquía perceptiva enso→kaizen→build→os→saludo→signal→orbes).
   ============================================================ */

/* ----- CINEMATIC MAGIC RESTORATION (2026-05-18 fix(home): bring back the magic)

   La ronda anterior alargó la cascade «para que se notara» — pero
   Ibon dijo «hemos perdido la magia». Diagnóstico: alargar las
   transiciones (duration 480→600ms) las hace MENOS crisp, no más
   visibles. Una transición rápida y nítida se PERCIBE más que una
   transición lenta y suave. Mismo con los delays adicionales: rompen
   el ritmo original.

   FIX: restaurar V2 timings EXACTOS (los que tenían la magia que
   Ibon recuerda). Solo el signal panel (1900ms V2) y feed-line
   (2050ms, nuevo) y orbes (2200ms V2 conservado) se mantienen como
   V3.1 — pero respetando el ritmo V2.

   No overrides de duración. La V2 ya tenía 480ms con bezier crisp.
   No overrides de wordmark delays. La V2 ya tenía 700/940/1140.
   No overrides de greeting/continuity delays. La V2 ya tenía
   1340/1620.

   El bloque queda CORTO porque la magia estaba en V2 y mi trabajo
   es no estorbarla. ----- */

/* signal panel: V2 ya lo ponía en 1900ms. Mantenemos. */
body.page-home section.next-line[data-next-line] {
  animation: home-fade-in 460ms var(--ease) 1900ms forwards;
}

/* feed-line: elemento nuevo de V3.1, entre signal y orbes */
body.page-home .signal-feed-line {
  opacity: 0;
  animation: home-fade-in 460ms var(--ease) 2050ms forwards;
}

/* orbes: V2 original 2200ms+ (no shift artificial) */
body.page-home .home-orbs .module-cell:nth-child(1) { animation-delay: 2200ms; }
body.page-home .home-orbs .module-cell:nth-child(2) { animation-delay: 2260ms; }
body.page-home .home-orbs .module-cell:nth-child(3) { animation-delay: 2320ms; }
body.page-home .home-orbs .module-cell:nth-child(4) { animation-delay: 2380ms; }
body.page-home .home-orbs .module-cell:nth-child(5) { animation-delay: 2440ms; }
body.page-home .home-orbs .module-cell:nth-child(6) { animation-delay: 2500ms; }
body.page-home .home-orbs .module-cell:nth-child(7) { animation-delay: 2560ms; }
body.page-home .home-orbs .module-cell:nth-child(8) { animation-delay: 2620ms; }

/* ----- calm: enso secondary más presente, feed-line tenue, tagline visible ----- */
html[data-next-state="calm"] body.page-home .hero-enso .enso-ring-secondary {
  /* en calm el sistema "observa": el anillo secundario se hace más visible
     para comunicar que el sensor está activo, aunque sin alarma. */
  stroke: var(--fg-dim);
  opacity: 0.85;
  stroke-width: 0.9;
}
html[data-next-state="calm"] body.page-home .hero-enso .enso-ring-tertiary {
  opacity: 0.55;
}

html[data-next-state="calm"] body.page-home .signal-feed-line {
  /* feed-line tenue corta — calma con presencia, no calm muda */
  opacity: 0.35;
  height: 18px;
  background: repeating-linear-gradient(
    180deg,
    var(--fg-dim) 0,
    var(--fg-dim) 2px,
    transparent 2px,
    transparent 6px
  );
}

/* signal-tagline: solo en calm reforzando «Kaizen sigue observando».
   Backend devuelve «sin vencimientos próximos.» — texto autoritario
   inalterado. El tagline es display layer, no inventa estado. */
body.page-home .next-line .signal-tagline {
  display: none;
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--fg-dim);
  letter-spacing: 0.04em;
  margin-top: 2px;
}
html[data-next-state="calm"] body.page-home .next-line .signal-tagline {
  display: block;
}

/* ----- enso authority: ajustes para evitar overflow en 1366×768 ----- */
/* La cinematic entry V2 tenía hero a clamp(64,7vw,96). El bump a (96,9vw,136)
   sumado al signal panel adelantado puede comprimir vertical en 1280×720.
   Compensamos reduciendo home-head padding y greeting margin. */
body.page-home .home-head {
  /* sin override directo — la regla V2 line 1341 ya define la composición;
     aquí sólo reducimos espacio interior si es necesario en viewports cortos. */
}
@media (max-height: 760px) {
  /* WO BRAND MOTION (2026-05-22): enso un poco menor en viewports
     cortos para no aplastar greeting + orbes. Sigue siendo visible y
     vivo (latido + órbita heredados de la regla general). */
  body.page-home .hero-enso {
    width: clamp(40px, 3.5vw, 52px);
    height: clamp(40px, 3.5vw, 52px);
  }
  body.page-home section.next-line[data-next-line] {
    margin-top: 0.8rem;
  }
  body.page-home .signal-feed-line {
    height: 22px;
  }
}

/* ----- mobile: enso se mantiene en su clamp inferior (96px) ----- */
@media (max-width: 520px) {
  /* WO BRAND MOTION (2026-05-22): mobile estrecho — enso compacto pero
     no minúsculo. 40px deja la firma claramente visible y vivo, sin
     empujar los orbes abajo. */
  body.page-home .hero-enso {
    width: 40px;
    height: 40px;
  }
}

/* ============================================================
   ADAPTIVE CALM (2026-05-18) — opción A del brainstorm de Ibon.

   Premisa: en calm el sistema no tiene nada operativo que decir.
   Mostrar un panel grande explicando «no pasa nada» tres veces
   (label + texto + tagline) es ruido sobre el vacío. La señal se
   reduce a una micro-línea sutil que confirma estado al ojo
   entrenado sin robar foco al saludo + memoria + orbes.

   En decision/risk/active el panel se mantiene central y prominente
   como ya está — la atención por excepción es la regla.
   ============================================================ */

/* Estructura: signal panel pierde su grid de 2 columnas, queda a 1 columna,
   sin rail, sin axis, sin paddings de panel. */
html[data-next-state="calm"] body.page-home section.next-line[data-next-line] {
  display: block;
  max-width: 540px;
  margin: 1.2rem auto 0;
  padding: 0;
  text-align: center;
}

/* Ocultar todo lo que es "panel": axis vertical, label uppercase, tagline,
   meta, action (calm no tiene href). */
html[data-next-state="calm"] body.page-home .next-line .signal-axis,
html[data-next-state="calm"] body.page-home .next-line .signal-label,
html[data-next-state="calm"] body.page-home .next-line .signal-tagline,
html[data-next-state="calm"] body.page-home .next-line .signal-meta,
html[data-next-state="calm"] body.page-home .next-line .next-action {
  display: none;
}

html[data-next-state="calm"] body.page-home .next-line .signal-body {
  padding: 0;
  gap: 0;
  display: block;
}

/* Texto principal: línea suave, no peso de panel.
   var(--fg-soft) en lugar de fg pleno; tamaño small; sin tracking marcado. */
html[data-next-state="calm"] body.page-home .next-line .next-text {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--fg-soft);
  letter-spacing: -0.001em;
  line-height: 1.5;
  display: inline;
}

/* Feed-line: desaparece en calm. No hay destino que apuntar. */
html[data-next-state="calm"] body.page-home .signal-feed-line {
  display: none;
}

/* ============================================================
   PROJECT PAGE (refactor 2026-05-18 post-Ibon)
   Página del proyecto en /proyectos/<slug>. Reemplaza al patrón
   anterior (ficha + kanban separados). Tabs internas: Resumen
   (default) · Tablero (link a kanban) · Bitácora (historial).
   ============================================================ */

.project-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--line);
  margin: 1.25rem 0 1.5rem 0;
  padding: 0;
}
.project-tab {
  padding: 0.6rem 1.1rem;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--fg-dim);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
  margin-bottom: -1px;
}
.project-tab:hover {
  color: var(--fg);
}
.project-tab.is-active {
  color: var(--fg);
  border-bottom-color: var(--fg);
  font-weight: 500;
}

.project-tab-content {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

/* Sección due cards específicas del proyecto */
.project-due-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.project-due-item {
  border: 1px solid var(--line);
  border-radius: 2px;
}
.project-due-item.is-overdue {
  border-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 5%, transparent);
}
.project-due-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.85rem;
  text-decoration: none;
  color: var(--fg);
}
.project-due-when {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  color: var(--fg-soft);
  text-transform: lowercase;
  min-width: 110px;
  flex-shrink: 0;
}
.project-due-item.is-overdue .project-due-when {
  color: var(--warn);
  font-weight: 600;
}
.project-due-title {
  flex: 1;
  font-size: var(--text-sm);
  color: var(--fg);
}

/* Header row con link "ver todas" para últimas entradas */
.section-header-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}
.section-more-link {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.section-more-link:hover {
  color: var(--lime);
  border-color: var(--lime);
}

/* responsive */
@media (max-width: 520px) {
  .project-tabs { gap: 0; }
  .project-tab { padding: 0.5rem 0.8rem; font-size: var(--text-xs); }
  .project-due-link { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
  .project-due-when { min-width: 0; }
}

/* =========================================================
   SIGNAL PANEL — Sub-fase 2B (2026-05-18) · drawer lateral derecho
   inspirado en panels secundarios de Linear/Notion. Estado default:
   cerrado, solo handle visible. Click → slide-in del drawer.
   ========================================================= */

.signal-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  z-index: 25;
  pointer-events: none;        /* contenedor inerte; handle/drawer marcan auto */
  font-family: var(--font-sans);
}

/* Handle — chip vertical pegada al borde derecho */
.signal-panel-handle {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: auto;
  background: var(--bg-2);
  color: var(--fg-dim);
  border: 1px solid var(--line-strong);
  border-right: none;
  border-radius: 8px 0 0 8px;
  padding: 0.85rem 0.55rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    opacity 180ms var(--ease),
    transform 220ms var(--ease-spring);
}
.signal-panel-handle:hover {
  background: color-mix(in srgb, var(--bg-2) 88%, var(--fg) 6%);
  color: var(--fg);
  transform: translateY(-50%) translateX(-2px);
}
.signal-panel-handle:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
  color: var(--fg);
}
.signal-panel-handle-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
/* Pulso lima sutil para señales urgentes (overdue / mention) */
.signal-panel-handle-pulse {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 0 0 var(--lime-soft);
  animation: signal-handle-pulse 2.2s ease-in-out infinite;
}
@keyframes signal-handle-pulse {
  0%   { box-shadow: 0 0 0 0 var(--lime-soft); }
  60%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Drawer — desliza desde la derecha cuando data-state="open".
   Empieza BAJO el topbar global (var(--topbar-h)) para no taparse por el
   sticky del topbar (z-index 30 > 25 del drawer). Así el header del panel
   queda visible en lugar de quedar oculto bajo el topbar. */
.signal-panel-drawer {
  position: absolute;
  right: 0;
  top: var(--topbar-h);
  bottom: 0;
  width: clamp(320px, 30vw, 400px);
  background: var(--bg-2);
  border-left: 1px solid var(--line-strong);
  transform: translateX(100%);
  transition: transform 340ms cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  box-shadow: -24px 0 48px -16px rgb(0 0 0 / 0.5);
}
.signal-panel[data-state="open"] .signal-panel-drawer {
  transform: translateX(0);
}
/* Cuando el drawer está abierto, atenuamos el handle para que no compita */
.signal-panel[data-state="open"] .signal-panel-handle {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%) translateX(20px);
}

.signal-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.4rem 1.5rem 1rem;
  border-bottom: 1px solid var(--line);
}
.signal-panel-title {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
}
.signal-panel-close {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast) var(--ease);
  border-radius: 4px;
}
.signal-panel-close svg { width: 16px; height: 16px; }
.signal-panel-close:hover { color: var(--fg); }
.signal-panel-close:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
  color: var(--fg);
}

.signal-panel-body {
  padding: 1.5rem;
  flex: 1;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

/* Contenido — card con jerarquía editorial Linear-style (eyebrow uppercase
   tenue + mensaje hero + tagline gris + CTA con flecha) */
.signal-panel-card {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 280ms var(--ease) 200ms,
              transform 320ms var(--ease) 200ms;
}
.signal-panel[data-state="open"] .signal-panel-card {
  opacity: 1;
  transform: translateY(0);
}
.signal-panel-eyebrow {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 0.4rem;
}
.signal-panel-card[data-next-kind="overdue"] .signal-panel-eyebrow,
.signal-panel-card[data-next-kind="mention"] .signal-panel-eyebrow {
  color: var(--lime);
}
.signal-panel-message {
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--fg);
  letter-spacing: -0.015em;
  line-height: 1.3;
  margin: 0;
}
.signal-panel-tagline {
  font-size: var(--text-xs);
  color: var(--fg-dim);
  letter-spacing: 0.02em;
  display: none;  /* solo se muestra en estado calm */
}
.signal-panel-card[data-next-kind="calm"] .signal-panel-tagline { display: inline; }
.signal-panel-action {
  margin-top: 0.6rem;
  align-self: flex-start;
  font-size: var(--text-sm);
  color: var(--fg);
  text-decoration: none;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  transition:
    color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    background var(--t-fast) var(--ease);
}
.signal-panel-action:hover {
  color: var(--lime);
  border-color: var(--lime);
  background: color-mix(in srgb, var(--lime) 6%, transparent);
}
.signal-panel-empty {
  font-size: var(--text-sm);
  color: var(--fg-dim);
  margin: 0;
  letter-spacing: -0.01em;
}

/* Reduced-motion: sin slide ni pulso */
@media (prefers-reduced-motion: reduce) {
  .signal-panel-drawer { transition: none; }
  .signal-panel-handle-pulse { animation: none; }
  .signal-panel-card { transition: none; opacity: 1; transform: none; }
}

/* Responsive: en pantallas estrechas el drawer ocupa más del viewport */
@media (max-width: 520px) {
  .signal-panel-drawer { width: 88vw; }
}

/* =========================================================
   ENSO WARP — Sub-fase 5C (2026-05-18)

   El efecto magnético ahora vive enteramente en JS (cursor_magnetic.js)
   y modifica el atributo `d` del path .ring en tiempo real (warp local
   del SVG, no transform del elemento). Aquí solo declaramos transitions
   defensivas para que cualquier cambio futuro de stroke/color sea suave.
   ========================================================= */

body.page-home .hero-lockup .hero-enso path.ring {
  /* La deformación es por reemplazo de atributo d; no necesita transition
     (cambia frame-a-frame con rAF). Pero stroke/color sí transicionan
     suave por si se modulan en hover futuro. */
  transition: stroke 280ms var(--ease);
}

/* =========================================================
   WO #1 Fase 2 (2026-05-18) — Lápiz de edición + modal del orbe

   El lápiz vive en la esquina superior derecha del tooltip de cada
   proyecto. Visible cuando la tarjeta está abierta. Click → modal con
   campos name + description. Estilo Linear: minimalista, fondo casi
   transparente, hover sutil, focus ring lime.
   ========================================================= */

.module-tooltip-edit {
  position: absolute;
  top: 0.55rem;
  right: 0.55rem;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--fg-faint);
  cursor: pointer;
  transition:
    background 180ms var(--ease),
    color      180ms var(--ease);
  padding: 0;
}
.module-tooltip-edit:hover,
.module-tooltip-edit:focus-visible {
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--fg);
  outline: none;
}
.module-tooltip-edit:focus-visible {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--lime) 70%, transparent);
}

/* Modal de edición del orbe — singleton fuera del flujo. Estilo Linear
   sobrio: backdrop plano (sin blur cliché), dialog con borde nítido y
   sombra contenida, fade-in simple (sin scale spring). Linear real usa
   este patrón; el glassmorphism + spring rise es over-used AI-design. */
/* WO RECOVERY-UI (2026-05-23): link discreto bajo la grid de orbes
   que abre la papelera. Visible solo si hay items que restaurar
   (count > 0; JS toggle hidden attribute). Estilo minimal: texto
   pequeño, no compite con orbes ni con + Nuevo Proyecto. */
.home-archive-link {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font: inherit;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 0.3rem 0.6rem;
  margin: 1rem auto 0;
  border-radius: 999px;
  transition: color 160ms var(--ease), background 160ms var(--ease);
}
.home-archive-link:hover,
.home-archive-link:focus-visible {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  outline: none;
}

/* Panel de papelera — modal singleton al body. Mismo z-index que el
   modal de edicion (no se solapan: uno abierto a la vez). Estilo
   alineado con .orb-edit-modal. */
.archive-panel-modal[hidden] { display: none; }
.archive-panel-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}
.archive-panel-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
}
.archive-panel-dialog {
  position: relative;
  width: min(420px, 100%);
  max-height: calc(100vh - 1.5rem);
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: 0 20px 56px -12px rgb(0 0 0 / 0.5);
  padding: 1.25rem 1.4rem 1.1rem;
  animation: orb-edit-fade 180ms var(--ease);
}
.archive-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.archive-panel-head h2 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.archive-panel-close {
  background: transparent;
  border: none;
  color: var(--fg-faint);
  font-size: 1.25rem;
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  line-height: 1;
}
.archive-panel-close:hover { color: var(--fg); }
.archive-panel-hint {
  font-size: 0.72rem;
  color: var(--fg-faint);
  line-height: 1.5;
  margin: 0 0 0.85rem;
}
.archive-panel-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.archive-panel-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--fg) 8%, transparent);
}
.archive-panel-item-info {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  flex: 1;
}
.archive-panel-item-name {
  font-size: 0.85rem;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.archive-panel-item-kind {
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  color: var(--fg-faint);
  text-transform: uppercase;
}
.archive-panel-item-kind[data-kind="deleted"] {
  color: color-mix(in srgb, #C76685 80%, var(--fg-faint));
}
.archive-panel-restore {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent);
  color: var(--fg-dim);
  font: inherit;
  font-size: 0.72rem;
  padding: 0.35rem 0.7rem;
  border-radius: 6px;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
  flex-shrink: 0;
}
.archive-panel-restore:hover {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--fg) 40%, transparent);
}
.archive-panel-restore:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.archive-panel-empty {
  font-size: 0.78rem;
  color: var(--fg-faint);
  text-align: center;
  margin: 0.5rem 0;
}

.orb-edit-modal[hidden] { display: none; }
.orb-edit-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}
.orb-edit-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
}
.orb-edit-dialog {
  position: relative;
  width: min(440px, 100%);
  /* WO ARCHIVE+SOFT-DELETE (2026-05-23): cap altura + scroll interno
     para que en mobile pequeño los botones archive/delete (que viven
     bajo guardar/cancelar) sigan alcanzables sin overflow del modal. */
  max-height: calc(100vh - 1.5rem);
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: 0 20px 56px -12px rgb(0 0 0 / 0.5);
  padding: 1.25rem 1.4rem 1.1rem;
  animation: orb-edit-fade 180ms var(--ease);
}
@keyframes orb-edit-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.orb-edit-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.orb-edit-head h2 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg);
  text-transform: lowercase;
}
.orb-edit-close {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--fg-faint);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: background 180ms var(--ease), color 180ms var(--ease);
}
.orb-edit-close:hover,
.orb-edit-close:focus-visible {
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--fg);
  outline: none;
}
.orb-edit-form {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.orb-edit-field {
  display: flex;
  flex-direction: column;
  gap: 0.32rem;
}
.orb-edit-label {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.orb-edit-form input[type="text"],
.orb-edit-form textarea {
  width: 100%;
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.55rem 0.7rem;
  color: var(--fg);
  font: inherit;
  font-size: 0.88rem;
  resize: vertical;
  transition: border-color 180ms var(--ease), background 180ms var(--ease);
}
.orb-edit-form textarea {
  min-height: 64px;
  line-height: 1.4;
}
.orb-edit-form input[type="text"]:focus,
.orb-edit-form textarea:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--fg) 38%, transparent);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.orb-edit-error {
  font-size: 0.78rem;
  color: #fca5a5;
  margin-top: -0.2rem;
}
.orb-edit-error[hidden] { display: none; }
.orb-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.55rem;
  margin-top: 0.35rem;
}

/* WO ARCHIVE+SOFT-DELETE (2026-05-23): zona discreta inferior del modal
   con archivar + eliminar. Separada visualmente del cluster guardar/
   cancelar por una línea sutil y padding-top. Confirmación inline
   reemplaza la fila de botones (no usar window.confirm que escapa
   scope HTMX). Copy honesto: "eliminar" = soft-delete (NO borra datos
   físicos). */
.orb-edit-danger {
  margin-top: 0.65rem;
  padding-top: 0.65rem;
  border-top: 1px solid color-mix(in srgb, var(--fg) 8%, transparent);
}
.orb-edit-danger-row {
  display: flex;
  justify-content: space-between;
  gap: 0.55rem;
  align-items: center;
}
.orb-edit-archive,
.orb-edit-delete {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.4rem 0.8rem;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  transition: background 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease);
}
.orb-edit-archive {
  border-color: color-mix(in srgb, var(--fg) 18%, transparent);
  color: var(--fg-dim);
}
.orb-edit-archive:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg);
}
.orb-edit-delete {
  color: color-mix(in srgb, #C76685 80%, var(--fg-dim));
  border-color: color-mix(in srgb, #C76685 26%, transparent);
}
.orb-edit-delete:hover {
  background: color-mix(in srgb, #C76685 10%, transparent);
  color: color-mix(in srgb, #C76685 95%, var(--fg));
  border-color: color-mix(in srgb, #C76685 45%, transparent);
}
.orb-edit-confirm {
  margin-top: 0.55rem;
  padding: 0.65rem 0.75rem;
  border-radius: 7px;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent);
}
.orb-edit-confirm-msg {
  font-size: 0.78rem;
  color: var(--fg-dim);
  margin: 0 0 0.55rem;
  line-height: 1.45;
}
.orb-edit-confirm-row {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}
.orb-edit-confirm-cancel,
.orb-edit-confirm-ok {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.38rem 0.85rem;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  transition: background 160ms var(--ease), color 160ms var(--ease);
}
.orb-edit-confirm-cancel {
  border-color: color-mix(in srgb, var(--fg) 18%, transparent);
  color: var(--fg-dim);
}
.orb-edit-confirm-cancel:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.orb-edit-confirm-ok {
  background: color-mix(in srgb, #C76685 80%, var(--fg));
  border-color: color-mix(in srgb, #C76685 70%, var(--fg));
  color: var(--bg);
  font-weight: 500;
}
.orb-edit-confirm-ok[data-confirm-mode="archive"] {
  background: var(--fg);
  border-color: var(--fg);
}
.orb-edit-confirm-ok:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.orb-edit-cancel,
.orb-edit-save {
  font: inherit;
  font-size: 0.82rem;
  padding: 0.45rem 0.95rem;
  border-radius: 7px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 180ms var(--ease), border-color 180ms var(--ease);
}
.orb-edit-cancel {
  background: transparent;
  border-color: var(--line);
  color: var(--fg-dim);
}
.orb-edit-cancel:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg);
}
.orb-edit-save {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
  font-weight: 500;
}
.orb-edit-save:hover {
  background: color-mix(in srgb, var(--fg) 85%, transparent);
}
.orb-edit-save:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .orb-edit-dialog { animation: none; }
}

/* ----- WO #1 Fase 3 — sección imagen/color del modal ----- */
.orb-edit-image-section {
  gap: 0.55rem;
}
.orb-edit-image-preview {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.6rem;
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
}
/* Preview del orbe en el modal de edición — WO 2026-05-19 (3ª iter).
   WYSIWYG con el orbe real:
     Sin imagen: background = color elegido (var --orb-preview-color).
     Con imagen: layer 1 (color sólido) abajo, layer 2 (imagen) arriba
     con `background-blend-mode: multiply` → exactamente lo mismo que
     el orbe real (`.module-disc.has-image` con bg + image multiply).
   Resultado: lo que ves en el preview es lo que verás en la home. */
.orb-edit-preview-disc {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background-color: var(--orb-preview-color, var(--fg-faint));
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  flex: 0 0 auto;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--fg) 10%, transparent);
}
.orb-edit-preview-disc[data-has-image="true"] {
  /* WO BUGFIX IMAGEN SHELL (2026-05-22, 2ª iter): preview alineado
     con .module-disc.has-image — shell neutra var(--line-strong)
     debajo, imagen encima. Antes el preview era transparent y el
     disco de 36px se veía como un fantasma. */
  background-color: var(--line-strong);
  background-image: var(--orb-preview-image, none);
  background-blend-mode: multiply, normal;
}
.orb-edit-preview-label {
  font-size: 0.78rem;
  color: var(--fg-dim);
  letter-spacing: 0.01em;
}
.orb-edit-palette {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(28px, 1fr));
  gap: 0.4rem;
}
.orb-edit-swatch {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid transparent;
  background: var(--swatch);
  cursor: pointer;
  padding: 0;
  transition: transform 160ms var(--ease), border-color 160ms var(--ease);
  outline: none;
}
.orb-edit-swatch:hover {
  transform: scale(1.08);
}
.orb-edit-swatch[aria-checked="true"] {
  border-color: var(--fg);
  transform: scale(1.05);
}
.orb-edit-swatch:focus-visible {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--lime) 70%, transparent);
}

/* ============================================================
   WO_PROTOTYPE_FAST SHAPES v2 (2026-05-22) — MOCK, no persistencia.
   12 formas: circle (default), squircle, square, diamond, triangle-up,
   triangle-down, pentagon, hexagon, octagon, capsule, soft-blob, star.
   Aplica via [data-orb-shape] en .module-disc, .module-image y el
   preview disc.

   Glow para clip-path shapes: usa `filter: drop-shadow` en lugar de
   box-shadow (que sigue la bbox rectangular). drop-shadow respeta el
   clip-path → glow sigue la silueta real.
   ============================================================ */

/* Shapes con border-radius puro (sin clip-path) — box-shadow normal funciona */
.module-disc[data-orb-shape="circle"],
.orb-edit-preview-disc[data-orb-shape="circle"] { border-radius: 50%; clip-path: none; }
.module-disc[data-orb-shape="circle"] .module-image { border-radius: 50%; clip-path: none; }

.module-disc[data-orb-shape="squircle"],
.orb-edit-preview-disc[data-orb-shape="squircle"] { border-radius: 22%; clip-path: none; }
.module-disc[data-orb-shape="squircle"] .module-image { border-radius: 22%; clip-path: none; }

.module-disc[data-orb-shape="square"],
.orb-edit-preview-disc[data-orb-shape="square"] { border-radius: 4%; clip-path: none; }
.module-disc[data-orb-shape="square"] .module-image { border-radius: 4%; clip-path: none; }

/* Capsule: aspect ratio 1.5:1, requiere override explícito de width/height
   con specificity alta para vencer las reglas previas de .home-orbs. */
.module-disc[data-orb-shape="capsule"],
.orb-edit-preview-disc[data-orb-shape="capsule"] {
  border-radius: 999px;
  clip-path: none;
}
.module-disc[data-orb-shape="capsule"] .module-image {
  border-radius: 999px; clip-path: none;
}
body.page-home .home-orbs .module-disc[data-orb-shape="capsule"] {
  width: 100px;
  height: 60px;
}
@media (max-width: 520px) {
  body.page-home .home-orbs .module-disc[data-orb-shape="capsule"] {
    width: 92px;
    height: 56px;
  }
}
.orb-edit-preview-disc[data-orb-shape="capsule"] {
  width: 54px;
  height: 30px;
}

/* Shapes con clip-path — glow via filter: drop-shadow */
.module-disc[data-orb-shape="diamond"],
.orb-edit-preview-disc[data-orb-shape="diamond"] {
  border-radius: 0;
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
.module-disc[data-orb-shape="diamond"] .module-image {
  border-radius: 0; clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}

.module-disc[data-orb-shape="triangle-up"],
.orb-edit-preview-disc[data-orb-shape="triangle-up"] {
  border-radius: 0;
  clip-path: polygon(50% 5%, 95% 95%, 5% 95%);
}
.module-disc[data-orb-shape="triangle-up"] .module-image {
  border-radius: 0; clip-path: polygon(50% 5%, 95% 95%, 5% 95%);
}

.module-disc[data-orb-shape="triangle-down"],
.orb-edit-preview-disc[data-orb-shape="triangle-down"] {
  border-radius: 0;
  clip-path: polygon(5% 5%, 95% 5%, 50% 95%);
}
.module-disc[data-orb-shape="triangle-down"] .module-image {
  border-radius: 0; clip-path: polygon(5% 5%, 95% 5%, 50% 95%);
}

.module-disc[data-orb-shape="pentagon"],
.orb-edit-preview-disc[data-orb-shape="pentagon"] {
  border-radius: 0;
  clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
}
.module-disc[data-orb-shape="pentagon"] .module-image {
  border-radius: 0; clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
}

.module-disc[data-orb-shape="hexagon"],
.orb-edit-preview-disc[data-orb-shape="hexagon"] {
  border-radius: 0;
  clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}
.module-disc[data-orb-shape="hexagon"] .module-image {
  border-radius: 0; clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

.module-disc[data-orb-shape="octagon"],
.orb-edit-preview-disc[data-orb-shape="octagon"] {
  border-radius: 0;
  clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}
.module-disc[data-orb-shape="octagon"] .module-image {
  border-radius: 0; clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}

/* Soft-blob: border-radius asimétrico — orgánico sin clip-path */
.module-disc[data-orb-shape="soft-blob"],
.orb-edit-preview-disc[data-orb-shape="soft-blob"] {
  border-radius: 60% 40% 55% 45% / 50% 60% 40% 50%;
  clip-path: none;
}
.module-disc[data-orb-shape="soft-blob"] .module-image {
  border-radius: 60% 40% 55% 45% / 50% 60% 40% 50%; clip-path: none;
}

/* WO SHIP_SAFE (2026-05-22): star QUEDA FUERA — el prototype v2 mostró
   que con imagen parece clip-art y rompe estética calm/premium. Lista
   final 11 formas, sin star. Cualquier projects.json con shape=star
   cae a circle por _normalize_shape() en backend. */

/* TRIANGLE_INITIALS_FIX: el clip-path triangular recorta el centro
   horizontal donde las iniciales se renderizan por defecto. Las
   desplazamos hacia donde el triángulo es más ancho:
   - triangle-up: parte ancha abajo → iniciales bajan (translateY +)
   - triangle-down: parte ancha arriba → iniciales suben (translateY -)
   También reducimos el font-size un toque para entrar entre puntas. */
.module-disc[data-orb-shape="triangle-up"] .module-initials {
  transform: translateY(22%);
  font-size: 0.82em;
}
.module-disc[data-orb-shape="triangle-down"] .module-initials {
  transform: translateY(-18%);
  font-size: 0.82em;
}

/* Glow drop-shadow para clip-path shapes (sustituye box-shadow circular).
   Box-shadow original sigue activo pero se ve solo en shapes con
   border-radius. Para los clipped, drop-shadow respeta la silueta. */
.module-disc[data-orb-shape="diamond"],
.module-disc[data-orb-shape="triangle-up"],
.module-disc[data-orb-shape="triangle-down"],
.module-disc[data-orb-shape="pentagon"],
.module-disc[data-orb-shape="hexagon"],
.module-disc[data-orb-shape="octagon"] {
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4));
}
.module-cell:hover .module-disc[data-orb-shape="diamond"],
.module-cell:hover .module-disc[data-orb-shape="triangle-up"],
.module-cell:hover .module-disc[data-orb-shape="triangle-down"],
.module-cell:hover .module-disc[data-orb-shape="pentagon"],
.module-cell:hover .module-disc[data-orb-shape="hexagon"],
.module-cell:hover .module-disc[data-orb-shape="octagon"] {
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.55));
}

/* MOBILE_EDIT_ACCESS_FIX (SHIP_SAFE 2026-05-22): el lápiz original
   vive dentro del tooltip que el hotfix oculta en touch. Añadimos un
   botón pequeño visible SOLO en pointer:coarse (touch). En desktop
   (pointer:fine) queda oculto — la edición sigue siendo via tooltip-
   hover-pencil. Posición: bajo el label, discreto. Pulsarlo dispara
   el mismo openOrbEdit que el pencil del tooltip. */
.module-cell-edit-touch {
  display: none;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
  border-radius: 999px;
  color: var(--fg-faint);
  cursor: pointer;
  padding: 0.2rem 0.6rem;
  margin: 0.25rem auto 0;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.module-cell-edit-touch:hover,
.module-cell-edit-touch:focus-visible {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--fg) 30%, transparent);
  outline: none;
}
@media (pointer: coarse), (hover: none) {
  .module-cell-edit-touch {
    display: inline-flex;
  }
}
/* Excepción: la celda "+nuevo proyecto" no necesita edit affordance —
   no es un proyecto editable. */
.orb-create-cell .module-cell-edit-touch {
  display: none !important;
}

/* Selector de formas en el modal — base styles (label + botones).
   El layout (grid A3) está más abajo. */
.orb-edit-shapes {
  margin: 0.55rem 0 0;
}
.orb-edit-shapes-label {
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  text-transform: uppercase;
  margin-right: 0.3rem;
}
.orb-edit-shape-btn {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent);
  cursor: pointer;
  padding: 0;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 160ms var(--ease), background 160ms var(--ease);
}
.orb-edit-shape-btn:hover {
  border-color: color-mix(in srgb, var(--fg) 30%, transparent);
}
.orb-edit-shape-btn[aria-checked="true"] {
  border-color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.orb-edit-shape-glyph {
  display: inline-block;
  width: 14px;
  height: 14px;
  background: color-mix(in srgb, var(--fg) 70%, transparent);
}
.s-circle { border-radius: 50%; }
.s-squircle { border-radius: 22%; }
.s-square { border-radius: 4%; }
.s-capsule { border-radius: 999px; width: 16px; height: 10px; }
.s-diamond { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
.s-triangle-up { clip-path: polygon(50% 5%, 95% 95%, 5% 95%); }
.s-triangle-down { clip-path: polygon(5% 5%, 95% 5%, 50% 95%); }
.s-pentagon { clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%); }
.s-hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); }
.s-octagon { clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); }
.s-soft-blob { border-radius: 60% 40% 55% 45% / 50% 60% 40% 50%; }
/* WO SHIP_SAFE (2026-05-22): glyph s-star eliminado — star fuera del set. */

/* WO SHIP_SAFE (2026-05-22): A3 Compact Shape Grid es el default. La
   variante A2 wrap row del prototype quedó descartada por imprevisibilidad
   con 11 botones. Mismo ritmo visual que la paleta de color, pero
   estructurado en grid 6 columnas con label inline. */
.orb-edit-shapes {
  display: grid;
  grid-template-columns: auto repeat(6, 1fr);
  gap: 0.4rem 0.5rem;
  align-items: center;
}
.orb-edit-shapes .orb-edit-shapes-label {
  margin-right: 0.2rem;
  grid-row: 1;
}
.orb-edit-shapes .orb-edit-shape-btn {
  width: 100%;
  height: 32px;
  min-width: 32px;
}

.orb-edit-image-controls {
  display: flex;
  gap: 0.55rem;
  align-items: center;
}
.orb-edit-upload {
  font-size: 0.78rem;
  color: var(--fg-dim);
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  cursor: pointer;
  transition: background 180ms var(--ease), color 180ms var(--ease);
}
.orb-edit-upload:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg);
}
.orb-edit-image-remove {
  font: inherit;
  font-size: 0.78rem;
  color: var(--fg-faint);
  background: transparent;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--fg-faint) 60%, transparent);
  text-underline-offset: 3px;
}
.orb-edit-image-remove:hover {
  color: var(--fg);
}
.orb-edit-image-remove[hidden] { display: none; }
.orb-edit-image-hint {
  margin: 0;
  font-size: 0.72rem;
  color: var(--fg-faint);
  line-height: 1.4;
}

/* ============================================================
   .kz-live-system — capa "centro operativo vivo" en home
   (WO_IMPLEMENT_PREMIUM_DEPTH_B, 2026-05-19)
   Comunica en 5 segundos: qué se movió, qué decisión viene, qué
   aprendió el sistema. Datos REALES desde context Jinja (actor_pulse,
   next_decision, lesson_headline). Fallback calm honesto si vacío.
   ============================================================ */

.kz-live-system {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  width: 100%;
  margin-top: 0.25rem;
  margin-bottom: 0.25rem;
}

/* En mobile (≤520px), apilar verticalmente — 3 cards no caben en row.
   Compactar todo para preservar invariante "home cabe sin scroll" en
   viewport 375×812 con DB poblada (cards con texto real más largo).
   2026-05-19 polish: enso authority — sube a 96px (mismo mínimo
   desktop) para que el enso siga siendo protagonista de identidad, no
   icono. Cards más comprimidas para compensar. */
@media (max-width: 520px) {
  .kz-live-system {
    grid-template-columns: 1fr;
    gap: 5px;
    margin-top: 0;
    margin-bottom: 0;
  }
  .kz-live-card {
    padding: 0.35rem 0.55rem;
    gap: 0.05rem;
    border-radius: 8px;
  }
  .kz-live-card-eyebrow {
    font-size: 0.58rem;
  }
  .kz-live-card-title {
    font-size: 0.76rem;
    line-height: 1.2;
    /* Truncate multi-line robusto: 2 líneas con ellipsis. Necesario
       porque lessons como "no cerrar la ui sin agotar la escalera de
       evidencia." son largas y no caben en 1 línea sin overflow visual. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
    /* Reserva altura exacta para 2 líneas — evita salto cuando cambia
       de 1 a 2 líneas con contenido distinto. */
    max-height: calc(1.2em * 2);
  }
  .kz-live-card-meta {
    font-size: 0.58rem;
    line-height: 1.15;
  }
  /* WO BRAND MOTION (2026-05-22): en mobile el enso sube a 44px y el
     wordmark a 1.25rem para que la firma sea legible y la animación
     (latido + órbita) se perciba. !important sigue siendo necesario
     para vencer cualquier override previo del cinematic. */
  /* hero-enso ya retirado del template ship-safe; estos !important
     quedan como inocuos si el SVG no existe. */
  body.page-home .hero-enso {
    width: 44px !important;
    height: 44px !important;
  }
  /* WO HOME PRESENCE LAYOUT REBALANCE (2026-05-22): wordmark
     discreto en mobile, saludo medio. */
  body.page-home .wordmark-hero {
    font-size: 1rem !important;
    line-height: 1.0 !important;
  }
  body.page-home .greeting {
    font-size: 1rem !important;
    margin-top: 0;
    margin-bottom: 0;
  }
  /* En mobile, el greeting-context "↳ estabas en X" compite con
     la capa live-system por espacio vertical y la información se
     duplica en parte (la próxima decisión ya alude a contexto). */
  body.page-home .greeting-context {
    display: none;
  }
}

.kz-live-card {
  background: color-mix(in srgb, var(--fg-dim) 4%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--fg-dim) 12%, transparent);
  border-radius: 10px;
  padding: 0.75rem 0.9rem;
  /* Soft elevation multicapa (anti-cargo-cult: NO copia el cubic-bezier de
     Linear, usa Kaizen actual; pero adopta la idea de "varias capas con
     opacidad baja" en lugar de una sombra única pesada). */
  box-shadow:
    rgba(0, 0, 0, 0.04) 0 1px 2px 0,
    rgba(0, 0, 0, 0.06) 0 4px 12px -4px,
    rgba(0, 0, 0, 0.08) 0 12px 32px -16px;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  text-align: left;
  /* Tokens A: motion duration 100ms (vs 120ms del default), easing
     ease-out-quad observado en Linear público. Aplicado SOLO a estas
     cards nuevas, no como cambio global del sistema. */
  transition:
    background var(--t-live, 100ms) cubic-bezier(0.25, 0.46, 0.45, 0.94),
    border-color var(--t-live, 100ms) cubic-bezier(0.25, 0.46, 0.45, 0.94),
    transform var(--t-live, 100ms) cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.kz-live-card:hover {
  background: color-mix(in srgb, var(--fg-dim) 7%, var(--bg));
  border-color: color-mix(in srgb, var(--fg-dim) 22%, transparent);
  transform: translateY(-1px);
}

.kz-live-card-eyebrow {
  /* CLAUDE.md: "Es la tipografía la que comunica peso, no las mayúsculas".
     Eyebrow en lowercase, tipografía pesa por color faint + tamaño pequeño. */
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  color: var(--fg-faint);
  font-weight: 500;
}

.kz-live-card-title {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--fg);
  line-height: 1.35;
  /* Evita overflow horizontal feo; el title puede ser frase larga */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.kz-live-card-title-calm {
  color: var(--fg-dim);
  font-style: normal;
}

.kz-live-card-link {
  text-decoration: none;
}
.kz-live-card-link:hover {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.kz-live-card-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  margin-right: 0.4em;
  vertical-align: 1px;
  flex-shrink: 0;
}

.kz-live-card-dot-calm {
  background: var(--fg-faint);
  opacity: 0.5;
}

.kz-live-card-meta {
  font-size: 0.72rem;
  color: var(--fg-dim);
  line-height: 1.3;
}

/* WO BRAND MOTION (2026-05-22): el enso vuelve a tamaño medio para
   acomodar el latido + órbita. Sigue mucho menor que el authority
   pre-MICRO-LAYOUT (112-144px); ahora 48-64px desktop, 44px mobile.
   Identidad enso intacta (PRODUCT_INVARIANT) — el cambio es de
   amplitud y motion, no de forma. */
body.page-home .hero-enso {
  width: clamp(48px, 4.5vw, 64px);
  height: clamp(48px, 4.5vw, 64px);
}
body.page-home .greeting {
  margin-top: 0.2rem;
}

/* Token C eliminado tras audit (kaizen-ux-auditor 2026-05-19): la regla
   `.topbar-link` original en línea 451 YA tiene exactamente lo que el
   token C pretendía (pill 999px + hover bg sutil + active bg + underline
   lime corto 16px). Mi versión duplicada cambiaba border-radius a 6px
   creando regresión de shape no intencional. Mantener regla original.
   Para tunear motion del topbar específicamente, considerar variables
   --t-fast/--ease en :root (afectan todo) — fuera de scope esta WO. */

/* =========================================================
   TOPBAR CHAT BUTTON (WO 2026-05-19) — gemelo del bell, abre el chat drawer.
   ========================================================= */
.topbar-chat {
  background: transparent;
  border: none;
  padding: 0.3rem 0.4rem;
  margin: 0 0.1rem;
  cursor: pointer;
  color: var(--fg-faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease-spring),
              background var(--t-fast) var(--ease);
  line-height: 0;
  border-radius: 6px;
}
.topbar-chat:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  transform: scale(1.08);
}
.topbar-chat:active { transform: scale(0.94); }
.topbar-chat.is-active { color: var(--fg); background: color-mix(in srgb, var(--fg) 8%, transparent); }
.topbar-chat:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}
.topbar-chat svg {
  width: 16px;
  height: 16px;
  display: block;
}

/* =========================================================
   SIGNAL PANEL — contexto adicional añadido WO 2026-05-19:
   bloque memoria "↳ estabas en X" dentro del drawer (antes vivía
   inline en la home, ahora se reubica aquí por decisión de Ibon).
   ========================================================= */
.signal-panel-context {
  margin-top: 1.5rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
}
.signal-panel-context-eyebrow {
  display: block;
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 0.4rem;
}
.signal-panel-context-line {
  margin: 0;
  font-size: 0.95rem;
  color: var(--fg-dim);
  line-height: 1.35;
}
.signal-panel-context-line a {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 30%, transparent);
  transition: border-color var(--t-fast) var(--ease);
}
.signal-panel-context-line a:hover {
  border-bottom-color: var(--fg);
}

/* =========================================================
   CHAT PANEL (WO 2026-05-19) — drawer derecho tipo Hangouts. Mismo patrón
   que signal-panel pero con su propio handle (apilado debajo), drawer
   independiente y mensajería persistida en SQLite (chat.db).
   ========================================================= */
.chat-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  z-index: 26;
  pointer-events: none;
  font-family: var(--font-sans);
}

/* Handle — chip vertical, apilado bajo el signal handle (offset vertical)
   para que ambos sean accesibles desde el borde derecho. */
.chat-panel-handle {
  position: absolute;
  right: 0;
  top: calc(50% + 70px);   /* offset bajo signal handle */
  transform: translateY(-50%);
  pointer-events: auto;
  background: var(--bg-2);
  color: var(--fg-dim);
  border: 1px solid var(--line-strong);
  border-right: none;
  border-radius: 8px 0 0 8px;
  padding: 0.7rem 0.5rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    opacity 180ms var(--ease),
    transform 220ms var(--ease-spring);
}
.chat-panel-handle:hover {
  background: color-mix(in srgb, var(--bg-2) 88%, var(--fg) 6%);
  color: var(--fg);
  transform: translateY(-50%) translateX(-2px);
}
.chat-panel-handle:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
  color: var(--fg);
}
.chat-panel-handle-icon { width: 16px; height: 16px; flex-shrink: 0; }
.chat-panel-handle-unread {
  position: absolute;
  top: 6px;
  right: 6px;
  min-width: 16px;
  height: 16px;
  border-radius: 8px;
  background: var(--lime);
  color: var(--bg);
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  padding: 0 4px;
}

.chat-panel-drawer {
  position: absolute;
  right: 0;
  top: var(--topbar-h);
  bottom: 0;
  width: clamp(320px, 32vw, 420px);
  background: var(--bg-2);
  border-left: 1px solid var(--line-strong);
  transform: translateX(100%);
  transition: transform 340ms cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  box-shadow: -24px 0 48px -16px rgb(0 0 0 / 0.5);
}
.chat-panel[data-state="open"] .chat-panel-drawer { transform: translateX(0); }
.chat-panel[data-state="open"] .chat-panel-handle {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%) translateX(20px);
}

.chat-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.4rem 1.5rem 1rem;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.chat-panel-title {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
}
.chat-panel-close {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast) var(--ease);
  border-radius: 4px;
}
.chat-panel-close svg { width: 16px; height: 16px; }
.chat-panel-close:hover { color: var(--fg); }
.chat-panel-close:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
  color: var(--fg);
}

.chat-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem 1rem 0.5rem;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
.chat-message-list {
  list-style: none;
  margin: 0;
  padding: 0.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.chat-empty {
  margin: 2rem 0;
  text-align: center;
  color: var(--fg-faint);
  font-size: var(--text-sm);
  font-style: italic;
}

.chat-message {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
}
.chat-message.is-self {
  flex-direction: row-reverse;
}
.chat-message-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 0.75rem;
  font-weight: 600;
  flex-shrink: 0;
  text-transform: uppercase;
}
.chat-message-bubble {
  background: color-mix(in srgb, var(--bg) 60%, var(--bg-2) 40%);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
  max-width: 75%;
  min-width: 0;
}
.chat-message.is-self .chat-message-bubble {
  background: color-mix(in srgb, var(--lime) 14%, var(--bg-2) 86%);
  border-color: color-mix(in srgb, var(--lime) 30%, var(--line));
}
.chat-message-head {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.15rem;
  align-items: baseline;
}
.chat-message-author {
  font-size: 0.72rem;
  text-transform: lowercase;
  color: var(--fg-faint);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.chat-message-time {
  font-size: 0.7rem;
  color: var(--fg-faint);
}
.chat-message-body {
  margin: 0;
  font-size: 0.95rem;
  color: var(--fg);
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-wrap: anywhere;
  line-height: 1.4;
}

.chat-panel-form {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem 1rem 1rem;
  border-top: 1px solid var(--line);
  align-items: flex-end;
  flex-shrink: 0;
}
.chat-panel-field {
  flex: 1;
  display: block;
  min-width: 0;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.chat-panel-input {
  width: 100%;
  resize: none;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
  font: inherit;
  font-size: 0.95rem;
  line-height: 1.4;
  min-height: 36px;
  max-height: 140px;
  transition: border-color var(--t-fast) var(--ease);
}
.chat-panel-input:focus {
  outline: none;
  border-color: var(--fg-dim);
}
.chat-panel-send {
  background: var(--bg);
  color: var(--fg-dim);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 0.4rem 0.55rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.chat-panel-send:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
  background: color-mix(in srgb, var(--fg) 4%, var(--bg));
}
.chat-panel-send svg { width: 16px; height: 16px; }
.chat-panel-send:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
}

/* Mobile: drawers ocupan más anchura y los handles se reducen */
@media (max-width: 720px) {
  .signal-panel-drawer,
  .chat-panel-drawer {
    width: 92vw;
    max-width: 380px;
  }
  .chat-panel-handle { padding: 0.6rem 0.4rem; }
}
