/* motion.css -- todo lo que se mueve en 3x0t1k.
   CURVAS: las dos de app.css y ninguna mas. --ease para lo que entra y se
   asienta (llega y frena); --ease-in-out para lo que CRUZA de lado a lado
   (barridos, lavados) y para los bucles ambientales. Cero cubic-bezier a
   mano en todo el sitio: verificado con grep.
   DURACIONES: la escala de app.css. .25s controles pequenos, .3s botones y
   tarjetas, .45s paneles y halos, .5s entradas, .85s el revelado al bajar.
   Lo que se sale de ahi (el arranque del tubo a 900ms, el vuelo al carrito
   a 620ms, el barrido a 1.05s) es un gesto largo con su porque escrito al
   lado, no un numero suelto.
   Solo transform y opacity; lo demas se justifica. Cero blur().
   will-change solo durante el gesto. Todo bucle se para fuera de
   pantalla y con la pestana oculta. */

:root { --x0-lamp: 900ms; }  /* un T5 real tarda ~0.9s en asentar */

/* --- 1. EL AGUA. El sitio se vende como acuario y fuera del home no se
   notaba. Dos sabanas a 54s y 71s (sin sincronia, o se lee el bucle) y
   diez motas. z-index -1: sobre el lienzo y bajo TODO el contenido.
   Los alfas estan MEDIDOS: al 10% el pixel mas claro del agua dejaba
   --ink-3 en 4.03:1, bajo AA. Asi queda en 4.82:1. */
.x0-water { position: fixed; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; contain: layout paint; }
.x0-water i { position: absolute; inset: -30%; display: block; will-change: transform; }
.x0-water .w1 {
  background: radial-gradient(42% 30% at 22% 18%, rgba(34,232,212,.06), transparent 64%),
              radial-gradient(38% 28% at 78% 62%, rgba(74,140,255,.065), transparent 66%);
  animation: x0-drift-a 54s var(--ease-in-out) infinite alternate;
}
.x0-water .w2 {
  background: radial-gradient(46% 34% at 62% 12%, rgba(139,92,255,.06), transparent 66%),
              radial-gradient(40% 30% at 12% 82%, rgba(255,46,126,.045), transparent 68%);
  animation: x0-drift-b 71s var(--ease-in-out) infinite alternate;
}
@keyframes x0-drift-a { from { transform: translate3d(-2%,1%,0) scale(1.04); } to { transform: translate3d(3%,-2%,0) scale(1.12); } }
@keyframes x0-drift-b { from { transform: translate3d(2%,-1%,0) scale(1.10); } to { transform: translate3d(-3%,2%,0) scale(1.02); } }
/* Duracion, retardo y deriva vienen de motion.js: sin eso las diez
   suben en formacion militar y se ve el truco. */
.x0-mote {
  position: absolute; bottom: -4vh; left: var(--x, 50%);
  width: var(--s, 3px); height: var(--s, 3px); border-radius: 50%;
  background: rgba(180,236,246,.55); opacity: 0; will-change: transform;
  animation: x0-rise var(--d, 40s) linear var(--dl, 0s) infinite;
}
@keyframes x0-rise {
  0% { transform: translate3d(0,0,0); opacity: 0; }
  12%, 84% { opacity: var(--o, .5); }
  100% { transform: translate3d(var(--dx, 3vw), -108vh, 0); opacity: 0; }
}

/* --- 2. TARJETA DE CORAL. La que mas se repite: si se siente cara, el
   sitio se siente caro. Tres capas EN ORDEN, no a la vez.
   (a) el barrido: la linterna que cruza el cristal al llegar. Una pasada
   con forwards para que acabe fuera del encuadre. Va con --ease-in-out
   (token de la casa): con --ease el cruce se comia en 80ms y no se veia. */
