/* ==========================================================================
   ASTRO — CAPA DE MOVIMIENTO: "smooth pero analógico"
   --------------------------------------------------------------------------
   Regla de oro: se anima SÓLO `transform` y `opacity` (compositor, 60 fps).
   La *física* es pesada: arranques lentos, frenadas secas, trinquete al
   encajar y luz de filamento que tarda en apagarse.

   Curvas:
     --ease-mech       masa metálica: cuesta arrancar, frena en seco
     --ease-clack      encaje en ranura, con rebote mecánico
     --ease-hydraulic  compuerta hidráulica: casi todo el recorrido al final
     --ease-ignite     filamento incandescente / fósforo calentándose
   Los números y los parpadeos van con `steps()`, no con suavizado.
   ========================================================================== */

:root {
  --ease-mech: cubic-bezier(0.2, 0.9, 0.25, 1.05);
  --ease-clack: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  --ease-hydraulic: cubic-bezier(0.85, 0, 0.15, 1);
  --ease-ignite: cubic-bezier(0.2, 0.6, 0.2, 1);
  --dur-press: 70ms;
  --dur-ghost: 500ms;
  --dur-ignite: 220ms;
}

/* ==========================================================================
   1. BOTONERA TÁCTICA — resistencia, hundimiento y rastro fantasma
   --------------------------------------------------------------------------
   El truco del "release": la regla base tiene transición LARGA de luz (0,5 s)
   y el estado :active la acorta a 70 ms. Al soltar, la regla base vuelve a
   mandar y la luz se desvanece medio segundo: el rastro fantasma.
   ========================================================================== */

.btn-action,
.btn-accent,
.btn-save,
.btn-success,
.btn-chip,
.btn-view-mode,
.btn-play,
.btn-toggle-map,
.btn-clear-search,
.btn-close-modal,
.btn-file-label,
.btn-open-modal,
.increment-btn,
.tl-preset-btn,
.stats-subtab,
.transit-subtab,
.center-toggle-group .center-btn,
.modal-tab,
.nav-tab {
  transition:
    transform var(--dur-press) var(--ease-mech),
    box-shadow var(--dur-ghost) var(--ease-ignite),
    border-color 200ms ease-in,
    background-color 200ms ease-in,
    color 200ms ease-in,
    text-shadow var(--dur-ghost) var(--ease-ignite);
}

/* Hover: el fósforo se calienta, no salta de golpe (entrada 0,2 s). */
.btn-action:hover,
.btn-accent:hover,
.btn-save:hover,
.btn-success:hover,
.btn-chip:hover,
.btn-view-mode:hover,
.btn-play:hover,
.btn-toggle-map:hover,
.btn-clear-search:hover,
.btn-close-modal:hover,
.btn-file-label:hover,
.btn-open-modal:hover,
.increment-btn:hover,
.tl-preset-btn:hover,
.stats-subtab:hover,
.transit-subtab:hover,
.center-toggle-group .center-btn:hover,
.modal-tab:hover,
.nav-tab:hover {
  transition:
    box-shadow var(--dur-ghost) var(--ease-ignite),
    border-color 200ms ease-in,
    background-color 200ms ease-in,
    color 200ms ease-in,
    text-shadow var(--dur-ghost) var(--ease-ignite);
}

/* Pulsación: baja de verdad y las sombras se comprimen al instante. */
.btn-action:active,
.btn-accent:active,
.btn-save:active,
.btn-success:active,
.btn-chip:active,
.btn-view-mode:active,
.btn-play:active,
.btn-toggle-map:active,
.btn-clear-search:active,
.btn-close-modal:active,
.btn-file-label:active,
.btn-open-modal:active,
.increment-btn:active,
.tl-preset-btn:active,
.stats-subtab:active,
.transit-subtab:active,
.center-toggle-group .center-btn:active,
.modal-tab:active,
.nav-tab:active {
  transform: translateY(4px);
  transition:
    transform var(--dur-press) linear,
    box-shadow var(--dur-press) linear,
    border-color var(--dur-press) linear,
    background-color var(--dur-press) linear;
  box-shadow:
    inset 0 3px 7px rgba(0, 0, 0, 0.75),
    0 0 0 #070b0d,
    0 0 8px rgba(255, 176, 0, 0.18) !important;
}

/* ==========================================================================
   2. DIAL CENTRAL / ROTADOR — trinquete con encaje y encendido de fluorescente
   ========================================================================== */

