Fireworks na oknie wygranej i zamykanie modala

This commit is contained in:
2026-02-08 15:36:06 +01:00
parent 657dc9cc1f
commit b8cf4d3cf4
5 changed files with 74 additions and 50 deletions

18
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "vue-nonograms-solid", "name": "vue-nonograms-solid",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"canvas-confetti": "^1.9.2", "fireworks-js": "^2.10.8",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.19" "vue": "^3.4.19"
}, },
@@ -937,16 +937,6 @@
"integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/canvas-confetti": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz",
"integrity": "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==",
"license": "ISC",
"funding": {
"type": "donate",
"url": "https://www.paypal.me/kirilvatev"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1010,6 +1000,12 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fireworks-js": {
"version": "2.10.8",
"resolved": "https://registry.npmjs.org/fireworks-js/-/fireworks-js-2.10.8.tgz",
"integrity": "sha512-UZNxeJvRmQzLisN4iriWXqKojG9TDJqc0dPmkUw0/+AEQQ3w8z1Jx2YdFSiBGSVb/u4dPTQXU109GMVblzhfpg==",
"license": "MIT"
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",

View File

@@ -8,12 +8,12 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"fireworks-js": "^2.10.8",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.19", "vue": "^3.4.19"
"canvas-confetti": "^1.9.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.1.4" "vite": "^5.1.4"
} }
} }

View File

@@ -1,11 +1,55 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { Fireworks } from 'fireworks-js';
import { usePuzzleStore } from '@/stores/puzzle'; import { usePuzzleStore } from '@/stores/puzzle';
const store = usePuzzleStore(); const store = usePuzzleStore();
const fireworksRef = ref(null);
let fireworksInstance = null;
const handleClose = () => {
store.closeWinModal();
};
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
handleClose();
}
};
onMounted(() => {
if (fireworksRef.value) {
fireworksInstance = new Fireworks(fireworksRef.value, {
autoresize: true,
opacity: 0.6,
acceleration: 1.05,
friction: 0.98,
gravity: 1.4,
particles: 60,
traceLength: 3,
traceSpeed: 10,
explosion: 5,
intensity: 35,
flickering: 60,
hue: { min: 170, max: 210 },
delay: { min: 20, max: 40 },
rocketsPoint: { min: 50, max: 50 }
});
fireworksInstance.start();
}
window.addEventListener('keydown', handleKeyDown);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown);
fireworksInstance?.stop(true);
fireworksInstance = null;
});
</script> </script>
<template> <template>
<div class="modal-overlay"> <div class="modal-overlay" @click.self="handleClose">
<div ref="fireworksRef" class="fireworks-layer"></div>
<div class="modal glass-panel"> <div class="modal glass-panel">
<h2>GRATULACJE!</h2> <h2>GRATULACJE!</h2>
<p>Rozwiązałeś zagadkę!</p> <p>Rozwiązałeś zagadkę!</p>
@@ -40,6 +84,15 @@ const store = usePuzzleStore();
animation: fadeIn 0.5s ease; animation: fadeIn 0.5s ease;
} }
.fireworks-layer {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
}
.modal { .modal {
padding: 40px; padding: 40px;
text-align: center; text-align: center;
@@ -48,6 +101,8 @@ const store = usePuzzleStore();
border: 1px solid var(--primary-accent); border: 1px solid var(--primary-accent);
box-shadow: 0 0 50px rgba(0, 242, 255, 0.2); box-shadow: 0 0 50px rgba(0, 242, 255, 0.2);
animation: slideUp 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); animation: slideUp 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
z-index: 1001;
} }
h2 { h2 {

View File

@@ -1,6 +1,5 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { usePuzzleStore } from '@/stores/puzzle'; import { usePuzzleStore } from '@/stores/puzzle';
import confetti from 'canvas-confetti';
export function useNonogram() { export function useNonogram() {
const store = usePuzzleStore(); const store = usePuzzleStore();
@@ -49,7 +48,6 @@ export function useNonogram() {
const stopDrag = () => { const stopDrag = () => {
isDragging.value = false; isDragging.value = false;
dragMode.value = null; dragMode.value = null;
checkWinEffect();
}; };
const applyDrag = (r, c) => { const applyDrag = (r, c) => {
@@ -86,38 +84,6 @@ export function useNonogram() {
store.setCell(r, c, dragMode.value); store.setCell(r, c, dragMode.value);
}; };
const checkWinEffect = () => {
if (store.isGameWon) {
triggerConfetti();
}
};
const triggerConfetti = () => {
const duration = 3000;
const end = Date.now() + duration;
(function frame() {
confetti({
particleCount: 5,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: ['#00f2ff', '#ff0055', '#ffffff']
});
confetti({
particleCount: 5,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: ['#00f2ff', '#ff0055', '#ffffff']
});
if (Date.now() < end) {
requestAnimationFrame(frame);
}
}());
};
return { return {
startDrag, startDrag,
onMouseEnter, onMouseEnter,

View File

@@ -324,6 +324,12 @@ export const usePuzzleStore = defineStore('puzzle', () => {
} }
} }
function closeWinModal() {
if (!isGameWon.value) return;
isGameWon.value = false;
saveState();
}
return { return {
currentLevelId, currentLevelId,
solution, solution,
@@ -340,7 +346,8 @@ export const usePuzzleStore = defineStore('puzzle', () => {
checkWin, checkWin,
loadState, // expose loadState loadState, // expose loadState
moves, moves,
undo undo,
closeWinModal
}; };
}); });