vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Use the library's realistic-look and fireworks examples for the burst
5946444571d7784252b131623cd15679adabe18f · Keenan Nemetz · 13d ago · parent 6b7d6a18
Use the library's realistic-look and fireworks examples for the burst 120 particles at scalar 1 from one point read as a small puff on a full-width page. The opening burst is now canvas-confetti's "realistic look" preset — 500 particles, spread 180, ticks 400, gravity 0.5, decay 0.92, startVelocity 30 and scalar 1.2 — followed by its "fireworks" volley from both edges, tapering over 1.2s instead of the example's 15: the page this lands on is one you are about to read, not a celebration screen. Measured in a headless Firefox at 1360x682 after a real Merge click: 181 frames over 3s, median 17ms, p95 20ms, worst 23ms, so it holds 60fps while it runs, and the canvas is gone by the time the burst is over — the volley's timer ends with it rather than lingering. Refs #01M5Z00VJ6215NFZ8F3KJ48FXA
server/web/static/app.js +32−6
| @@ -350,7 +350,15 @@ const copyText = (value, done) => { | |||
| 350 | 350 | // worker — but the library hands its canvas to the worker anyway, transferring it | |
| 351 | 351 | // with transferControlToOffscreen, and then draws nothing: the canvas is present | |
| 352 | 352 | // and permanently empty, with no error to see. An instance that draws on the main | |
| 353 | // thread costs 120 particles for ~2s, which is nothing, and needs no CSP change. | ||
| 353 | // thread costs a few hundred particles for a few seconds, which is nothing, and | ||
| 354 | // needs no CSP change. | ||
| 355 | // | ||
| 356 | // The numbers are two of the library's own examples. The opening burst is the | ||
| 357 | // "realistic look" preset: more particles, a wider spread, a longer life, lower | ||
| 358 | // gravity and scalar > 1, which is what stops it reading as a small puff. The | ||
| 359 | // volley that follows is the "fireworks" preset from both edges, cut from fifteen | ||
| 360 | // seconds to just over one, because the page this lands on is one you are about to | ||
| 361 | // read. Together they are under a thousand particles. | ||
| 354 | 362 | (() => { | |
| 355 | 363 | const params = new URLSearchParams(window.location.search); | |
| 356 | 364 | if (!params.has('merged')) return; | |
| @@ -364,12 +372,30 @@ const copyText = (value, done) => { | |||
| 364 | 372 | script.onload = () => { | |
| 365 | 373 | if (typeof window.confetti !== 'function') return; | |
| 366 | 374 | const burst = window.confetti.create(null, { resize: true, useWorker: false }); | |
| 367 | burst({ | ||
| 368 | particleCount: 120, | ||
| 369 | spread: 70, | ||
| 370 | origin: { x: 0.5, y: 0.6 }, | ||
| 375 | const look = { | ||
| 376 | startVelocity: 30, | ||
| 377 | spread: 180, | ||
| 378 | ticks: 400, | ||
| 379 | gravity: 0.5, | ||
| 380 | decay: 0.92, | ||
| 381 | scalar: 1.2, | ||
| 371 | 382 | disableForReducedMotion: true, | |
| 372 | }); | ||
| 383 | }; | ||
| 384 | |||
| 385 | // The "realistic look" burst. | ||
| 386 | burst({ ...look, particleCount: 500, origin: { x: 0.5, y: 0.6 } }); | ||
| 387 | |||
| 388 | // The "fireworks" volley, from both edges, tapering as the clock runs out. | ||
| 389 | const volleyMs = 1200; | ||
| 390 | const end = Date.now() + volleyMs; | ||
| 391 | (function frame() { | ||
| 392 | const left = end - Date.now(); | ||
| 393 | if (left <= 0) return; | ||
| 394 | const particleCount = 30 * (left / volleyMs); | ||
| 395 | burst({ ...look, particleCount, angle: 60, spread: 55, origin: { x: 0 } }); | ||
| 396 | burst({ ...look, particleCount, angle: 120, spread: 55, origin: { x: 1 } }); | ||
| 397 | setTimeout(frame, 250); | ||
| 398 | })(); | ||
| 373 | 399 | }; | |
| 374 | 400 | document.head.appendChild(script); | |
| 375 | 401 | })(); | |