.center-toggle-group {
  /* la corona dentada gira como un engranaje pesado */
  transition: transform 420ms var(--ease-clack);
}

.center-toggle-group .center-btn {
  position: relative;
  overflow: hidden;
}

/* Encaje en la ranura: giro corto con rebote mecánico. */
.center-btn.mo-clack,
.nav-tab.mo-clack,
.modal-tab.mo-clack,
.stats-subtab.mo-clack,
.transit-subtab.mo-clack,
.btn-view-mode.mo-clack {
  animation: mo-clack 380ms var(--ease-clack) both;
}

@keyframes mo-clack {
  0% { transform: translateZ(0) rotate(0deg) scale(1); }
  35% { transform: translateZ(0) rotate(-1.6deg) scale(0.965); }
  62% { transform: translateZ(0) rotate(0.9deg) scale(1.028); }
  80% { transform: translateZ(0) rotate(-0.35deg) scale(0.996); }
  100% { transform: translateZ(0) rotate(0deg) scale(1); }
}

/* Encendido del símbolo seleccionado: off-on-off-ON (tubo fluorescente).
   Con `steps()` no hay suavizado: son golpes de tensión. */
.mo-ignite {
  animation: mo-ignite var(--dur-ignite) steps(1, end) 1 both;
}

@keyframes mo-ignite {
  0% { opacity: 0.25; filter: none; }
  18% { opacity: 1; }
  30% { opacity: 0.3; }
  46% { opacity: 1; }
  58% { opacity: 0.45; }
  100% { opacity: 1; filter: drop-shadow(0 0 7px rgba(255, 176, 0, 0.75)); }
}

/* Cualquier símbolo del zodíaco que se encienda recibe el resplandor. */
.mo-symbol-lock {
  text-shadow: 0 0 8px currentColor, 0 0 18px currentColor;
}

/* ==========================================================================
   3. CHRONO-ADJUSTER — tirador continuo, lectura digital a saltos
   ========================================================================== */

/* El valor no se desliza: encaja de golpe, dígito a dígito. */
.mo-digit {
  display: inline-block;
  animation: mo-digit-snap 260ms var(--ease-mech) both;
  transform-origin: 50% 100%;
}

@keyframes mo-digit-snap {
  0% { transform: translateY(-55%) scaleY(0.7); opacity: 0; }
  55% { transform: translateY(8%) scaleY(1.04); opacity: 1; }
  100% { transform: translateY(0) scaleY(1); opacity: 1; }
}

/* Destello verde del panel LED cada vez que cambia un valor. */
.mo-led {
  animation: mo-led-flash 420ms steps(3, end) 1 both;
}

