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) => {
e.preventDefault();
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);
});
@ -5097,9 +5097,19 @@ function createRocketCelebration(targetElement) {
const scale = celebrationAmountScale();
const start = performance.now();
const flames = []; // tracked so expiry is swept in the same pass
// Pool cap scales with the amount tier so "crazy" never saturates and
// starves the trail (which made the thruster visibly cut out early).
const poolCap = Math.round(60 * Math.max(1, scale));
// Continuous emission: flames/sec = rate, so the trail never gaps.
// Spawning per-frame burst-saturated the pool cap at higher amounts,
// 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;
const tick = (now) => {
let r;
@ -5113,37 +5123,36 @@ function createRocketCelebration(targetElement) {
}
const gone = !r || r.width === 0 || r.bottom < -110 || r.top > window.innerHeight + 110;
if (!gone && now - start < flightMs + 150) {
// Spawn every frame while the pool has room; flames live
// ~0.5-1s so the trail stays continuous until the card is
// fully offscreen.
if (flames.length < poolCap) {
const climb = Math.max(0.35, Math.min(1, (now - start) / 500)); // throttle ramps up after liftoff
// Capped: extra tier budget goes into flame size/length,
// not into an ever-growing per-frame spawn rate.
const jets = Math.min(8, Math.max(2, Math.round((4 + Math.random() * 2) * Math.sqrt(scale))));
for (let i = 0; i < jets; i++) {
// Amount tier drives thruster particle size and flame length.
const size = (3 + Math.random() * 6) * scale;
const life = (380 + Math.random() * 380) * (scale >= 4 ? 1.3 : 1);
const el = document.createElement('div');
el.className = 'canvasrefined-rocket-flame';
el.style.width = (size * (0.8 + climb * 0.5)).toFixed(1) + 'px';
el.style.height = (size * (1.4 + Math.random() * 1.2) * (0.8 + climb * 0.5)).toFixed(1) + '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.background = colors[(Math.random() * colors.length) | 0];
// WAAPI: compositor-run motion; flame travel distance
// scales with the amount tier so the exhaust keeps up
// with bigger/faster rockets.
const fx = (Math.random() - 0.5) * 28 * scale;
const fy = 30 * scale;
el.animate([
{ transform: 'translate(0px, 0px) scale(1)', opacity: 0.95 },
{ transform: 'translate(' + fx.toFixed(1) + 'px, ' + fy + 'px) scale(0.15)', opacity: 0 }
], { duration: Math.round(life), easing: 'ease-out', fill: 'forwards' });
container.appendChild(el);
flames.push({ el, expire: now + life + 120 });
}
const climb = Math.max(0.45, Math.min(1, (now - start) / 500)); // throttle ramps up after liftoff
// Accumulate emission time and spawn the owed flames — a
// steady stream whose density is set by the amount tier,
// never bursts that exhaust (and starves) the pool.
const dt = Math.min(0.1, (now - lastEmit) / 1000);
lastEmit = now;
emitAcc = Math.min(emitAcc + flameRate * dt, 3); // don't bank a backlog
while (emitAcc >= 1 && flames.length < poolCap) {
emitAcc -= 1;
// Amount tier drives thruster particle size and flame length.
const size = (5 + Math.random() * 7) * sizeScale;
const life = (520 + Math.random() * 300) * (scale >= 4 ? 1.3 : 1);
const el = document.createElement('div');
el.className = 'canvasrefined-rocket-flame';
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.4)).toFixed(1) + '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.background = colors[(Math.random() * colors.length) | 0];
// WAAPI: compositor-run motion; flame travel distance
// scales with the amount tier so the exhaust keeps up
// with bigger/faster rockets.
const fx = (Math.random() - 0.5) * 28 * scale;
const fy = 30 * scale;
el.animate([
{ transform: 'translate(0px, 0px) scale(1)', opacity: 0.95 },
{ transform: 'translate(' + fx.toFixed(1) + 'px, ' + fy + 'px) scale(0.15)', opacity: 0 }
], { duration: Math.round(life), easing: 'ease-out', fill: 'forwards' });
container.appendChild(el);
flames.push({ el, expire: now + life + 120 });
}
rafId = requestAnimationFrame(tick);
} else {
@ -5268,7 +5277,11 @@ function setPinnedIncomplete(item, pinned) {
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 completeState = makeComplete;
@ -5313,7 +5326,7 @@ function markAs(item, element, makeComplete) {
// instantly, i.e. on cards without a transition (announcements).
let exitStyle = "slide";
let exitDelay = 0;
if (completeState) {
if (completeState && opts.celebrate !== false) {
try {
const celebration = runCelebration(element);
exitStyle = celebration.style;