.coral-card .shot::after {
  content: ''; position: absolute; inset: -20% -60%; z-index: 1; pointer-events: none; opacity: 0;
  background: linear-gradient(104deg, transparent 40%, rgba(196,246,255,.30) 50%, transparent 60%);
  transform: translate3d(-70%, 0, 0);
}
@media (hover: hover) and (pointer: fine) {
  .coral-card:hover .shot::after { opacity: 1; animation: x0-sweep 1.05s var(--ease-in-out) forwards; }
  /* una pieza vendida no se enciende: el luto tambien es informacion */
  .coral-card:has(.sold-out):hover .shot::after { animation: none; opacity: 0; }
}
@keyframes x0-sweep { from { transform: translate3d(-70%,0,0); } to { transform: translate3d(70%,0,0); } }

/* (b) el halo de .glow-card se despega del borde y sigue al dedo. Mueve
   un radial-gradient (repaint, no composite) pero solo en la UNICA
   tarjeta bajo el cursor y una vez por frame (rAF en motion.js). */
.coral-card.glow-card::before {
  background: radial-gradient(72% 58% at var(--mx, 50%) var(--my, 112%),
              color-mix(in srgb, var(--glow) 30%, transparent), transparent 70%);
}
.coral-card:hover .shot img { will-change: transform, opacity; }

/* (c) la foto de dia no aparece: se ENCIENDE. Baja la actinica, entra un
   resplandor calido por arriba (donde va la pantalla del tanque) y la
   diurna sube detras con .1s. Antes se cruzaban a la vez y se leia como
   un cambio de imagen cualquiera. */
.coral-card .shot::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(86% 62% at 50% -6%, rgba(255,196,104,.20), transparent 72%);
  opacity: 0; transition: opacity .5s var(--ease) .06s;
}
.coral-card:has(.alt-shot):hover .shot::before { opacity: 1; }
.coral-card:has(.alt-shot):hover .shot img[data-light="actinic"] { opacity: .1; }
.coral-card .shot .alt-shot { transition: opacity .6s var(--ease) .1s, transform 1.1s var(--ease); }

/* el corazon late una vez y queda relleno. fill es repaint de un SVG de
   15px: se paga, es la confirmacion del gesto. */
/* Traia dos velocidades para el mismo gesto: `all .25s` y detras
   `transform .3s`, que ganaba. Una sola, la de los controles pequenos
   de app.css (.chip, .input): .25s. */
.coral-card .wish { transition: all .25s var(--ease); }
.coral-card .wish:hover { transform: scale(1.12); }
.coral-card .wish.is-on svg { fill: currentColor; animation: x0-heart .5s var(--ease); }
@keyframes x0-heart { 0%, 100% { transform: scale(1); } 42% { transform: scale(1.42); } }
/* la flecha que se separa: gesto de la casa */
/* .35s hacia .3s: la flecha se movia mas lenta que el boton que la lleva
   (app.css transiciona .btn en .3s) y en el hover se veia el desfase. */
.btn > svg { transition: transform .3s var(--ease); }
.btn:hover > svg:last-child { transform: translateX(4px); }

/* --- 3. EL DIMMER. Era un cambio de opacidad de .45s. Ahora es el
   herraje real: a LUZ DE DIA el tubo arranca (pega, se cae, vuelve,
   asienta en 0.9s) con un lavado calido cruzando el cristal; a ACTINICA
   no hay parpadeo, el LED azul entra seco en 0.35s. Dos luces, dos
   caracteres. app.js escribe opacity inline: una animacion CSS gana a un
   inline en la cascada y al acabar le devuelve el mando. */