@keyframes mo-led-flash {
  0% { color: #d8ffe0; text-shadow: 0 0 12px rgba(74, 246, 38, 0.95), 0 0 26px rgba(74, 246, 38, 0.5); }
  33% { color: #8cff6b; text-shadow: 0 0 10px rgba(74, 246, 38, 0.7); }
  66% { color: #4af626; text-shadow: 0 0 6px rgba(74, 246, 38, 0.45); }
  100% { text-shadow: var(--glow-phosphor); }
}

/* ==========================================================================
   4. PANTALLA CELESTIAL — trazado láser
   --------------------------------------------------------------------------
   Aquí vivía el BARRIDO DE RADAR: una franja verde vertical, con su halo, que
   cruzaba la pantalla de izquierda a derecha cada vez que la rueda se
   redibujaba. Sobraba: pasaba por encima de la gráfica justo cuando hay que
   leerla. Fuera la clase, el `@keyframes mo-sweep` y la función `sweep()`.
   ========================================================================== */

/* --------------------------------------------------------------------------
   LA FORMACIÓN — la tinta se guarda y luego se suelta
   --------------------------------------------------------------------------
   `mo-form`  : la tinta está GUARDADA (invisible). La pone `armFormation()`
                en cuanto la pantalla se apaga, antes de que se vea nada.
   `mo-draw`  : la tinta se SUELTA, pieza a pieza, con el retardo que le toca a
                cada una en línea. Lo pone `playFormation()` al abrirse el
                cristal.

   Antes esto era al revés: la rueda se veía entera, y sólo después se
   escondían sus líneas para dibujarlas otra vez. Entero → nada → piezas.
   -------------------------------------------------------------------------- */

/* Guardada: ni una línea ni un glifo a la vista. */
.mo-form [data-mo-trace] {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.mo-form [data-mo-fade] { opacity: 0; }

/* Soltándose: la geometría se traza, el texto se enciende. `both` mantiene el
   estado inicial durante el retardo, así que una pieza que aún no ha llegado a
   su turno sigue invisible. */
.mo-draw [data-mo-trace] {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: mo-trace 620ms var(--ease-hydraulic) both;
}

.mo-draw [data-mo-fade] {
  animation: mo-fade 340ms var(--ease-ignite) both;
}

/* LOS ASPECTOS VAN DESPACIO. Son las líneas que hay que leer —qué se toca con
   qué— y entran las últimas, cuando los planetas ya están puestos. El resto de
   la rueda se forma rápido; esto se toma su tiempo a propósito. */
.mo-draw [data-mo-trace][data-mo-slow] {
  animation-duration: 1150ms;
}

.mo-draw [data-mo-fade][data-mo-slow] {
  animation-duration: 900ms;
}

@keyframes mo-trace {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

@keyframes mo-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* --------------------------------------------------------------------------
   LAS CASAS — los botones están, se encienden sus lecturas
   --------------------------------------------------------------------------
   `mo-casa-form` : el panel sale SIN LECTURAS. Los doce botones están a la
                    vista desde el primer fotograma —son hardware, como los de
                    un panel analógico—; lo que falta es lo que llevan escrito.
   `mo-casa-draw` : cada lectura se enciende cuando le toca, en el orden
                    barajado que reparte `playHouses()`, y un tercio de las
                    bombillas parpadea antes de cuajar.

   El marco del botón NO se toca nunca: su color de borde dice a qué peldaño de
   la jerarquía pertenece esa casa, y eso es del botón, no de la lectura.
   -------------------------------------------------------------------------- */

.mo-casa-form .house-cell-head,
.mo-casa-form .house-cell-syms { opacity: 0; }

.mo-casa-draw .house-cell-head,
.mo-casa-draw .house-cell-syms {
  animation: mo-casa-fill 420ms var(--ease-ignite) both;
}

/* La bombilla que no entra a la primera. Parpadea hasta que se queda. */
.mo-casa-draw .house-cell[data-parpadeo] .house-cell-head,
.mo-casa-draw .house-cell[data-parpadeo] .house-cell-syms {
  animation-name: mo-casa-parpadeo;
  animation-duration: 1000ms;
  animation-timing-function: linear;
}

@keyframes mo-casa-fill {
  from { opacity: 0; transform: scale(0.62); }
  to { opacity: 1; transform: none; }
}

/* Encendido con titubeo: coge, se cae, vuelve, se queda a medias y por fin
   cuaja. Los tiempos son irregulares a propósito; un parpadeo regular sería
   un estrobo, no una bombilla. */
@keyframes mo-casa-parpadeo {
  0%   { opacity: 0;    transform: scale(0.62); }
  12%  { opacity: 0.75; transform: none; }
  20%  { opacity: 0.06; }
  34%  { opacity: 0.90; }
  44%  { opacity: 0.12; }
  56%  { opacity: 0.95; }
  64%  { opacity: 0.35; }
  76%  { opacity: 1; }
  84%  { opacity: 0.68; }
  100% { opacity: 1;    transform: none; }
}

/* Trazado láser: `pathLength="1"` normaliza todas las longitudes, así un único
   dasharray sirve para círculos, rectas y curvas. El trazado y su `@keyframes`
   viven arriba, con la formación, porque son la misma pieza. */

/* Aquí vivían las RETÍCULAS militares: una cruz de mira que se bloqueaba sobre
   cada planeta dibujado (hasta 16 a la vez, con su aro y su destello). Sobraban
   y ensuciaban la rueda justo cuando acaba de encenderse y hay que leerla. Fue
   una decisión de producto, no un problema técnico: fuera el bloque entero, su
   CSS y sus dos @keyframes. */

/* ==========================================================================
   5. EL FILAMENTO — la bombilla tarda en coger
   --------------------------------------------------------------------------
   Aquí vivía el "scramble" tipo Matrix: letras aleatorias que se iban
   resolviendo, con cursor de bloque parpadeando. Se quitó entero.

   Era un truco DIGITAL y la casa es analógica. Un filamento no escribe: duda.
   Da dos o tres tirones, se queda a medio gas, vuelve a coger y acaba
   encendido. Todo con `opacity`, así que el texto está entero en el DOM
   desde el primer fotograma: esto es luz, no contenido.
   ========================================================================== */

.mo-filament {
  animation: mo-filament 560ms ease-in-out both;
}

@keyframes mo-filament {
  0%   { opacity: 0.06; }
  9%   { opacity: 0.34; }
  15%  { opacity: 0.08; }   /* no engancha */
  26%  { opacity: 0.48; }
  34%  { opacity: 0.16; }   /* vuelve a fallar */
  47%  { opacity: 0.72; }
  56%  { opacity: 0.40; }
  70%  { opacity: 0.94; }
  79%  { opacity: 0.62; }
  90%  { opacity: 1; }
  100% { opacity: 1; }
}

/* ==========================================================================
   6. INACTIVIDAD — la máquina NO respira sola
   --------------------------------------------------------------------------
   Aquí vivía un `crt-breath` de 9 s: la opacidad de las pantallas grandes
   (.wheel-stage es la rejilla ENTERA de la pestaña de la rueda) subía y bajaba
   entre 0,955 y 1. Eso es una fluctuación de luz sobre media interfaz, y no se
   quiere. El tubo ya tiene su propia vida al encenderse; después se queda
   quieto y a plena luz.

   Y aquí vivía también el RÓTULO DE VIGILANCIA: "SCANNING DEEP SPACE 0x…",
   un hex y un decimal cambiando solos más un osciloscopio, encima de cada
   pantalla. Sobraba: era información inventada tapando la gráfica. Fuera.
   ========================================================================== */

/* Grano de película: textura FIJA. Aquí había un `crt-flicker` sobre la capa
   que cubre TODA la página; con el nombre `crt-flicker` repetido además en
   `crt.css`, el navegador se quedaba con el último —el parpadeo rápido del
   tubo— y la página entera latía de 0,88 a 0,04 de opacidad cada 5,5 s.
   Textura quieta y el parpadeo se queda donde tiene que estar: en el tubo. */
body::after {
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='0.5'/></svg>"),
    repeating-linear-gradient(180deg, rgba(0, 0, 0, 0.16) 0 1px, transparent 1px 3px);
  background-repeat: repeat, repeat;
  opacity: 0.22;
}

/* ==========================================================================
   7. BARRAS DE NIVEL — el nivel sube a muescas, no se desliza
   ========================================================================== */

/* Pistas: rejilla de muescas, como el vúmetro de un aparato de agujas. */
.celebrity-score-bar-bg,
.nx-pole-track,
.nx-el-bar,
.prominent-intensity-bar,
.nx-balance {
  background-image: repeating-linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.55) 0 1px,
    transparent 1px 7px
  );
}

/* Relleno a saltos: el nivel avanza muesca a muesca (nada de suavizado). */
.celebrity-score-bar-fill,
.prominent-intensity-fill,
.nx-pole-track i,
.nx-balance > span,
.nx-el-bar > i {
  position: relative;
  transition: width 480ms steps(16, end);
}

/* Cabeza del nivel: el borde de ataque se enciende mientras avanza. */
.celebrity-score-bar-fill::after,
.prominent-intensity-fill::after,
.nx-pole-track i::after,
.nx-el-bar > i::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 2px;
  pointer-events: none;
  background: rgba(230, 255, 220, 0.9);
  box-shadow: 0 0 6px rgba(74, 246, 38, 0.9), 0 0 12px rgba(74, 246, 38, 0.5);
}

/* ==========================================================================
   7. SECUENCIA DE ENCENDIDO — la máquina calienta
   --------------------------------------------------------------------------
   Nada aparece encendido: un panel arranca APAGADO (`mo-off`), coge de uno en
   uno (`mo-on`) y el pitido de "listo" suena cuando el último ha terminado.
   Todo con opacidad: es lo único que el compositor puede animar sin repintar
   el panel entero.

   El encendido es de BOMBILLA, no de relé: no hay un `off-on` limpio, hay un
   filamento que duda. Arranca completamente apagado (0, no 0,07: apagado de
   verdad), da un tirón, se cae, vuelve, se queda a medio gas y por fin coge
   del todo. 980 ms, con curva suave —un filamento no va a saltos— y sin
   `steps()`: eso es un LED, no una bombilla.
   ========================================================================== */

.mo-off {
  opacity: 0;
  transition: none !important;
}

/* Encendido por bombilla: tirón, caída, medio gas, tirón corto, plena luz. */
.mo-on {
  animation: mo-panel-on 980ms var(--ease-ignite) both;
}

@keyframes mo-panel-on {
  0%   { opacity: 0; }
  7%   { opacity: 0.42; }
  12%  { opacity: 0.05; }   /* el filamento no engancha a la primera */
  21%  { opacity: 0.55; }
  29%  { opacity: 0.12; }   /* y se cae otra vez */
  40%  { opacity: 0.68; }
  50%  { opacity: 0.38; }
  62%  { opacity: 0.86; }
  72%  { opacity: 0.64; }
  84%  { opacity: 1; }
  92%  { opacity: 0.9; }
  100% { opacity: 1; }
}

/* El cristal de una pantalla se enciende como un televisor: primero una línea
   horizontal, luego se abre en vertical y se estabiliza.
   El fogonazo va en un ::before con opacidad, NO con `filter: brightness()`:
   filtrar una pantalla grande obliga a rasterizarla en cada fotograma y el
   encendido se caía a ~36 fps. Con opacidad y escala todo lo hace el
   compositor y se mantienen los 60. */
.mo-tv-on {
  animation: mo-tv-on 520ms var(--ease-mech) both;
  transform-origin: 50% 50%;
}

@keyframes mo-tv-on {
  0% { transform: translateZ(0) scaleY(0.004) scaleX(1.03); opacity: 0; }
  9% { transform: translateZ(0) scaleY(0.012) scaleX(1); opacity: 1; }
  26% { transform: translateZ(0) scaleY(1) scaleX(1); }
  38% { opacity: 0.72; }
  52% { opacity: 1; }
  68% { opacity: 0.92; }
  100% { transform: translateZ(0) scaleY(1) scaleX(1); opacity: 1; }
}

.mo-tv-on::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  background: radial-gradient(
    ellipse at 50% 50%,
    rgba(235, 255, 238, 0.92) 0%,
    rgba(140, 255, 120, 0.45) 55%,
    rgba(74, 246, 38, 0) 100%
  );
  animation: mo-tv-flash 520ms var(--ease-ignite) both;
  will-change: opacity;
}

@keyframes mo-tv-flash {
  0% { opacity: 1; }
  20% { opacity: 0.5; }
  38% { opacity: 0.1; }
  50% { opacity: 0.3; }
  68% { opacity: 0.05; }
  100% { opacity: 0; }
}

/* Apertura de abanico de la rueda de cartas guardadas: el aro gira y los
   nodos se despliegan desde el centro. Los 18 `<g>` no llevan atributo
   `transform` (se colocan por coordenadas internas), así que animarlos por CSS
   no pisa su posición: al acabar vuelven solos a su sitio. */
.mo-fan .wheel-chart-node {
  transform-box: view-box;
  transform-origin: 400px 400px;
  animation: mo-fan-out 780ms var(--ease-mech) both;
}

@keyframes mo-fan-out {
  0% { transform: rotate(-54deg) scale(0.5); opacity: 0; }
  45% { opacity: 1; }
  78% { transform: rotate(3deg) scale(1.015); }
  100% { transform: rotate(0deg) scale(1); opacity: 1; }
}

.mo-fan .wheel-conn-line {
  animation: mo-fan-line 520ms var(--ease-hydraulic) both;
}

@keyframes mo-fan-line {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

.mo-fan .wheel-zodiac-slice {
  transform-box: view-box;
  transform-origin: 400px 400px;
  animation: mo-zenith 620ms var(--ease-mech) both;
}

@keyframes mo-zenith {
  0% { transform: rotate(-24deg) scale(0.82); opacity: 0; }
  70% { opacity: 1; }
  100% { transform: rotate(0deg) scale(1); opacity: 1; }
}

/* ==========================================================================
   8. ACCESIBILIDAD — movimiento reducido
   (style.css ya neutraliza animaciones; aquí se apaga lo decorativo.)
   ========================================================================== */

/* Interruptor explícito, no `prefers-reduced-motion`: Brave pone esa media
   query en `reduce` por defecto y dejaba la capa de movimiento entera muerta. */
html.astro-reduce-motion .mo-off { opacity: 1 !important; }
html.astro-reduce-motion .mo-filament { animation: none !important; }

/* Fundido rápido de la rueda natal del modal. Sustituye al trazado pieza a
   pieza cuando la pantalla no lleva tubo CRT: ese recorrido por 360 nodos SVG
   costaba 150-250 ms de tirón al abrir una carta (ver ui_motion.js). */
.mo-quick-in {
  animation: mo-quick-in 180ms ease-out both;
}

@keyframes mo-quick-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

html.astro-reduce-motion .mo-quick-in { animation: none !important; }
