| author | Alan Dipert
<alan@dipert.org> 2025-11-17 00:42:59 UTC |
| committer | Alan Dipert
<alan@dipert.org> 2025-11-17 00:42:59 UTC |
| parent | 1845142d45318174d239698d495f1012d75ddac4 |
| fairy-finder/game.js | +65 | -3 |
| fairy-finder/index.html | +8 | -0 |
| fairy-finder/styles.css | +62 | -0 |
diff --git a/fairy-finder/game.js b/fairy-finder/game.js index deacce0..12e957e 100644 --- a/fairy-finder/game.js +++ b/fairy-finder/game.js @@ -8,6 +8,9 @@ const overlay = document.getElementById('overlay'); const overlayButton = document.getElementById('overlay-btn'); const overlayMessage = overlay.querySelector('p'); + const collectionList = document.getElementById('collection-list'); + const collectionCount = document.getElementById('collection-count'); + const collectionEmpty = document.getElementById('collection-empty'); const difficulties = { tiny: { size: 6, fairies: 5 }, @@ -25,7 +28,9 @@ isActive: false, timer: 0, timerInterval: null, + overlayTimeout: null, }; + const fairyCollection = []; const audio = createAudio(); function stopTimer() { @@ -165,6 +170,7 @@ } if (game.revealedSafe === game.boardSize * game.boardSize - game.fairyTotal) { revealAllFairies(true); + collectFairies(); finishGame(true); } } @@ -253,10 +259,24 @@ game.isActive = false; stopTimer(); overlayMessage.textContent = hasWon - ? 'You found every fairy friend! Want to search again?' + ? 'You found every fairy friend! They fluttered into your satchel.' : 'A shy fairy fluttered out! Try again for a perfect sweep?'; - overlayButton.textContent = hasWon ? 'Play again' : 'Try again'; - overlay.classList.remove('hidden'); + overlayButton.textContent = hasWon ? 'Search again' : 'Try again'; + if (game.overlayTimeout) { + clearTimeout(game.overlayTimeout); + game.overlayTimeout = null; + } + const delay = hasWon ? 1400 : 0; + const showOverlay = () => { + overlay.classList.remove('hidden'); + game.overlayTimeout = null; + }; + if (delay === 0) { + showOverlay(); + } else { + overlay.classList.add('hidden'); + game.overlayTimeout = setTimeout(showOverlay, delay); + } playSound(hasWon ? 'win' : 'lose'); } @@ -285,6 +305,10 @@ function startNewRound() { audio.resume(); + if (game.overlayTimeout) { + clearTimeout(game.overlayTimeout); + game.overlayTimeout = null; + } prepareGame(); overlay.classList.add('hidden'); } @@ -298,6 +322,44 @@ if (audio) audio.play(type); } + function collectFairies() { + if (!collectionList) return; + const newFairies = []; + for (let r = 0; r < game.boardSize; r += 1) { + for (let c = 0; c < game.boardSize; c += 1) { + if (game.board[r][c].hasFairy) { + newFairies.push({ row: r, col: c }); + } + } + } + if (!newFairies.length) return; + newFairies.forEach((fairy, index) => { + const fairyEl = document.createElement('span'); + fairyEl.className = 'collection-fairy collection-fairy--new'; + fairyEl.setAttribute('role', 'listitem'); + fairyEl.textContent = getFairyEmoji(); + fairyCollection.push(`${fairy.row}-${fairy.col}-${Date.now()}-${index}`); + collectionList.appendChild(fairyEl); + setTimeout(() => { + fairyEl.classList.remove('collection-fairy--new'); + }, 700); + }); + updateCollectionDisplay(); + } + + function updateCollectionDisplay() { + if (!collectionCount) return; + collectionCount.textContent = fairyCollection.length; + if (collectionEmpty) { + collectionEmpty.hidden = fairyCollection.length > 0; + } + } + + function getFairyEmoji() { + const emojis = ['🧚', '🧚♀️', '🧚♂️']; + return emojis[Math.floor(Math.random() * emojis.length)]; + } + function createAudio() { const AudioCtx = window.AudioContext || window.webkitAudioContext; if (!AudioCtx) return null; diff --git a/fairy-finder/index.html b/fairy-finder/index.html index f7c9ec4..3f74f4b 100644 --- a/fairy-finder/index.html +++ b/fairy-finder/index.html @@ -39,6 +39,14 @@ <aside class="instructions"> <h2>How to play</h2> <p>Fairies are hiding in the garden! Tap a square to peek. Numbers show how many fairies are next door. Mark fairy spots with a flag using right-click or a long press.</p> + <div class="collection" aria-live="polite"> + <div class="collection-header"> + <h3>Fairy satchel</h3> + <span class="collection-count"><span id="collection-count">0</span> friends</span> + </div> + <p id="collection-empty" class="collection-empty">Win a round to invite the fairies you find.</p> + <div class="collection-list" id="collection-list" role="list"></div> + </div> </aside> </section> </main> diff --git a/fairy-finder/styles.css b/fairy-finder/styles.css index f8cba0e..9b8b9f4 100644 --- a/fairy-finder/styles.css +++ b/fairy-finder/styles.css @@ -204,6 +204,68 @@ select { margin: 0.2rem 0 0; } +.collection { + margin-top: 1.5rem; + padding: 1rem 1.2rem; + border-radius: 1.2rem; + background: rgba(248, 218, 255, 0.45); + box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.7); +} + +.collection-header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; +} + +.collection-header h3 { + margin: 0; + font-size: 1.1rem; + color: #f26dbd; +} + +.collection-count { + font-weight: 700; + color: var(--text-dark); +} + +.collection-empty { + margin: 0.5rem 0 0; + font-style: italic; + opacity: 0.85; +} + +.collection-list { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + margin-top: 0.7rem; + min-height: 1.8rem; +} + +.collection-fairy { + font-size: 1.4rem; + filter: drop-shadow(0 4px 8px rgba(242, 109, 189, 0.4)); + animation: fairy-bob 4s ease-in-out infinite; +} + +.collection-fairy--new { + animation: fairy-collect 0.6s ease forwards; +} + +@keyframes fairy-collect { + 0% { transform: scale(0.3) translateY(12px); opacity: 0; } + 60% { transform: scale(1.2) translateY(-4px); opacity: 1; } + 100% { transform: scale(1) translateY(0); opacity: 1; } +} + +@keyframes fairy-bob { + 0% { transform: translateY(0); } + 50% { transform: translateY(-3px); } + 100% { transform: translateY(0); } +} + .overlay { position: fixed; inset: 0;