# Confetti

> Bursts of theme-colored confetti for a moment worth celebrating.

## Anatomy

Confetti marks a moment worth celebrating, such as a finished order or a completed goal.

Give a button the `confetti` class, and each click bursts confetti from its center over the whole page. The pieces take the theme's primary, secondary and tertiary colors and their containers. The component draws them on a canvas that lets clicks through and removes the canvas when the last piece fades. `AutoInit()` starts every `.confetti`.

Celebrate

```
<button type="button" class="filled confetti">Celebrate</button>
```

## On a canvas

A `<canvas class="confetti" aria-hidden="true">` covers its nearest positioned ancestor and bursts inside it once when it starts. To burst again, call `fire()` on its instance. To wait for the first call instead, set `manualStart`. A small canvas needs a lower `startVelocity` to keep the pieces in view.

Burst again

```
<div style="position: relative; height: 320px">
  <canvas class="confetti no-autoinit" aria-hidden="true"></canvas>
</div>

<script>
const canvas = document.querySelector('canvas.confetti');
const confetti = Expressive.Confetti.init(canvas, { startVelocity: 30, origin: { y: 0.6 } });
button.addEventListener('click', () => confetti.fire());
</script>
```

## From script

`Expressive.Confetti.fire(options)` bursts over the page with no markup, for example when a request succeeds. It returns a promise that resolves when the last piece is gone.

```
await saveOrder();
Expressive.Confetti.fire({ particleCount: 100, spread: 70, origin: { y: 0.6 } });
```

## Examples

Combine several calls for bigger effects. `origin` is a fraction of the viewport, so `{ x: 0 }` is the left edge.

Random direction Fireworks Side cannons Stars

### Random direction

```
Expressive.Confetti.fire({
  angle: 55 + Math.random() * 70,
  spread: 50 + Math.random() * 20,
  particleCount: 50 + Math.floor(Math.random() * 50),
  origin: { y: 0.6 }
});
```

### Fireworks

```
const end = Date.now() + 5000;
const timer = setInterval(() => {
  const left = end - Date.now();
  if (left <= 0) return clearInterval(timer);
  const burst = { spread: 360, startVelocity: 30, ticks: 60, particleCount: 50 * left / 5000 };
  Expressive.Confetti.fire({ ...burst, origin: { x: 0.1 + Math.random() * 0.2, y: Math.random() - 0.2 } });
  Expressive.Confetti.fire({ ...burst, origin: { x: 0.7 + Math.random() * 0.2, y: Math.random() - 0.2 } });
}, 250);
```

### Side cannons

```
const end = Date.now() + 3000;
const colors = ['#a786ff', '#fd8bbc', '#eca184', '#f8deb1'];
(function frame() {
  if (Date.now() > end) return;
  Expressive.Confetti.fire({ particleCount: 2, angle: 60, spread: 55, startVelocity: 60, origin: { x: 0, y: 0.5 }, colors });
  Expressive.Confetti.fire({ particleCount: 2, angle: 120, spread: 55, startVelocity: 60, origin: { x: 1, y: 0.5 }, colors });
  requestAnimationFrame(frame);
})();
```

### Stars

```
const stars = {
  spread: 360, ticks: 50, gravity: 0, decay: 0.94, startVelocity: 30,
  shapes: ['star'], colors: ['#ffe400', '#ffbd00', '#e89400', '#ffca6c', '#fdffb8']
};
for (const delay of [0, 100, 200]) {
  setTimeout(() => {
    Expressive.Confetti.fire({ ...stars, particleCount: 40 });
    Expressive.Confetti.fire({ ...stars, particleCount: 10, shapes: ['circle'], scalar: 0.75 });
  }, delay);
}
```

## Accessibility

-   Confetti is decoration. Say what happened in text as well, for example in a snackbar, and keep bursts for rare moments.
-   Mark an authored canvas `aria-hidden="true"`. The page canvas hides itself.
-   The component draws nothing when the reader prefers reduced motion, and `fire()` resolves at once.
-   The page canvas is a manual popover with pointer events off, so it draws above an open modal dialog without blocking clicks or moving focus.

## Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `particleCount` | Number | `50` | Pieces per burst. |
| `angle` | Number | `90` | Launch direction in degrees; 90 is straight up and 0 is right. |
| `spread` | Number | `45` | Degrees the pieces fan out from the angle. |
| `startVelocity` | Number | `45` | Launch speed in pixels per frame. |
| `decay` | Number | `0.9` | Share of its speed a piece keeps each frame. |
| `gravity` | Number | `1` | Pull toward the bottom; 0 floats. |
| `drift` | Number | `0` | Sideways push per frame; negative drifts left. |
| `ticks` | Number | `200` | Frames a piece lives while it fades, counted at 60 per second. |
| `origin` | Object | `{ x: 0.5, y: 0.5 }` | Launch point as fractions of the canvas from 0 to 1; a missing side is 0.5. A button launches from its own center instead. |
| `colors` | Array | `[]` | CSS colors. Empty uses the theme colors. |
| `shapes` | Array | `['square', 'circle']` | Any of `'square'`, `'circle'` and `'star'`. |
| `scalar` | Number | `1` | Piece size multiplier. |
| `manualStart` | Boolean | `false` | On a canvas, wait for `fire()` instead of bursting at init. |

## Methods

```
Expressive.Confetti.fire(options);   // bursts over the page; resolves when it ends

const confetti = Expressive.Confetti.getInstance(element);
confetti.fire({ spread: 90 });       // this canvas, or from this button's center
confetti.destroy();                  // removes the click handler, or clears the canvas
```