.gal-stage.x0-strike img[data-light="daylight"] { animation: x0-tube-on var(--x0-lamp) var(--ease-in-out) both; }
.gal-stage.x0-strike img[data-light="actinic"]  { animation: x0-tube-off var(--x0-lamp) var(--ease-in-out) both; }
@keyframes x0-tube-on  { 0% { opacity: 0; } 8% { opacity: .82; } 12% { opacity: .06; } 21% { opacity: .95; } 26% { opacity: .28; } 40%, 100% { opacity: 1; } }
@keyframes x0-tube-off { 0% { opacity: 1; } 8% { opacity: .3; } 12% { opacity: .85; } 21% { opacity: .12; } 26% { opacity: .5; } 40%, 100% { opacity: 0; } }
.gal-stage.x0-cold img[data-light="actinic"]  { animation: x0-led-on .35s var(--ease) both; }
.gal-stage.x0-cold img[data-light="daylight"] { animation: x0-led-off .35s var(--ease) both; }
@keyframes x0-led-on  { from { opacity: 0; } to { opacity: 1; } }
@keyframes x0-led-off { from { opacity: 1; } to { opacity: 0; } }
/* banda movida con transform, no con background-position: composicion */
.gal-stage::after { content: ''; position: absolute; inset: -10% -80%; z-index: 2; pointer-events: none; opacity: 0; transform: translate3d(-60%, 0, 0); }
.gal-stage.x0-strike::after {
  background: linear-gradient(100deg, transparent 34%, rgba(255,206,128,.30) 50%, transparent 66%);
  animation: x0-wash var(--x0-lamp) var(--ease-in-out) forwards; will-change: transform, opacity;
}
.gal-stage.x0-cold::after {
  background: linear-gradient(100deg, transparent 34%, rgba(120,190,255,.26) 50%, transparent 66%);
  animation: x0-wash .5s var(--ease-in-out) forwards; will-change: transform, opacity;
}
@keyframes x0-wash { 0% { transform: translate3d(-60%,0,0); opacity: 0; } 22% { opacity: 1; } 100% { transform: translate3d(60%,0,0); opacity: 0; } }
/* el boton pulsado guarda el halo de SU luz */
.light-toggle button[aria-pressed="true"] { box-shadow: 0 0 18px -4px rgba(74,140,255,.85); }
.light-toggle button[data-mode="daylight"][aria-pressed="true"] { box-shadow: 0 0 18px -4px rgba(255,176,32,.85); }
/* --- 4. LA ESPERA. Un spinner generico no dice nada: la espera cuenta
   que pasa y a donde va lo que pediste.
   (a) app.js cambia el HTML del boton por un spinner y el boton colapsaba
   de ~120px a 30px saltando la fila: motion.js le congela el ancho. */
[data-add-to-cart][data-busy] { pointer-events: none; }
[data-add-to-cart][data-busy] .spin { color: var(--cyan); }

/* (b) la pieza vuela al carrito: anadir un coral de $400 solo movia un
   numerito en la esquina y nadie lo veia. */
.x0-fly {
  position: fixed; z-index: 190; pointer-events: none; overflow: hidden;
  border-radius: var(--r-md); border: 1px solid color-mix(in srgb, var(--cyan) 55%, transparent);
  box-shadow: 0 0 42px -8px rgba(34,232,212,.7); will-change: transform, opacity;
}
.x0-fly img { width: 100%; height: 100%; object-fit: cover; }

/* (c) filtrar: shop.js ya marca aria-busy en #shopResults. La rejilla se
   hunde y se apaga mientras contesta el servidor; la tanda nueva entra
   escalonada de 45 en 45ms. */
/* El apagado NO toca el texto. Al 38% sobre --void, el nombre del coral
   (--ink) caia a 3.14:1 y la categoria (--ink-3) a 1.62:1: por debajo de AA.
   Que dure 280ms no lo arregla, un texto ilegible es un texto ilegible.
   Se apaga la MASA VISUAL, que son las fotos, y esas no tienen minimo de
   contraste que cumplir. La rejilla entera sigue hundiendose 4px, asi que
   el gesto se lee igual y todos los ratios quedan intactos.
   pointer-events lo pone shop.css, que es de quien es el comportamiento. */
#shopResults { transition: transform .25s var(--ease); }
#shopResults[aria-busy="true"] { transform: translateY(4px); }
#shopResults .shot { transition: opacity .25s var(--ease); }
#shopResults[aria-busy="true"] .shot { opacity: .38; }
/* La tanda nueva entra escalonada SIN JS: shop.js reemplaza el HTML y
   los elementos recien insertados arrancan su animacion solos. El ciclo
   de 4 desfasa tambien las filas, asi la ola sale en diagonal. */
