Plugin

solitaire-victory.js

The Solitaire victory animation as a drop-in canvas plugin. The classic trail-smearing bounce plus scatter, rain, fountain, and cyclone — 52 cards, zero dependencies.

npm install @goboldlyforward/solitaire-victory

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