I'm never making an animation again but I'm dying laughing
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user