From dc6c7cb825820f264b95e4fbd8cb5656af66c9bb Mon Sep 17 00:00:00 2001 From: KingKDot Date: Fri, 8 May 2026 22:55:41 -0500 Subject: [PATCH] I'm never making an animation again but I'm dying laughing --- Overlord-Server/public/assets/main.css | 58 +++++++++++++++++++++++++ Overlord-Server/public/assets/main.js | 29 ++++++++++++- Overlord-Server/public/assets/render.js | 6 ++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/Overlord-Server/public/assets/main.css b/Overlord-Server/public/assets/main.css index 58a8efe..21ce985 100644 --- a/Overlord-Server/public/assets/main.css +++ b/Overlord-Server/public/assets/main.css @@ -1198,6 +1198,64 @@ article.card[data-bookmarked="true"] { opacity: 0; } +@keyframes cardFlipFall { + 0% { + opacity: 1; + transform: + perspective(1400px) rotateX(0) rotateY(0) rotateZ(0) + translate3d(0, 0, 0) scale(1); + filter: brightness(1) saturate(1); + box-shadow: 0 0 0 rgba(0, 0, 0, 0); + } + 18% { + opacity: 1; + transform: + perspective(1400px) rotateX(6deg) rotateY(-12deg) rotateZ(-2deg) + translate3d(-4px, -16px, 40px) scale(1.04); + filter: brightness(1.18) saturate(1.15); + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.55), + 0 0 32px rgba(244, 63, 94, 0.35); + } + 55% { + opacity: 1; + transform: + perspective(1400px) rotateX(35deg) rotateY(360deg) rotateZ(12deg) + translate3d(40px, 60px, -120px) scale(0.92); + filter: brightness(0.9) saturate(0.9); + box-shadow: + 0 40px 70px rgba(0, 0, 0, 0.6), + 0 0 22px rgba(244, 63, 94, 0.25); + } + 100% { + opacity: 0; + transform: + perspective(1400px) rotateX(75deg) rotateY(720deg) rotateZ(38deg) + translate3d(140px, 110vh, -400px) scale(0.5); + filter: brightness(0.45) saturate(0.6); + box-shadow: 0 0 0 rgba(0, 0, 0, 0); + } +} + +.card-uninstalling { + animation: cardFlipFall 1100ms cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards; + transform-origin: center center; + transform-style: preserve-3d; + backface-visibility: hidden; + pointer-events: none; + position: relative; + z-index: 5; + will-change: transform, opacity, filter; +} + +@media (prefers-reduced-motion: reduce) { + .card-uninstalling { + animation: none; + opacity: 0; + transition: opacity 220ms ease; + } +} + .ripple { position: relative; overflow: hidden; diff --git a/Overlord-Server/public/assets/main.js b/Overlord-Server/public/assets/main.js index 2e59124..29ea315 100644 --- a/Overlord-Server/public/assets/main.js +++ b/Overlord-Server/public/assets/main.js @@ -132,6 +132,26 @@ function getClientCard(clientId) { return document.querySelector(`article[data-id="${selectorId}"]`); } +window.__uninstallingClientIds = window.__uninstallingClientIds || new Set(); + +function flipFallAndRemove(clientId, { delayMs = 0 } = {}) { + const card = getClientCard(clientId); + if (!card || card.classList.contains("card-uninstalling")) return; + window.__uninstallingClientIds.add(clientId); + if (delayMs > 0) card.style.animationDelay = `${delayMs}ms`; + card.classList.add("card-uninstalling"); + + let removed = false; + const cleanup = () => { + if (removed) return; + removed = true; + card.remove(); + setTimeout(() => window.__uninstallingClientIds.delete(clientId), 2500); + }; + card.addEventListener("animationend", cleanup, { once: true }); + setTimeout(cleanup, 1100 + delayMs + 400); +} + function openTagNoteEditor(clientId, currentTag, currentNote) { return new Promise((resolve) => { const overlay = document.createElement("div"); @@ -1440,9 +1460,16 @@ menu.addEventListener("click", async (e) => { markManualDisconnect(contextCard); } + if (action === "uninstall") { + flipFallAndRemove(contextCard); + } + const ok = await sendCommand(contextCard, action); if (ok) { - setTimeout(() => loadWithOptions({ force: true }), 400); + // Wait for the flip-fall to finish before refreshing the grid, otherwise + // the reload would yank the animating card from layout. + const refreshDelay = action === "uninstall" ? 1300 : 400; + setTimeout(() => loadWithOptions({ force: true }), refreshDelay); } closeMenu(clearContext); }); diff --git a/Overlord-Server/public/assets/render.js b/Overlord-Server/public/assets/render.js index 3f7b54c..1992c75 100644 --- a/Overlord-Server/public/assets/render.js +++ b/Overlord-Server/public/assets/render.js @@ -269,10 +269,14 @@ export function createRenderer({ const hadCards = grid.querySelectorAll("article[data-id]").length > 0; + const uninstallingIds = window.__uninstallingClientIds; + items.forEach((client) => { seen.add(client.id); + if (uninstallingIds && uninstallingIds.has(client.id)) return; const existing = grid.querySelector(`article[data-id="${client.id}"]`); if (existing) { + if (existing.classList.contains("card-uninstalling")) return; const digest = cardDigest(client); if (existing._cardDigest === digest) return; existing._cardDigest = digest; @@ -283,7 +287,7 @@ export function createRenderer({ }); Array.from(grid.querySelectorAll("article[data-id]")) - .filter((el) => !seen.has(el.dataset.id)) + .filter((el) => !seen.has(el.dataset.id) && !el.classList.contains("card-uninstalling")) .forEach((el) => el.remove()); if (reorder) {