/* =========================================================================
   ZEO 2.0 — espíritu de relámpago (orden Hidalgo 2026-07-30, desde cero)
   Ley: DESIGN.md (Command Ledger · plata sobre grafito; glow solo en lo vivo)
   Biblia: docs/ZEO-PERSONAJE.md
   Reglas duras: solo transform/opacity animan; halo = gradiente (sin filtros);
   prefers-reduced-motion = pose estática + texto; cero librerías.
   El DOM lo construye web/zeo.js — aquí solo vive la piel y el movimiento.
   ========================================================================= */

.zeo-root{
  /* Tokens — heredan del tema del dashboard con fallback al tema oscuro de DESIGN.md */
  --zeo-graphite: var(--text, #EDF2F7);        /* en dashboard oscuro --text es plata… */
  --zeo-body-fill: #121A28;                    /* …por eso el cuerpo fija su grafito propio
                                                  (Tormenta 2026-08-24: grafito con azul, no neutro,
                                                   para que ZEO no flote fuera de su hábitat) */
  --zeo-light: #E8F0FF;                        /* relámpago: la LUZ de Tormenta (2026-08-24) */
  --zeo-pupil: #0B0B0D;
  --zeo-riesgo: #FF7A6E;
  --zeo-exito: #4FD1A0;
  --zeo-surface: var(--surface, #121214);
  --zeo-border: var(--border-2, #34343A);
  --zeo-ink: var(--text, #EDF2F7);
  --zeo-ease: cubic-bezier(.23,1,.32,1);       /* ease-out fuerte (UI) */
  --zeo-ease-io: cubic-bezier(.77,0,.175,1);   /* in-out fuerte (poses en pantalla) */

  /* Pose por estado (las transiciones de los miembros leen estas variables) */
  --zeo-lid: 0;          /* párpado superior 0..1 */
  --zeo-lid-low: 0;      /* párpado inferior 0..1 (sonrisa de ojos) */
  --zeo-halo: .38;       /* opacidad del halo */
  --zeo-fil: .85;        /* 🔴 opacidad del filamento. Era .22, luego .3, y AUN ASÍ no se
                            distinguía del cuerpo: plata al 30 % sobre grafito es casi
                            grafito (lo reportó Hidalgo: «no se logra ver bien»). El rayo
                            es la firma del personaje; si hay que apuntarlo para verlo, no
                            está cumpliendo su función. */
  --zeo-chispa: .88;     /* opacidad de la chispa suelta — la manda el ESTADO, igual que
                            el filamento: sin energía no puede haber una chispa orbitando */
  --zeo-brow: 0;         /* opacidad de cejas */
  --zeo-brow-tilt: 0deg; /* inclinación de cejas (V de alerta = positivo) */
  --zeo-eye-scale: 1;    /* apertura extra de ojos (curioso/escucha) */
  --zeo-boca: 1;         /* 🔴 la boca YA NO es de dos estados (orden Hidalgo 2026-08-09:
                            «quisiera también que le pongas una boca»). Nació el 05-ago
                            existiendo solo en `pensando` y `hablando`, con la idea de que
                            así informara por sí sola; en la práctica ZEO pasa casi todo el
                            tiempo en `idle` y quedaba una cara sin boca. Ahora está
                            SIEMPRE, cerrada (`--zeo-boca-sy:.34`), y lo que sigue
                            informando es el MOVIMIENTO: mastica al pensar, se abre al
                            hablar. Se apaga solo donde una boca mentiría: dormido y
                            no_molestar. */
  --zeo-boca-sy: .34;    /* abertura: .34 = labio cerrado · 1 = boca abierta */
  --zeo-pose-y: 0px;     /* cuerpo sube/baja */
  --zeo-pose-sx: 1;      /* squash/stretch */
  --zeo-pose-sy: 1;
  --zeo-lean: 0deg;      /* inclinación hacia el cursor (curioso) */

  position: fixed; z-index: 70;
  left: 18px; bottom: 84px;
  width: 96px; height: 120px;
  cursor: grab; user-select: none; -webkit-user-select: none; touch-action: none;
}
.zeo-root:active{ cursor: grabbing; }
.zeo-root:focus-visible{
  /* !important: los skins del dashboard imponen outline var(--accent)!important
     sobre todo [tabindex]; el foco de ZEO es su propia luz plata */
  outline: 2px solid var(--zeo-light) !important;
  outline-offset: 6px; border-radius: 24px;
}

/* Capas: root(arrastre) > float(bucle de flote) > pose(estado, transicionada) > svg */
.zeo-float{ width:100%; height:100%; will-change: transform; }
.zeo-pose{
  width:100%; height:100%;
  transform: translateY(var(--zeo-pose-y)) scale(var(--zeo-pose-sx), var(--zeo-pose-sy));
  transition: transform .42s var(--zeo-ease-io);
}
.zeo-svg{ width:100%; height:100%; display:block; overflow:visible; }

/* Todo miembro del SVG transforma sobre su propia caja */
.zeo-svg *{ transform-box: fill-box; }

/* ---- halo: luz detrás del cuerpo (gradiente radial, no filtro) ---- */
.zeo-halo{
  opacity: var(--zeo-halo);
  transition: opacity .6s var(--zeo-ease);
  transform-origin: center;
}

/* ---- cuerpo y cola ---- */
.zeo-body{
  fill: var(--zeo-body-fill);
  stroke: color-mix(in srgb, var(--zeo-light) 42%, transparent);
  stroke-width: 1;
  transition: stroke .4s ease;
}
.zeo-shine{
  fill:none; stroke: var(--zeo-light); stroke-width:2; stroke-linecap:round;
  opacity:.30; transition: opacity .4s ease;
}
/* ---- cola = CHISPA SUELTA (2026-08-09) ----
   Ya no cuelga del cuerpo: orbita separada de él. Eso cambia su color, y no por
   gusto: mientras era un apéndice se dibujaba SOBRE el grafito de ZEO y podía ir
   en grafito con filo de plata. Suelta se dibuja sobre el FONDO DE LA PÁGINA, así
   que le aplica la regla que ya costó un despliegue (§3.1 de la biblia, «en el
   lado claro no se ven»): lo que se apoya en la página usa `--zeo-ink`, que sigue
   al tema —tinta en claro, plata en oscuro—, JAMÁS `--zeo-light`, que es plata
   siempre y desaparece sobre papel. */
.zeo-tail{
  fill: var(--zeo-ink);
  fill-opacity: var(--zeo-chispa);
  stroke: none;
  transform-origin: 50% 100%;   /* pivota por su punta baja, la que mira al cuerpo */
  transition: transform .42s var(--zeo-ease-io), fill-opacity .5s ease, fill .4s ease;
}

/* ---- filamento (el rayo interior) + chispa ---- */
/* 🔴 RELLENO, no trazo (2026-08-10). Como línea de 3,6 el rayo no tenía SILUETA, y
   sin silueta el ojo le busca otro significado — entre los ojos se leía como nariz.
   Relleno tiene la forma completa del rayo, que es la firma del personaje. */
.zeo-fil{
  fill: var(--zeo-light); stroke:none;
  opacity: var(--zeo-fil);
  transition: opacity .5s ease, fill .4s ease;
}
.zeo-spark{
  fill: var(--zeo-light); opacity:0;
  /* Eje del rayo relleno: entra por el corte de arriba, baja por el quiebre y sale
     por la punta. No es el contorno (recorrerlo daría una vuelta completa, que se
     leería como un borde encendiéndose, no como energía bajando). */
  /* Puntos MEDIDOS uno a uno con `isPointInFill` contra el relleno del rayo, no
     calculados a ojo: el limbo de arriba es una cuña estrecha (a y=61 mide 1,5
     unidades de ancho) y una diagonal «obvia» se salía por el filo. */
  offset-path: path('M52.1 59.5 L50 63 L47.2 68 L47.6 72 L46.4 76 L44.9 80');
  offset-distance: 0%;
  transition: opacity .3s ease;
}

/* ---- boca (orden Hidalgo 2026-08-05) ----
   La biblia decía «no tiene boca»; la orden la agrega, y para que siga SIGNIFICANDO
   algo existe solo cuando ZEO trabaja la palabra: `pensando` y `hablando`. En los
   otros 9 estados no está —no es que esté cerrada: no está— y la cara es la de
   siempre. Es el MISMO trazo de plata de las cejas y el filamento, no una carita
   pegada: a 24 px la silueta (guijarro + cola + dos ojos) no cambia.
   Nace apagada por variable: si el navegador no produce frames, la boca aparece o
   no aparece por CSS, nunca por una animación a medio correr (regla §3.1 de la
   biblia: fallar hacia visible, jamás depender del compositor). */
.zeo-boca{
  opacity: var(--zeo-boca);
  transform-origin: center;
  /* Aplastar el grupo = boca cerrada (los dos labios se juntan en una línea). Es la
     pose BASE: con reduced-motion o sin compositor la boca se queda aquí, visible y
     quieta. La escala va en el GRUPO, nunca en cada labio (se descuadrarían). */
  transform: scaleY(var(--zeo-boca-sy));
  /* SIN transición de opacidad, y es la cuarta vez que este archivo aprende lo mismo
     (§3.1 de la biblia): una transición NO AVANZA si el navegador no produce frames
     —pestaña de fondo, headless—, así que la boca se quedaba con la opacidad vieja, o
     sea invisible, mientras el estado ya decía «pensando». Lo destapó medir la opacidad
     real en el editor. La opacidad sale DIRECTA de la variable; lo que se transiciona
     es la forma, que si no corre no impide ver nada. */
  transition: transform .3s var(--zeo-ease);
}
.zeo-labio{
  fill:none; stroke: var(--zeo-light);
  stroke-linecap:round; stroke-linejoin:round;
}
/* El labio de arriba es más fino y casi recto: es lo que evita la sonrisa boba. */
.zeo-labio.sup{ stroke-width:1.9; opacity:.85 }
.zeo-labio.inf{ stroke-width:2.2 }

/* ---- ojos ---- */
.zeo-eye-g{ transform-origin: center; transform: scaleY(var(--zeo-eye-scale));
  transition: transform .32s var(--zeo-ease); }
.zeo-eyeball{ fill: var(--zeo-light); }
.zeo-pupil{
  fill: var(--zeo-pupil);
  /* la persecución del cursor la escribe JS en style.transform (spring propio);
     esta transición cubre los saltos de pose entre estados */
  transition: transform .28s var(--zeo-ease);
  will-change: transform;
}
.zeo-lid{
  fill: var(--zeo-body-fill);
  transform-origin: 50% 0%;
  transform: scaleY(var(--zeo-lid));
  transition: transform .3s var(--zeo-ease);
}
.zeo-lid-low{
  fill: var(--zeo-body-fill);
  transform-origin: 50% 100%;
  transform: scaleY(var(--zeo-lid-low));
  transition: transform .3s var(--zeo-ease);
}
/* parpadeo: JS agrega .blink un instante */
.zeo-root.blink .zeo-lid{ transform: scaleY(1); transition-duration: .09s; }

/* ---- cejas ---- */
.zeo-brow{
  fill:none; stroke: var(--zeo-light); stroke-width:2.5; stroke-linecap:round;
  opacity: var(--zeo-brow);
  transition: opacity .3s ease, transform .3s var(--zeo-ease);
  transform-origin: center;
}
.zeo-brow.bl{ transform: rotate(calc(var(--zeo-brow-tilt) * 1)); }
.zeo-brow.br{ transform: rotate(calc(var(--zeo-brow-tilt) * -1)); }

/* ---- anillos de escucha (pulso blanco) ---- */
.zeo-ring{
  fill:none; stroke: var(--zeo-light); stroke-width:1.5;
  opacity:0; transform-origin:center;
}

/* ---- figura completa: inclinación hacia el cursor ---- */
.zeo-figure{
  transform-origin: 50% 62%;
  transform: rotate(var(--zeo-lean));
  transition: transform .5s var(--zeo-ease);
}

/* =========================================================================
   BUCLES DE VIDA (solo transform/opacity; se pausan con .zeo-paused)
   ========================================================================= */
/* REGLA DE RENDIMIENTO: JAMÁS var()/calc() dentro de @keyframes — obliga a
   recalcular estilo por frame en el hilo principal (medido: rompía el
   presupuesto de 2 % CPU). Los bucles usan literales del estado que los usa. */
@keyframes zeoFloat{ 0%,100%{ transform: translateY(0) } 50%{ transform: translateY(-3px) } }
@keyframes zeoBreath{ 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.012) } }
@keyframes zeoBreathSlow{ 0%,100%{ transform: translateY(4px) scale(1) }
  50%{ transform: translateY(4px) scale(1.02) } }
@keyframes zeoSway{ 0%,100%{ transform: rotate(-3deg) } 50%{ transform: rotate(3deg) } }
@keyframes zeoHaloBreath{ 0%,100%{ opacity: .38 } 50%{ opacity: .48 } }
@keyframes zeoSparkRun{
  0%{ offset-distance:0%; opacity:0 } 12%{ opacity:.95 }
  88%{ opacity:.95 } 100%{ offset-distance:100%; opacity:0 } }
@keyframes zeoRing{
  0%{ transform: scale(.55); opacity:.5 }
  100%{ transform: scale(1.25); opacity:0 } }
@keyframes zeoFilTalk{ 0%,100%{ opacity:.28 } 50%{ opacity:.6 } }
@keyframes zeoDot{ 0%,100%{ opacity:.55 } 50%{ opacity:1 } }
/* Masticar la idea: el labio se corre a un lado y se aprieta, como quien masculla
   mientras calcula. Ciclo largo (1,8 s) a propósito — rápido leería como parloteo,
   y ZEO pensando no parlotea. Los valores van LITERALES, no con var(): un var()
   dentro de @keyframes obliga a recalcular estilo por frame (regla medida con CDP).
   El scaleY vive dentro de cada paso porque la animación reemplaza el transform
   completo de la regla base. */
@keyframes zeoBocaMastica{
  0%,100%{ transform: translateX(0) scale(1,.34) }
  22%    { transform: translateX(1.5px) scale(.82,.30) }
  50%    { transform: translateX(-1.5px) scale(.86,.40) }
  76%    { transform: translateX(.7px) scale(.95,.32) }
}
/* Hablar: se abre y cierra al mismo ritmo (.42 s) con que pulsa el filamento, así
   la luz del pecho y la boca dicen lo mismo en vez de contarse por separado. */
@keyframes zeoBocaHabla{
  0%,100%{ transform: scale(1,.5) }
  50%    { transform: scale(.94,1.15) }
}

/* =========================================================================
   ESTADOS — la máquina vive en data-state; las poses transicionan solas
   ========================================================================= */

/* idle: flote (HTML, compositado); parpadeo/mirada/cola = gestos de JS.
   Medido con CDP: animar en bucle hijos del SVG (halo, cola) recalcula estilo
   por frame en el hilo principal — en idle solo queda el flote; la cola se
   balancea por ráfagas (WAAPI en zeo.js) y el halo respira con el cuerpo. */
.zeo-root[data-state="idle"] .zeo-float{ animation: zeoFloat 4.4s ease-in-out infinite; }

/* dormido: baja, cierra, casi sin luz */
.zeo-root[data-state="dormido"]{
  --zeo-lid:1; --zeo-halo:.08; --zeo-fil:.06; --zeo-chispa:.12; --zeo-pose-y:4px;
  --zeo-boca:0;
}
.zeo-root[data-state="dormido"] .zeo-pose{ animation: zeoBreathSlow 5.5s ease-in-out infinite; }

/* despierta: la coreografía la dirige JS (WAAPI); la pose destino es idle */

/* curioso: cejas arriba, ojos 8% más abiertos, inclinado (JS fija --zeo-lean ±6°) */
.zeo-root[data-state="curioso"]{
  --zeo-brow:.9; --zeo-brow-tilt:-6deg; --zeo-eye-scale:1.08; --zeo-halo:.46;
}
.zeo-root[data-state="curioso"] .zeo-float{ animation: zeoFloat 4.4s ease-in-out infinite; }

/* pensando: párpados a media asta, la chispa recorre el filamento y la boca mastica.
   La boca aparece aquí porque es donde ZEO trabaja la palabra; el bucle vive en un
   hijo del SVG, que solo se permite en estados de paso (nunca en idle) — igual que
   la chispa, que ya lo hacía desde el 30-jul. */
.zeo-root[data-state="pensando"]{
  --zeo-lid:.45; --zeo-fil:.5; --zeo-halo:.42; --zeo-boca:1; --zeo-boca-sy:.34;
}
.zeo-root[data-state="pensando"] .zeo-float{ animation: zeoFloat 7s ease-in-out infinite; }
.zeo-root[data-state="pensando"] .zeo-spark{ animation: zeoSparkRun 1.4s linear infinite; }
.zeo-root[data-state="pensando"] .zeo-boca{ animation: zeoBocaMastica 1.8s ease-in-out infinite; }
.zeo-root[data-state="pensando"] .zeo-pupil{ transform: translate(-1.5px,-2.5px); }

/* hablando: filamento pulsa, la boca se abre y cierra al mismo compás
   (el micro-rebote por sílaba lo pone JS con WAAPI) */
.zeo-root[data-state="hablando"]{ --zeo-halo:.5; --zeo-boca:1; --zeo-boca-sy:.5; }
.zeo-root[data-state="hablando"] .zeo-fil{ animation: zeoFilTalk .42s ease-in-out infinite; }
.zeo-root[data-state="hablando"] .zeo-boca{ animation: zeoBocaHabla .42s ease-in-out infinite; }
.zeo-root[data-state="hablando"] .zeo-float{ animation: zeoFloat 4.4s ease-in-out infinite; }

/* escucha: quieto (atención), ojos bien abiertos, pulsos blancos concéntricos */
.zeo-root[data-state="escucha"]{ --zeo-eye-scale:1.1; --zeo-halo:.55; }
.zeo-root[data-state="escucha"] .zeo-ring{ animation: zeoRing 2s var(--zeo-ease) infinite; }
.zeo-root[data-state="escucha"] .zeo-ring.r2{ animation-delay: 1s; }

/* alerta: cuerpo tenso, cejas en V, cola rígida, tinte riesgo — SIEMPRE con chip de texto */
.zeo-root[data-state="alerta"]{
  --zeo-brow:1; --zeo-brow-tilt:14deg; --zeo-pose-sx:1.05; --zeo-pose-sy:.94;
  --zeo-fil:.55; --zeo-halo:.3;
}
.zeo-root[data-state="alerta"] .zeo-brow{ stroke: var(--zeo-riesgo); }
/* El rayo ya no es trazo sino relleno: se tiñe con `fill`. Con `stroke` quedaba
   invisible en alerta —el estado que MÁS necesita que se vea. */
.zeo-root[data-state="alerta"] .zeo-fil{ fill: var(--zeo-riesgo); }
.zeo-root[data-state="alerta"] .zeo-body{ stroke: color-mix(in srgb, var(--zeo-riesgo) 55%, transparent); }
/* La chispa se queda RÍGIDA y toma el tinte de riesgo, como la cola cuando colgaba.
   Va MEZCLADA con `--zeo-ink` y no en `--zeo-riesgo` puro: el riesgo es un color fijo
   pensado para dibujarse sobre el grafito del cuerpo, y medido sobre papel da 2,6:1
   —bajo el mínimo de 3:1 para gráficos—. La chispa está suelta sobre la PÁGINA, así
   que necesita un color que siga al tema; mezclada mide 5,4:1 en claro y sigue
   leyéndose como riesgo. Mismo recurso que celebra. */
.zeo-root[data-state="alerta"] .zeo-tail{
  transform: rotate(0deg);
  fill: color-mix(in srgb, var(--zeo-riesgo) 60%, var(--zeo-ink));
}

/* celebra: sonrisa de ojos; el salto y el florecer del halo los dirige JS (una vez) */
.zeo-root[data-state="celebra"]{ --zeo-lid-low:.55; --zeo-halo:.6; --zeo-fil:.5; --zeo-chispa:1; }
.zeo-root[data-state="celebra"] .zeo-fil{ fill: color-mix(in srgb, var(--zeo-exito) 55%, var(--zeo-light)); }
.zeo-root[data-state="celebra"] .zeo-tail{ fill: color-mix(in srgb, var(--zeo-exito) 45%, var(--zeo-ink)); }

/* no_molestar: se apaga a un punto de luz */
.zeo-root[data-state="no_molestar"]{ --zeo-halo:1; }
.zeo-root[data-state="no_molestar"] .zeo-pose{ transform: scale(.16) translateY(160px); }
.zeo-root[data-state="no_molestar"] .zeo-body, .zeo-root[data-state="no_molestar"] .zeo-tail,
.zeo-root[data-state="no_molestar"] .zeo-figure .zeo-face, .zeo-root[data-state="no_molestar"] .zeo-fil,
.zeo-root[data-state="no_molestar"] .zeo-spark, .zeo-root[data-state="no_molestar"] .zeo-shine{
  opacity:0; transition: opacity .35s ease;
}
.zeo-root[data-state="no_molestar"] .zeo-halo{ animation: zeoDot 6s ease-in-out infinite; }

/* sin_motor: párpados al 60%, cejas caídas, filamento APAGADO — y la chispa TAMBIÉN.
   Una chispa orbitando mientras el motor está caído sería la interfaz mintiendo:
   diría «hay energía» junto al chip que dice que no la hay. El enchufe desconectado
   (§3.1) es el accesorio que informa; la chispa se calla. */
.zeo-root[data-state="sin_motor"]{
  --zeo-lid:.6; --zeo-brow:.8; --zeo-brow-tilt:-10deg; --zeo-fil:.04; --zeo-halo:.14;
  --zeo-chispa:0;
}
.zeo-root[data-state="sin_motor"] .zeo-float{ animation: zeoFloat 8s ease-in-out infinite; }

/* =========================================================================
   CHIP DE ESTADO (texto SIEMPRE acompaña al color) y BURBUJA
   ========================================================================= */
.zeo-chip{
  position:absolute; left:50%; top:100%; transform: translate(-50%,2px);
  max-width: 220px; width:max-content;
  font-family: var(--mono, ui-monospace, monospace);
  font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color: var(--zeo-ink);
  background: var(--zeo-surface);
  border:1px solid var(--zeo-border);
  border-radius:4px; padding:3px 8px;
  opacity:0; transition: opacity .25s ease;
  pointer-events:none; text-align:center; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
}
.zeo-chip.show{ opacity:1; }
.zeo-root[data-state="alerta"] .zeo-chip{ border-color: var(--zeo-riesgo); color: var(--zeo-riesgo); }
.zeo-root[data-state="sin_motor"] .zeo-chip{ border-color: var(--zeo-border); }

.zeo-say{
  position:fixed; z-index:71; max-width:320px;
  background: var(--zeo-surface); color: var(--zeo-ink);
  border:1px solid var(--zeo-border); border-radius:12px;
  padding:12px 14px; font-size:13px; line-height:1.5;
  box-shadow: 0 16px 40px rgba(0,0,0,.35);
  opacity:0; transform: translateY(6px) scale(.98);
  transition: opacity .18s var(--zeo-ease), transform .18s var(--zeo-ease);
  pointer-events:none;
}
.zeo-say.show{ opacity:1; transform:none; pointer-events:auto; }
.zeo-say .zs-t{
  font-family: var(--mono, ui-monospace, monospace);
  font-size:9.5px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color: var(--zeo-light); opacity:.75; margin-bottom:5px;
}

/* =========================================================================
   ACCESORIOS (orden Hidalgo 2026-07-31: «cuando se mueva que le salga una nube y
   cuando piense que saque un libro; inventa más animaciones»)
   Biblia: docs/ZEO-PERSONAJE.md §3.1.

   Cada accesorio DICE ALGO: es una señal redundante del estado, no adorno — el
   chip ya lleva el texto y esto lo refuerza en un vistazo. Por eso hay uno por
   estado con significado y ninguno "porque queda bonito".

   PRESUPUESTO DE CPU (la regla que gobierna este archivo): los accesorios viven
   en HTML compositado —no como hijos del SVG—, animan SOLO transform/opacity, y
   ninguno tiene bucle perpetuo en `idle`. El libro late solo mientras se piensa
   y la nube existe solo mientras se arrastra: en reposo, cero frames nuevos.
   ========================================================================= */
.zeo-props{
  position:absolute; inset:0; pointer-events:none;
  /* Por encima de la figura pero dentro del root, así el arrastre lo lleva consigo. */
  z-index:2;
}
/* NACE VISIBLE. Antes nacía en `opacity:0` y una clase `.on` lo encendía por
   transición — y una transición NO AVANZA si el navegador no produce frames
   (pestaña de fondo, headless): la clase quedaba puesta y la opacidad congelada en
   0, o sea invisible para siempre. Es el mismo error que el del `requestAnimationFrame`
   por otra puerta, y lo destapó medir la opacidad REAL en vez de la clase.
   REGLA: la visibilidad no depende de que haya animación. La entrada la hace WAAPI
   desde zeo.js como adorno; si no corre, el accesorio simplemente ya está ahí. */
.zeo-prop{
  position:absolute; opacity:1; will-change:transform,opacity;
  /* 🔴 HALO DE SEPARACIÓN (orden Hidalgo 2026-08-10: «el rayo y el signo no se ven
     en lo negro»). Un accesorio no se apoya sobre UN fondo: cae medio sobre la
     página y medio sobre el CUERPO de ZEO, que es grafito casi negro SIEMPRE, en los
     dos temas (`--zeo-body-fill:#1A1A1D`, fijo a propósito). Por eso ningún color
     plano puede funcionar solo, y por eso este archivo ya cambió de token dos veces
     sin resolverlo: `--zeo-light` desaparecía sobre la página clara (07-31) y
     `--zeo-ink` —que es tinta oscura en tema claro— desaparece sobre el cuerpo. Cada
     arreglo tapaba la mitad que el anterior destapaba.
     La salida no es elegir mejor el color: es que el glifo lleve su propio contraste.
     Un halo de plata detrás del trazo lo despega del cuerpo negro, y sobre la página
     clara es casi invisible (la tinta sigue haciendo el trabajo). En tema oscuro el
     glifo ya es plata y el halo solo lo engorda un pelo.
     Es un filtro ESTÁTICO, no animado: no entra en el presupuesto de idle. */
  filter: drop-shadow(0 0 1.6px color-mix(in srgb, var(--zeo-light) 72%, transparent))
          drop-shadow(0 0 3px color-mix(in srgb, var(--zeo-light) 34%, transparent));
}
.zeo-prop svg{ display:block; overflow:visible }
/* OJO con el token: los accesorios se dibujan SOBRE LA PÁGINA, no sobre el cuerpo
   de ZEO. `--zeo-light` es plata casi blanca —contrasta contra el grafito del cuerpo
   pero DESAPARECE en tema claro— y así se desplegaron el 2026-07-31 (lo reportó
   Hidalgo: «en el lado claro no se ven»). El token correcto es `--zeo-ink`, que
   sigue al tema (`var(--text)`): tinta en claro, plata en oscuro. Es el mismo que
   usan el chip y la burbuja, que sí se veían en ambos. */
.zeo-prop path, .zeo-prop circle, .zeo-prop line, .zeo-prop rect{
  stroke:var(--zeo-ink); stroke-width:1.7; fill:none;
  stroke-linecap:round; stroke-linejoin:round;
}
/* Colocación por accesorio: cada uno donde el gesto lo pide, sin taparle la cara. */
.zeo-prop--libro{ right:-12px; top:54px; width:44px }       /* apoyado, lado libre de la cola */
/* 🔴 Estos cuatro vivían arriba a la DERECHA, que es donde desde el 2026-08-09 vive
   la chispa suelta: se pisaban y no se entendía ninguno de los dos (lo reportó
   Hidalgo con una captura del `?` encima del rayo). El rincón derecho es de la
   chispa; los accesorios se mudan al IZQUIERDO. */
.zeo-prop--zzz{ left:0; top:2px; width:26px }
.zeo-prop--pregunta{ left:4px; top:-2px; width:16px }
.zeo-prop--alerta{ left:6px; top:-2px; width:14px }
.zeo-prop--rayo{ left:0; top:-6px; width:24px }
.zeo-prop--enchufe{ left:-18px; top:60px; width:42px }
/* El signo de alerta y el rayo son señales de estado: llevan su color semántico,
   pero NUNCA son la única señal (el chip siempre trae texto). */
/* El riesgo puro (#D97B72) sobre página clara da 2.8:1 — por debajo del mínimo de
   3:1 para gráficos. Se mezcla hacia la tinta del tema: conserva el HUE de riesgo y
   gana contraste en ambos lados (medido 5.2:1 en claro, alto en oscuro). */
.zeo-prop--alerta path, .zeo-prop--alerta circle{
  stroke:color-mix(in srgb, var(--zeo-riesgo) 70%, var(--zeo-ink));
}
.zeo-prop--rayo path{ stroke:var(--zeo-ink); fill:var(--zeo-exito); fill-opacity:.34 }
/* La página del libro late solo mientras piensa; la mueve WAAPI desde zeo.js. */
.zeo-prop--libro .zeo-hoja{ transform-origin:16px 10px }

/* Nube de arrastre: bocanadas de una sola pasada. Se crean al mover y se borran
   al terminar su animación — no hay ninguna viva cuando ZEO está quieto. */
.zeo-puff{
  position:fixed; z-index:69; pointer-events:none;
  width:18px; height:12px; border-radius:9px;
  /* OJO DOBLE aquí. (1) Sobre página blanca, plata = invisible. (2) La nube vive en
     <body>, FUERA de `.zeo-root`, y `--zeo-ink` está declarado DENTRO del root: usarlo
     aquí lo deja sin valor y la nube se queda sin fondo en los dos temas (medido:
     1.1:1 en oscuro). Se usa `--text`, que es token de `:root` del dashboard y sí se
     ve desde aquí, con la plata como reserva para cuando ZEO corre suelto. */
  background:var(--text, #EDF2F7); opacity:.26;
  will-change:transform,opacity;
}

/* =========================================================================
   PAUSA (pestaña oculta / fuera de viewport) y REDUCED MOTION
   ========================================================================= */
.zeo-root.zeo-paused *, .zeo-root.zeo-paused{ animation-play-state: paused !important; }

@media (prefers-reduced-motion: reduce){
  .zeo-root *, .zeo-root{ animation: none !important; }
  /* Los accesorios SIGUEN apareciendo —son señal de estado, no movimiento— pero
     entran sin desplazamiento ni escala: solo opacidad. La nube de arrastre no
     aparece en absoluto: es puro movimiento y no dice nada que el chip no diga. */
  .zeo-prop{ transform: none !important; }   /* nace visible; sin gesto de entrada */
  .zeo-puff{ display: none !important; }
  .zeo-pose, .zeo-lid, .zeo-lid-low, .zeo-pupil, .zeo-tail, .zeo-figure, .zeo-eye-g{
    transition: none !important;
  }
  /* La boca SIGUE APARECIENDO: es señal de estado («está pensando»), no movimiento.
     Entra solo con opacidad y se queda en su pose base (labio cerrado, aplastado);
     lo que se va es el masticar y el compás de sílabas. */
  .zeo-halo, .zeo-fil, .zeo-brow, .zeo-chip, .zeo-say{ transition: opacity .2s ease !important; }
  .zeo-boca{ transition: none !important; }   /* aparece y ya: su opacidad no se anima */
}
/* simulación de reduced-motion para /zeo/demo (mismo efecto, activable a mano) */
html[data-zeo-rm="1"] .zeo-root *, html[data-zeo-rm="1"] .zeo-root{ animation: none !important; }
html[data-zeo-rm="1"] .zeo-pose, html[data-zeo-rm="1"] .zeo-lid, html[data-zeo-rm="1"] .zeo-lid-low,
html[data-zeo-rm="1"] .zeo-pupil, html[data-zeo-rm="1"] .zeo-tail, html[data-zeo-rm="1"] .zeo-figure,
html[data-zeo-rm="1"] .zeo-eye-g{ transition: none !important; }
html[data-zeo-rm="1"] .zeo-prop{ transform: none !important; }
html[data-zeo-rm="1"] .zeo-boca{ transition: none !important; }
html[data-zeo-rm="1"] .zeo-puff{ display: none !important; }

@media (max-width:880px){
  .zeo-root{ left:12px; bottom:132px; width:76px; height:95px; }
  .zeo-say{ max-width:82vw; }
}

/* =========================================================================
   EL BUSTO DE MÁRMOL — el cuerpo nuevo (2026-08-19)
   Orden de Hidalgo: la mascota pasa a ser el dios Zeus. Cambia lo que se
   DIBUJA; la máquina de estados, las frases y la API no se tocan.

   Cuatro imágenes superpuestas y una capa de luz. Los once estados salen de
   combinarlas con opacidad y tinte, no de once archivos: a 44 px nadie
   distinguiría once esculturas, y once PNG de museo pesarían megas.
   Solo se animan `opacity` y `transform` (compositor), nunca `filter` en
   bucle: el presupuesto de ≤2 % de CPU en reposo es una regla medida.
   ========================================================================= */
.zeo-busto{position:absolute;inset:0;border-radius:50%;overflow:hidden;
  opacity:0;transition:opacity .3s cubic-bezier(.23,1,.32,1);
  /* Medallón: el anillo de oro y la sombra interior hacen que la piedra se
     lea como una pieza engastada y no como una foto recortada. */
  box-shadow:0 0 0 1.5px var(--oro,#C8A44D),inset 0 2px 10px rgba(0,0,0,.45)}
/* El SVG de respaldo se apaga SOLO cuando la imagen base cargó de verdad. */
.zeo-root.zeo-busto-listo .zeo-busto{opacity:1}
.zeo-root.zeo-busto-listo .zeo-svg{opacity:0;transition:opacity .3s}
.zeo-busto .zb{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  display:block;opacity:0;transition:opacity .22s cubic-bezier(.23,1,.32,1);
  /* 🔴 «No se puede arrastrar bien a Zeus» (Hidalgo 2026-08-20). La mascota ahora es un
     <img> (el busto), y una imagen es ARRASTRABLE de forma nativa: al agarrarla, el
     navegador arrancaba su propio arrastre-fantasma y secuestraba el arrastre por
     pointer-capture. `pointer-events:none` manda el puntero al root (que es quien
     arrastra) y `user-drag:none` mata el gesto nativo. El click sigue llegando al root
     por burbujeo. */
  pointer-events:none;-webkit-user-drag:none;user-drag:none}
.zeo-busto .zb--base{opacity:1}

/* ---- los once estados, derivados de cuatro piezas ---------------------- */
/* hablando: la boca abierta. Alterna con la base al compás del filamento, que
   es el mismo gesto que ya tenía ZEO — la voz no cambió, cambió la cara. */
.zeo-root[data-state="hablando"] .zb--hablando{animation:zb-boca .42s steps(1,end) infinite}
@keyframes zb-boca{0%,49%{opacity:1}50%,100%{opacity:0}}
/* alerta: la luz de las cuencas vira a ámbar. Es la pieza propia, no un filtro:
   un `hue-rotate` sobre mármol lo vuelve amarillo entero. */
.zeo-root[data-state="alerta"] .zb--alerta{opacity:1}
/* sin_motor: la piedra apagada. Dice «no hay motor» sin una palabra, y es el
   estado donde la ausencia de luz ES el mensaje. */
.zeo-root[data-state="sin_motor"] .zb--apagada{opacity:1}
.zeo-root[data-state="dormido"] .zb--apagada{opacity:.72}
.zeo-root[data-state="no_molestar"] .zb--apagada{opacity:.55}

/* ---- la luz de las cuencas -------------------------------------------- */
/* Vive en su propia capa para poder latir sin repintar la escultura. La
   posición está medida sobre la maestra: los ojos caen al 41 % de la altura. */
.zeo-busto .zb-luz{position:absolute;left:50%;top:41%;width:62%;height:13%;
  transform:translate(-50%,-50%);pointer-events:none;opacity:0;
  background:radial-gradient(ellipse at 30% 50%,rgba(46,155,224,.55) 0%,rgba(46,155,224,0) 62%),
             radial-gradient(ellipse at 70% 50%,rgba(46,155,224,.55) 0%,rgba(46,155,224,0) 62%)}
.zeo-root.zeo-busto-listo[data-state="pensando"] .zb-luz{opacity:1;animation:zb-late 1.8s ease-in-out infinite}
.zeo-root.zeo-busto-listo[data-state="curioso"] .zb-luz{opacity:.65}
.zeo-root.zeo-busto-listo[data-state="escucha"] .zb-luz{opacity:1;animation:zb-late 2.6s ease-in-out infinite}
.zeo-root.zeo-busto-listo[data-state="celebra"] .zb-luz{opacity:1}
@keyframes zb-late{0%,100%{opacity:.35}50%{opacity:1}}
/* celebra: el oro del anillo se enciende un instante. El aro es del contenedor,
   así que basta cambiar su sombra — cero repintado de la piedra. */
.zeo-root[data-state="celebra"] .zeo-busto{
  box-shadow:0 0 0 2px var(--oro,#C8A44D),0 0 16px rgba(200,164,77,.55),inset 0 2px 10px rgba(0,0,0,.45)}
.zeo-root[data-state="alerta"] .zeo-busto{
  box-shadow:0 0 0 1.5px #D97B72,inset 0 2px 10px rgba(0,0,0,.45)}
.zeo-root[data-state="sin_motor"] .zeo-busto{
  box-shadow:0 0 0 1.5px rgba(140,140,150,.5),inset 0 2px 10px rgba(0,0,0,.5)}

/* Sin movimiento: la luz se queda encendida y quieta. El estado sigue siendo
   legible —es lo que informa— y desaparece solo la animación. */
@media (prefers-reduced-motion:reduce){
  .zeo-busto .zb,.zeo-busto{transition:none}
  .zeo-root[data-state="hablando"] .zb--hablando{animation:none;opacity:1}
  .zeo-root.zeo-busto-listo[data-state="pensando"] .zb-luz,
  .zeo-root.zeo-busto-listo[data-state="escucha"] .zb-luz{animation:none;opacity:1}
}
