From a904d9a23d4d80a4acac96aa11856677a44cfaa1 Mon Sep 17 00:00:00 2001 From: yo Date: Tue, 21 Jul 2026 16:20:24 +0200 Subject: [PATCH] =?UTF-8?q?Anima=20la=20apertura=20y=20cierre=20de=20cada?= =?UTF-8?q?=20versi=C3=B3n=20del=20Changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/js/ui/changelog.js | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/game/js/ui/changelog.js b/game/js/ui/changelog.js index 4d786d1..0dfc0bc 100644 --- a/game/js/ui/changelog.js +++ b/game/js/ui/changelog.js @@ -232,9 +232,75 @@ if (boton) boton.classList.remove('novedad'); } + const REDUCE_MOTION = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false; + const DUR = 220; + + // abre/cierra un
animando su altura (WAAPI), en vez del salto + // instantáneo nativo; basado en el patrón estándar de web.dev para
+ function acordeonAnimado(det) { + const sum = det.querySelector('summary'); + let anim = null; + let cerrando = false; + let abriendo = false; + + function alturaAbierta() { return sum.offsetHeight + det.querySelector('.changelog-ul').offsetHeight; } + + function encoger() { + cerrando = true; + const desde = `${det.offsetHeight}px`; + const hasta = `${sum.offsetHeight}px`; + anim?.cancel(); + anim = det.animate({ height: [desde, hasta] }, { duration: DUR, easing: 'ease-out' }); + anim.onfinish = () => terminar(false); + anim.oncancel = () => { cerrando = false; }; + } + + function crecer() { + abriendo = true; + const desde = `${det.offsetHeight}px`; + const hasta = `${alturaAbierta()}px`; + anim?.cancel(); + anim = det.animate({ height: [desde, hasta] }, { duration: DUR, easing: 'ease-out' }); + anim.onfinish = () => terminar(true); + anim.oncancel = () => { abriendo = false; }; + } + + function terminar(abierto) { + det.open = abierto; + anim = null; + cerrando = abriendo = false; + det.style.height = det.style.overflow = ''; + } + + function abrir() { + det.style.overflow = 'hidden'; + det.style.height = `${det.offsetHeight}px`; + det.open = true; + requestAnimationFrame(crecer); + } + + function cerrar() { + if (!det.open) return; + det.style.overflow = 'hidden'; + encoger(); + } + + return { abrir, cerrar, estaAbierto: () => det.open || abriendo }; + } + + // control sin animación (prefers-reduced-motion): mismo interfaz, sin WAAPI + function acordeonSimple(det) { + return { + abrir: () => { det.open = true; }, + cerrar: () => { det.open = false; }, + estaAbierto: () => det.open, + }; + } + function render(cont) { if (!cont || cont.childElementCount) return; // contenido estático: se pinta una sola vez const frag = document.createDocumentFragment(); + const detalles = []; CHANGELOG.forEach((entrada, i) => { const det = document.createElement('details'); det.className = 'cdx'; @@ -251,8 +317,19 @@ } det.appendChild(ul); frag.appendChild(det); + detalles.push(det); }); cont.appendChild(frag); + + detalles.forEach((det) => { + const sum = det.querySelector('summary'); + const ctrl = REDUCE_MOTION ? acordeonSimple(det) : acordeonAnimado(det); + sum.addEventListener('click', (e) => { + e.preventDefault(); + if (ctrl.estaAbierto()) ctrl.cerrar(); + else ctrl.abrir(); + }); + }); } window.Changelog = { render, marcarVisto };