diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b055c90..c332c38 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -25,6 +25,7 @@ "@livekit/track-processors": "^0.7.2", "@supabase/supabase-js": "^2.46.0", "better-sqlite3": "^11.3.0", + "canvas-confetti": "^1.9.4", "electron-updater": "^6.3.0", "i18next": "^23.16.4", "libsodium-wrappers-sumo": "0.7.15", @@ -38,6 +39,7 @@ }, "devDependencies": { "@types/better-sqlite3": "^7.6.0", + "@types/canvas-confetti": "^1.9.0", "@types/libsodium-wrappers": "^0.7.14", "@types/libsodium-wrappers-sumo": "^0.8.2", "@types/react": "^18.3.12", diff --git a/apps/desktop/src/components/GameModal.tsx b/apps/desktop/src/components/GameModal.tsx index 55ff32a..bab0a90 100644 --- a/apps/desktop/src/components/GameModal.tsx +++ b/apps/desktop/src/components/GameModal.tsx @@ -1,3 +1,4 @@ +import confetti from 'canvas-confetti'; import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -44,6 +45,22 @@ export function GameModal({ gameId, onClose }: Props) { : game.players[1] === game.winnerUserId ? 1 : null; + + useEffect(() => { + if (finished && winnerIdx !== null && winnerIdx === myPlayerIdx) { + // Two bursts from the lower corners for a celebratory feel. + void confetti({ + particleCount: 80, + spread: 60, + origin: { x: 0.2, y: 0.9 }, + }); + void confetti({ + particleCount: 80, + spread: 60, + origin: { x: 0.8, y: 0.9 }, + }); + } + }, [finished, winnerIdx, myPlayerIdx]); const title = game?.gameType === 'c4' ? t('app:game.c4', { defaultValue: 'Vier-Gewinnt' }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 076f1e9..830e310 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: better-sqlite3: specifier: ^11.3.0 version: 11.10.0 + canvas-confetti: + specifier: ^1.9.4 + version: 1.9.4 electron-updater: specifier: ^6.3.0 version: 6.8.3 @@ -102,6 +105,9 @@ importers: '@types/better-sqlite3': specifier: ^7.6.0 version: 7.6.13 + '@types/canvas-confetti': + specifier: ^1.9.0 + version: 1.9.0 '@types/libsodium-wrappers': specifier: ^0.7.14 version: 0.7.14 @@ -1967,6 +1973,9 @@ packages: '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/canvas-confetti@1.9.0': + resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -2670,6 +2679,9 @@ packages: caniuse-lite@1.0.30001788: resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + canvas-confetti@1.9.4: + resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -8703,6 +8715,8 @@ snapshots: '@types/node': 22.19.17 '@types/responselike': 1.0.3 + '@types/canvas-confetti@1.9.0': {} + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -9634,6 +9648,8 @@ snapshots: caniuse-lite@1.0.30001788: {} + canvas-confetti@1.9.4: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1