touchup on celebrations

This commit is contained in:
Guy Sandler 2026-10-13 16:04:57 -07:00
parent 4e7f66d5f7
commit 85ac994d99

View File

@ -4531,7 +4531,7 @@ function populateAnnouncements() {
announcement.querySelector(".better-todo-announcement-checkmark").addEventListener("click", (e) => { announcement.querySelector(".better-todo-announcement-checkmark").addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
markAs(item, announcement.firstElementChild, !(item.planner_override?.marked_complete === true)); markAs(item, announcement.firstElementChild, !(item.planner_override?.marked_complete === true), { celebrate: false });
}); });
attachTodoHoverPreview(announcement, item); attachTodoHoverPreview(announcement, item);
}); });
@ -5097,9 +5097,19 @@ function createRocketCelebration(targetElement) {
const scale = celebrationAmountScale(); const scale = celebrationAmountScale();
const start = performance.now(); const start = performance.now();
const flames = []; // tracked so expiry is swept in the same pass const flames = []; // tracked so expiry is swept in the same pass
// Pool cap scales with the amount tier so "crazy" never saturates and // Continuous emission: flames/sec = rate, so the trail never gaps.
// starves the trail (which made the thruster visibly cut out early). // Spawning per-frame burst-saturated the pool cap at higher amounts,
const poolCap = Math.round(60 * Math.max(1, scale)); // then paused until old flames expired — that read as broken
// segments with the thruster visibly switching off.
const flameRate = 26 * scale; // flames per second
// Base thrust: less/normal get a stronger base size than before
// (they looked weak); more/crazy scale up on top.
const sizeScale = scale >= 4 ? 3 : scale >= 2 ? 2.4 : scale >= 1 ? 1.8 : 1;
// Cap = steady-state concurrent flames (rate x max life) + headroom,
// so sustained emission never gets throttled into gaps.
const poolCap = Math.ceil(flameRate * 1.1) + 8;
let emitAcc = 0;
let lastEmit = start;
let rafId = null; let rafId = null;
const tick = (now) => { const tick = (now) => {
let r; let r;
@ -5113,22 +5123,22 @@ function createRocketCelebration(targetElement) {
} }
const gone = !r || r.width === 0 || r.bottom < -110 || r.top > window.innerHeight + 110; const gone = !r || r.width === 0 || r.bottom < -110 || r.top > window.innerHeight + 110;
if (!gone && now - start < flightMs + 150) { if (!gone && now - start < flightMs + 150) {
// Spawn every frame while the pool has room; flames live const climb = Math.max(0.45, Math.min(1, (now - start) / 500)); // throttle ramps up after liftoff
// ~0.5-1s so the trail stays continuous until the card is // Accumulate emission time and spawn the owed flames — a
// fully offscreen. // steady stream whose density is set by the amount tier,
if (flames.length < poolCap) { // never bursts that exhaust (and starves) the pool.
const climb = Math.max(0.35, Math.min(1, (now - start) / 500)); // throttle ramps up after liftoff const dt = Math.min(0.1, (now - lastEmit) / 1000);
// Capped: extra tier budget goes into flame size/length, lastEmit = now;
// not into an ever-growing per-frame spawn rate. emitAcc = Math.min(emitAcc + flameRate * dt, 3); // don't bank a backlog
const jets = Math.min(8, Math.max(2, Math.round((4 + Math.random() * 2) * Math.sqrt(scale)))); while (emitAcc >= 1 && flames.length < poolCap) {
for (let i = 0; i < jets; i++) { emitAcc -= 1;
// Amount tier drives thruster particle size and flame length. // Amount tier drives thruster particle size and flame length.
const size = (3 + Math.random() * 6) * scale; const size = (5 + Math.random() * 7) * sizeScale;
const life = (380 + Math.random() * 380) * (scale >= 4 ? 1.3 : 1); const life = (520 + Math.random() * 300) * (scale >= 4 ? 1.3 : 1);
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'canvasrefined-rocket-flame'; el.className = 'canvasrefined-rocket-flame';
el.style.width = (size * (0.8 + climb * 0.5)).toFixed(1) + 'px'; el.style.width = (size * (0.8 + climb * 0.4)).toFixed(1) + 'px';
el.style.height = (size * (1.4 + Math.random() * 1.2) * (0.8 + climb * 0.5)).toFixed(1) + 'px'; el.style.height = (size * (1.4 + Math.random() * 1.2) * (0.8 + climb * 0.4)).toFixed(1) + 'px';
el.style.left = Math.round(r.left + r.width * (0.38 + Math.random() * 0.24)) + 'px'; el.style.left = Math.round(r.left + r.width * (0.38 + Math.random() * 0.24)) + 'px';
el.style.top = Math.round(r.bottom - 4) + 'px'; el.style.top = Math.round(r.bottom - 4) + 'px';
el.style.background = colors[(Math.random() * colors.length) | 0]; el.style.background = colors[(Math.random() * colors.length) | 0];
@ -5144,7 +5154,6 @@ function createRocketCelebration(targetElement) {
container.appendChild(el); container.appendChild(el);
flames.push({ el, expire: now + life + 120 }); flames.push({ el, expire: now + life + 120 });
} }
}
rafId = requestAnimationFrame(tick); rafId = requestAnimationFrame(tick);
} else { } else {
// Let the last flames finish, then drop the whole container. // Let the last flames finish, then drop the whole container.
@ -5268,7 +5277,11 @@ function setPinnedIncomplete(item, pinned) {
chrome.storage.sync.set({ todo_force_incomplete: pins }); chrome.storage.sync.set({ todo_force_incomplete: pins });
} }
function markAs(item, element, makeComplete) { // opts.celebrate === false suppresses the completion celebration — used for
// announcement "mark as seen", which shares the marked-complete plumbing but
// isn't a task the user finished, and keeps celebrations strictly on task
// completion (never on uncompleting, which is already gated by completeState).
function markAs(item, element, makeComplete, opts = {}) {
const csrfToken = CSRFtoken(); const csrfToken = CSRFtoken();
const completeState = makeComplete; const completeState = makeComplete;
@ -5313,7 +5326,7 @@ function markAs(item, element, makeComplete) {
// instantly, i.e. on cards without a transition (announcements). // instantly, i.e. on cards without a transition (announcements).
let exitStyle = "slide"; let exitStyle = "slide";
let exitDelay = 0; let exitDelay = 0;
if (completeState) { if (completeState && opts.celebrate !== false) {
try { try {
const celebration = runCelebration(element); const celebration = runCelebration(element);
exitStyle = celebration.style; exitStyle = celebration.style;