#shopResults .grid > * { animation: x0-in .5s var(--ease) both; }
#shopResults .grid > :nth-child(4n+2) { animation-delay: .06s; }
#shopResults .grid > :nth-child(4n+3) { animation-delay: .12s; }
#shopResults .grid > :nth-child(4n+4) { animation-delay: .18s; }
@keyframes x0-in { from { opacity: 0; transform: translateY(16px); } }

/* (d) el envio recarga la pagina: el boton dice que ya salio o la gente
   pulsa dos veces. */
.btn.is-busy { pointer-events: none; opacity: .72; }
.btn.is-busy::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.22) 50%, transparent 70%);
  transform: translate3d(-100%, 0, 0); animation: x0-scan 1.1s var(--ease-in-out) infinite;
}
@keyframes x0-scan { to { transform: translate3d(100%, 0, 0); } }

/* --- 5. ENTRE PAGINAS. Solo opacity, JAMAS transform: un transform en
   <main> lo vuelve bloque contenedor y rompe la hoja de filtros fija de
   /shop. El fundido corre MIENTRAS carga: no retrasa nada. */
.x0-beam {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 300; pointer-events: none;
  transform: scaleX(0); transform-origin: 0 50%; opacity: 0;
  background: linear-gradient(90deg, var(--actinic), var(--cyan), var(--lime));
  box-shadow: 0 0 12px rgba(34,232,212,.7);
  transition: transform .5s var(--ease), opacity .25s var(--ease);
}
.x0-beam.is-on { opacity: 1; }
html.x0-leaving main { opacity: 0; transition: opacity .25s var(--ease); }
main { animation: x0-page-in .42s var(--ease) both; }
@keyframes x0-page-in { from { opacity: 0; } }

/* --- 6. VACIO Y 404: toda tu atencion y nada que vender. Se lo ganan. */
.empty .icon > svg, .empty > svg { animation: x0-suspend 7.5s var(--ease-in-out) infinite alternate; }
@keyframes x0-suspend { from { transform: translate3d(0,-4px,0) rotate(-1.5deg); } to { transform: translate3d(0,5px,0) rotate(1.5deg); } }
/* 404: el tubo del rotulo esta fundido. Steps duros: un fluorescente
   muerto no se atenua, tartamudea. */
.p-404 .eyebrow { animation: x0-dead-tube 5.2s steps(1) infinite; }
@keyframes x0-dead-tube { 0%, 41%, 43%, 55%, 57%, 84%, 86%, 100% { opacity: 1; } 42%, 56%, 85% { opacity: .18; } 58%, 59% { opacity: .55; } }

/* --- 7. FRENOS */
/* Pestana oculta: se para TODO, tambien las causticas y el neon que ya
   existian. El selector universal solo se evalua al ocultarla. */
html.x0-hidden *, html.x0-hidden *::before, html.x0-hidden *::after { animation-play-state: paused !important; }
.x0-paused, .x0-paused *, .x0-paused *::before, .x0-paused *::after { animation-play-state: paused !important; }

@media (prefers-reduced-motion: reduce) {
  /* app.css ya deja animaciones y transiciones en .01ms bajo reduce:
     aqui solo se salvan los ESTADOS FINALES que quedarian mal (pagina en
     opacity 0, zoom clavado, banda del barrido a la vista). */
  .x0-water, .x0-fly, .x0-beam { display: none !important; }
  html.x0-leaving main { opacity: 1 !important; }
  .coral-card .shot::after, .coral-card .shot::before, .gal-stage::after { opacity: 0 !important; }
  .coral-card:hover .shot img { transform: none; will-change: auto; }
  .coral-card:has(.alt-shot):hover .shot img[data-light="actinic"] { opacity: 1; }
  .coral-card .wish:hover, .coral-card .wish:active { transform: none; }
  #shopResults[aria-busy="true"] { transform: none; }
  #shopResults[aria-busy="true"] .shot { opacity: .55; }
}
