Playground
Deal me in
Pick an effect, tune the deck, and hit Deal. Every change here is one setOptions() call; the button is one play().
Gallery
Five ways to win
Each tile is a live SolitaireVictory instance on loop. Same 52-card deck, same physics knobs — only the effect changes.
Usage
Drop it in
One CSS file, one JS file, one container — or no container at all for a fullscreen celebration.
<link rel="stylesheet" href="path/to/solitaire-victory.css">
<script src="path/to/solitaire-victory.js"></script>
<div id="stage" style="height: 60vh"></div>
<script>
const win = new SolitaireVictory('#stage');
win.play('bounce').then(() => {
console.log('all 52 cards are down');
});
// Swap effects any time — play() accepts overrides too.
win.play('rain', { loop: true });
</script>
The one-liner
// Fullscreen overlay over your whole app. Plays once, fades out,
// removes itself. Clicks pass straight through the entire time.
SolitaireVictory.celebrate();
SolitaireVictory.celebrate({ effect: 'scatter', cardWidth: 80 });
Options
new SolitaireVictory('#stage', {
effect: 'bounce', // 'bounce' | 'scatter' | 'rain' | 'fountain' | 'cyclone'
cards: 52, // deck size the effect animates
cardWidth: 72, // px; height keeps the 2.5 × 3.5 poker ratio
speed: 1, // global time multiplier
gravity: 2200, // px/s²
restitution: 0.82, // bounce: energy kept per floor hit
stagger: 0.4, // bounce: seconds between card launches
piles: 4, // bounce: foundation piles across the top
loop: false, // replay forever (rain recycles seamlessly)
trails: null, // null = effect default (bounce smears, others don't)
background: null, // canvas backdrop; null = transparent (your CSS shows through)
backColor: '#2b4d9b', // card-back color
faceDownRatio: null, // fraction of face-down cards (rain 0.35, cyclone 0.25)
onComplete: null, // fn({ effect, willLoop }) — fires every completion
autoplay: false, // play() immediately on construct
});
Methods
win.play(effect?, overrides?); // returns a Promise: 'complete' | 'stopped'
win.pause();
win.resume();
win.stop(); // halt + wipe the canvas (trails included)
win.clear(); // wipe trails without ending the run
win.setOptions({ ... }); // merge + apply; speed/gravity change live
win.resize(); // manual re-measure (auto via ResizeObserver)
win.destroy(); // tear down canvas + listeners
win.isPlaying; // boolean
SolitaireVictory.EFFECTS; // ['bounce', 'scatter', 'rain', 'fountain', 'cyclone']
Trails, the signature move
// The bounce never repaints — that's what smears the trails, exactly
// like the 1990 original. Force it on (or off) for any effect:
win.play('rain', { trails: true }); // rain that smears — surprisingly great
win.play('bounce', { trails: false }); // a tidy, repainting bounce