feat: release 0.7.0 celebration flow

This commit is contained in:
2026-04-16 13:41:22 +02:00
parent ba4a112bbc
commit 67d362f1d9
6 changed files with 340 additions and 6 deletions

View File

@@ -20,9 +20,50 @@
const quickWinSortIds = document.getElementById("quickWinSortIds");
const quickWinSortSave = document.getElementById("quickWinSortSave");
const quickWinToggleButtons = document.querySelectorAll("[data-quick-win-toggle]");
const celebrationLayer = document.getElementById("celebrationLayer");
const celebratePoints = Number.parseInt(document.body.dataset.celebratePoints || "", 10);
const scrollRestoreKey = "putzliga:scroll-restore";
let draggedQuickWin = null;
let quickWinSortDirty = false;
function rememberScrollPosition() {
try {
window.sessionStorage.setItem(
scrollRestoreKey,
JSON.stringify({
path: window.location.pathname,
y: window.scrollY,
}),
);
} catch (_) {
// Ignore storage errors and continue normally.
}
}
function restoreScrollPosition() {
try {
const rawValue = window.sessionStorage.getItem(scrollRestoreKey);
if (!rawValue) {
return;
}
const saved = JSON.parse(rawValue);
window.sessionStorage.removeItem(scrollRestoreKey);
if (!saved || saved.path !== window.location.pathname || typeof saved.y !== "number") {
return;
}
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
window.scrollTo({ top: saved.y, behavior: "auto" });
});
});
} catch (_) {
// Ignore malformed storage data.
}
}
function buildCompletionOptions(button) {
const options = [];
const assignedPairs = [
@@ -60,6 +101,7 @@
choiceButton.textContent = option.label;
choiceButton.addEventListener("click", () => {
dialogChoice.value = option.value;
rememberScrollPosition();
dialog.close();
dialogForm.submit();
});
@@ -74,13 +116,33 @@
}
if (quickTaskOpen && quickTaskDialog) {
quickTaskOpen.addEventListener("click", () => quickTaskDialog.showModal());
quickTaskOpen.addEventListener("click", () => {
if (!quickTaskDialog.open) {
quickTaskDialog.showModal();
}
});
}
if (quickTaskClose && quickTaskDialog) {
quickTaskClose.addEventListener("click", () => quickTaskDialog.close());
quickTaskClose.addEventListener("click", () => {
if (quickTaskDialog.open) {
quickTaskDialog.close();
}
});
}
[dialog, quickTaskDialog].forEach((activeDialog) => {
if (!activeDialog) {
return;
}
activeDialog.addEventListener("click", (event) => {
if (event.target === activeDialog && activeDialog.open) {
activeDialog.close();
}
});
});
function updateQuickWinsState() {
const selectedPresetCount = [...quickWinInputs].filter((input) => input.checked).length;
const customSelected = quickWinCustomToggle?.checked === true;
@@ -123,6 +185,25 @@
});
}
if (dialogForm) {
dialogForm.addEventListener("submit", () => {
rememberScrollPosition();
});
}
document.querySelectorAll('form[action*="/complete"]').forEach((form) => {
form.addEventListener("submit", () => {
rememberScrollPosition();
});
});
const quickWinsForm = document.getElementById("quickWinsForm");
if (quickWinsForm) {
quickWinsForm.addEventListener("submit", () => {
rememberScrollPosition();
});
}
function syncQuickWinSortIds() {
if (!quickWinSortList || !quickWinSortIds) {
return;
@@ -140,6 +221,65 @@
}
}
function clearCelebrationQuery() {
if (!window.history.replaceState) {
return;
}
const url = new URL(window.location.href);
if (!url.searchParams.has("celebrate_points")) {
return;
}
url.searchParams.delete("celebrate_points");
const nextUrl = `${url.pathname}${url.search}${url.hash}`;
window.history.replaceState({}, document.title, nextUrl);
}
function celebrateCompletion(points) {
if (!celebrationLayer || !Number.isFinite(points) || points <= 0) {
return;
}
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
celebrationLayer.hidden = false;
celebrationLayer.setAttribute("aria-hidden", "false");
celebrationLayer.innerHTML = "";
const score = document.createElement("div");
score.className = "celebration-score";
score.textContent = points;
celebrationLayer.appendChild(score);
const glow = document.createElement("div");
glow.className = "celebration-glow";
celebrationLayer.appendChild(glow);
if (!prefersReducedMotion) {
const colors = [
"rgba(94, 168, 255, 0.96)",
"rgba(52, 211, 153, 0.95)",
"rgba(250, 204, 21, 0.92)",
"rgba(191, 219, 254, 0.96)",
];
Array.from({ length: 14 }).forEach((_, index) => {
const particle = document.createElement("span");
particle.className = "celebration-particle";
particle.style.setProperty("--angle", `${Math.round((360 / 14) * index + Math.random() * 18)}deg`);
particle.style.setProperty("--distance", `${72 + Math.round(Math.random() * 44)}px`);
particle.style.setProperty("--delay", `${(Math.random() * 0.08).toFixed(2)}s`);
particle.style.setProperty("--size", `${7 + Math.round(Math.random() * 7)}px`);
particle.style.background = colors[index % colors.length];
celebrationLayer.appendChild(particle);
});
}
window.setTimeout(() => {
celebrationLayer.hidden = true;
celebrationLayer.setAttribute("aria-hidden", "true");
celebrationLayer.innerHTML = "";
}, prefersReducedMotion ? 520 : 1500);
}
if (quickWinSortList) {
syncQuickWinSortIds();
setQuickWinSortDirty(false);
@@ -282,4 +422,11 @@
togglePush().catch((error) => console.error("Push toggle failed", error));
});
}
if (Number.isFinite(celebratePoints) && celebratePoints > 0) {
celebrateCompletion(celebratePoints);
clearCelebrationQuery();
}
restoreScrollPosition();
})();