Files
2026-08-01 02:11:52 -05:00

96 lines
3.4 KiB
JavaScript

const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const activeAnimations = new WeakMap();
const visibilityIntent = new WeakMap();
let panelSwapSequence = 0;
export function prefersReducedMotion() {
return reducedMotionQuery.matches;
}
export function animateElement(element, keyframes, options = {}) {
if (!element || prefersReducedMotion() || typeof element.animate !== "function") {
return null;
}
activeAnimations.get(element)?.cancel();
const animation = element.animate(keyframes, {
duration: 220,
easing: "cubic-bezier(0.2, 0.8, 0.2, 1)",
fill: "none",
...options,
});
activeAnimations.set(element, animation);
animation.addEventListener("finish", () => {
if (activeAnimations.get(element) === animation) activeAnimations.delete(element);
}, { once: true });
animation.addEventListener("cancel", () => {
if (activeAnimations.get(element) === animation) activeAnimations.delete(element);
}, { once: true });
return animation;
}
export function revealElement(element, options = {}) {
if (!element) return null;
visibilityIntent.set(element, true);
element.classList.remove("hidden");
element.removeAttribute("hidden");
return animateElement(element, [
{ opacity: 0, transform: `translateY(${options.offset ?? 8}px) scale(${options.scale ?? 0.99})` },
{ opacity: 1, transform: "translateY(0) scale(1)" },
], { duration: options.duration ?? 220 });
}
export async function concealElement(element, options = {}) {
if (!element || element.classList.contains("hidden") || element.hasAttribute("hidden")) return false;
visibilityIntent.set(element, false);
const animation = animateElement(element, [
{ opacity: 1, transform: "translateY(0) scale(1)" },
{ opacity: 0, transform: `translateY(${options.offset ?? -4}px) scale(${options.scale ?? 0.995})` },
], { duration: options.duration ?? 110, easing: "ease-in" });
if (animation) {
try { await animation.finished; } catch {}
}
if (visibilityIntent.get(element) === false) {
element.classList.add("hidden");
return true;
}
return false;
}
export async function swapPanels(outgoing, incoming) {
if (!incoming || outgoing === incoming) return;
const sequence = ++panelSwapSequence;
if (outgoing) await concealElement(outgoing, { duration: 90, offset: -3 });
if (sequence !== panelSwapSequence) return;
revealElement(incoming, { duration: 190, offset: 6 });
}
export function staggerChildren(container, options = {}) {
if (!container || prefersReducedMotion()) return;
const children = Array.from(container.children).slice(0, options.limit ?? 12);
children.forEach((child, index) => {
animateElement(child, [
{ opacity: 0, transform: `translateY(${options.offset ?? 7}px)` },
{ opacity: 1, transform: "translateY(0)" },
], {
duration: options.duration ?? 200,
delay: index * (options.interval ?? 24),
});
});
}
export function emphasizeElement(element, tone = "rgba(56, 189, 248, 0.28)") {
return animateElement(element, [
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
{ transform: "scale(1.012)", boxShadow: `0 0 0 3px ${tone}` },
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
], { duration: 420, easing: "ease-out" });
}
export function animateTextChange(element) {
return animateElement(element, [
{ opacity: 0.45, transform: "translateY(3px)" },
{ opacity: 1, transform: "translateY(0)" },
], { duration: 160 });
}