RAG-PSYCH / api /static /animations.js
arjun10g's picture
Initial deploy to Hugging Face Spaces
08fc97e
// GSAP-powered interactions:
// 1. Hero fade-in on initial page load
// 2. Results card + chunk-card stagger on every HTMX swap
// 3. Citation hover/focus β†’ glow + scale the matching chunk card and
// smooth-scroll it into view; a click locks the highlight briefly
const { gsap } = window;
const LOCK_MS = 1400;
// ─── 1. Page-load entrance ────────────────────────────────────────────────
gsap.from("#hero", { opacity: 0, y: -16, duration: 0.8, ease: "power2.out" });
gsap.from("#search-form", { opacity: 0, y: 20, duration: 0.7, delay: 0.2, ease: "power2.out" });
// ─── 2. Results swap-in animation ─────────────────────────────────────────
document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target.id !== "results") return;
const shell = e.detail.target.querySelector(".results-shell");
if (!shell) return;
gsap.from(shell, { opacity: 0, y: 20, duration: 0.5, ease: "power2.out" });
gsap.from(shell.querySelectorAll(".chunk-card"), {
opacity: 0, y: 14, duration: 0.45, stagger: 0.06, delay: 0.15, ease: "power2.out",
});
hookCitations(shell);
});
// ─── 3. Citation ↔ chunk-card linking ─────────────────────────────────────
function hookCitations(root) {
const cards = new Map();
root.querySelectorAll(".chunk-card").forEach((c) => {
cards.set(c.dataset.chunkId, c);
});
root.querySelectorAll(".citation").forEach((cite) => {
const cid = cite.dataset.chunk;
const target = cards.get(cid);
if (!target) {
// Cited ID not in retrieved set β€” flag as a hallucinated citation.
cite.classList.add("citation-invalid");
return;
}
let lockTimer = null;
const highlight = (lock = false) => {
gsap.to(target, { scale: 1.03, duration: 0.25, ease: "power2.out", overwrite: true });
target.classList.add("chunk-glow");
cite.classList.add("citation-active");
if (lock) {
clearTimeout(lockTimer);
lockTimer = setTimeout(() => unhighlight(), LOCK_MS);
}
};
const unhighlight = () => {
gsap.to(target, { scale: 1, duration: 0.25, ease: "power2.out", overwrite: true });
target.classList.remove("chunk-glow");
cite.classList.remove("citation-active");
};
cite.addEventListener("mouseenter", () => highlight(false));
cite.addEventListener("mouseleave", () => { if (!lockTimer) unhighlight(); });
cite.addEventListener("focus", () => highlight(false));
cite.addEventListener("blur", () => { if (!lockTimer) unhighlight(); });
cite.addEventListener("click", (ev) => {
ev.preventDefault();
highlight(true);
gsap.to(window, {
duration: 0.6,
scrollTo: { y: target, offsetY: 80 },
ease: "power2.inOut",
});
});
});
}