arnaucube
arnaucube/jellyfin-toolsPublic Self-hosted repository
jellyfin-tools/jellyfin-year-review/web/src/lib/Confetti.svelte
jellyfin-year-review/web/src/lib/Confetti.svelteRaw
 1<script>
 2  const colors = ['#c5f76b', '#a78bfa', '#ff7869', '#ffd166', '#65d6ed', '#ff86c8'];
 3  // Spread a short shower across the viewport, with varied timing and tumbling.
 4  const pieces = Array.from({ length: 90 }, (_, i) => ({
 5    left: (i * 37) % 100,
 6    delay: ((i * 13) % 120) / 100,
 7    duration: 3 + ((i * 17) % 140) / 100,
 8    drift: ((i * 29) % 240) - 120,
 9    rotation: (i % 2 ? 1 : -1) * (540 + (i * 31) % 720),
10    color: colors[i % colors.length]
11  }));
12</script>
13
14<div class="confetti" aria-hidden="true">
15  {#each pieces as piece, i}
16    <span style="left: {piece.left}%; background: {piece.color}; --delay: {piece.delay}s; --duration: {piece.duration}s; --drift: {piece.drift}px; --rotation: {piece.rotation}deg; border-radius: {i % 3 === 0 ? '50%' : '1px'};"></span>
17  {/each}
18</div>
19
20<style>
21  .confetti { position: fixed; inset: 0; z-index: 100; overflow: hidden; pointer-events: none; }
22  span { position: absolute; top: -24px; width: 8px; height: 14px; animation: fall var(--duration) var(--delay) linear both; }
23  @keyframes fall {
24    from { transform: translate3d(0, 0, 0) rotate(0deg); }
25    to { transform: translate3d(var(--drift), calc(100dvh + 48px), 0) rotateZ(var(--rotation)) rotateY(720deg); }
26  }
27  @media (prefers-reduced-motion: reduce) {
28    .confetti { display: none; }
29  }
30</style>