From 4f0b035e46ba63ee1e9d6252136e59000cdcfc68 Mon Sep 17 00:00:00 2001
From: JwelSrivastava <175561856+JwelSrivastava@users.noreply.github.com>
Date: Fri, 24 Oct 2025 15:51:43 +0530
Subject: [PATCH 1/3] Add Shuffle Board Power-Up Feature
- One-time use power-up that reshuffles unmatched cards
- Smooth bounce animation with audio feedback
- 2-move penalty affects star rating
- Responsive button layout with full text visibility
- Works on all difficulty levels
- Prevents conflicts during card interactions
---
index.html | 1 +
src/css/styles.css | 115 ++++++++++++++++++++++++++++++++++++++++++++-
src/js/app.js | 76 ++++++++++++++++++++++++++++++
3 files changed, 191 insertions(+), 1 deletion(-)
diff --git a/index.html b/index.html
index c8d26cdc..fa8b0b52 100644
--- a/index.html
+++ b/index.html
@@ -75,6 +75,7 @@
Vault‑Tec Memory
+
Tip: Try to remember positions — the Pip‑Boy rewards patience.
diff --git a/src/css/styles.css b/src/css/styles.css
index 38b8bfda..5a128e2c 100644
--- a/src/css/styles.css
+++ b/src/css/styles.css
@@ -226,6 +226,33 @@ h1.title {
display: flex;
gap: 0.6rem;
margin-top: 1rem;
+ flex-wrap: wrap;
+}
+
+.controls .btn {
+ flex: 1;
+ min-width: 120px;
+ white-space: nowrap;
+ overflow: visible;
+ text-overflow: clip;
+ font-size: 0.85rem;
+}
+
+/* Shuffle button styling */
+#shuffleBoard {
+ min-width: 120px;
+ text-align: center;
+}
+
+#shuffleBoard:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+#shuffleBoard:not(:disabled):hover {
+ background: linear-gradient(90deg, rgba(255, 210, 63, 0.1), rgba(255, 210, 63, 0.05));
+ border-color: rgba(255, 210, 63, 0.3);
+ color: var(--vault-yellow);
}
.btn.ghost#playDemo {
@@ -573,6 +600,30 @@ h1.title {
}
}
+/* Shuffle animation for cards */
+.card.shuffling {
+ animation: shuffleMove 0.8s ease-in-out;
+ z-index: 10;
+}
+
+@keyframes shuffleMove {
+ 0% {
+ transform: translateY(0) scale(1) rotateZ(0deg);
+ }
+ 25% {
+ transform: translateY(-20px) scale(0.9) rotateZ(-5deg);
+ }
+ 50% {
+ transform: translateY(-30px) scale(0.8) rotateZ(5deg);
+ }
+ 75% {
+ transform: translateY(-20px) scale(0.9) rotateZ(-2deg);
+ }
+ 100% {
+ transform: translateY(0) scale(1) rotateZ(0deg);
+ }
+}
+
.modal-overlay {
position: fixed;
inset: 0;
@@ -728,7 +779,54 @@ h1.title {
padding: 1rem 0;
}
.star { width: 34px; height: 28px; }
+
+ /* Better button responsiveness for medium screens */
+ .controls {
+ gap: 0.4rem;
+ flex-wrap: wrap;
+ }
+ .controls .btn {
+ font-size: 0.8rem;
+ padding: 0.5rem 0.6rem;
+ min-width: 100px;
+ flex: 1 1 auto;
+ }
+}
+
+@media (max-width: 600px) {
+ .controls {
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+ .controls .btn {
+ width: 100%;
+ flex: none;
+ font-size: 0.9rem;
+ padding: 0.6rem;
+ text-align: center;
+ min-width: auto;
+ white-space: normal;
+ overflow: visible;
+ }
+}
+/* Specific breakpoint for button text visibility */
+@media (max-width: 380px) {
+ .controls {
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+ .controls .btn {
+ width: 100%;
+ flex: none;
+ min-width: auto;
+ font-size: 0.9rem;
+ padding: 0.6rem;
+ white-space: normal;
+ overflow: visible;
+ text-overflow: clip;
+ }
}
+
@media (max-width: 480px) {
.deck {
grid-template-columns: repeat(3, 1fr);
@@ -742,9 +840,15 @@ h1.title {
}
.controls {
flex-direction: column;
+ gap: 0.5rem;
}
- .btn {
+ .controls .btn {
width: 100%;
+ flex: none;
+ font-size: 0.9rem;
+ padding: 0.6rem;
+ text-align: center;
+ white-space: normal;
}
.footer {
padding: 1rem;
@@ -756,6 +860,15 @@ h1.title {
gap: 0.3rem;
}
}
+
+/* Ensure buttons don't get too small on narrow screens */
+@media (max-width: 320px) {
+ .controls .btn {
+ font-size: 0.8rem;
+ padding: 0.5rem;
+ min-height: 40px;
+ }
+}
/* Responsive layout adjustments */
@media (max-width: 1400px) {
.app {
diff --git a/src/js/app.js b/src/js/app.js
index 51c7c38f..e7d18475 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -29,6 +29,7 @@
const restartBtn = document.getElementById('restart');
const restartBottom = document.getElementById('restart-bottom');
const hintBtn = document.getElementById('hint');
+ const shuffleBoardBtn = document.getElementById('shuffleBoard');
const modal = document.getElementById('modalOverlay');
const playAgainBtn = document.getElementById('playAgain');
const closeModalBtn = document.getElementById('closeModal');
@@ -73,6 +74,7 @@
let moveLimitEnabled = false;
let moveLimitMax = 0;
let remainingMoves = 0;
+ let shuffleUsed = false;
// toggle listener
moveLimitToggle.addEventListener('change', () => {
@@ -299,10 +301,14 @@
matched = 0;
moves = 0;
starCount = 3;
+ shuffleUsed = false;
updateUI();
started = false;
// enable hint based on difficulty
hintBtn.style.display = difficulties[selectedDifficulty].hideHint ? 'none' : '';
+ // enable shuffle button for new game
+ shuffleBoardBtn.disabled = false;
+ shuffleBoardBtn.textContent = 'Shuffle Board';
// ensure cards are interactive
Array.from(deckEl.querySelectorAll('.card')).forEach(c => { c.removeAttribute('aria-disabled'); c.classList.remove('matched', 'is-flip'); });
locked = false;
@@ -496,6 +502,76 @@
setTimeout(() => { c1.classList.remove('is-flip'); c2.classList.remove('is-flip'); playTone(440, 0.08); }, 900);
});
+ // shuffle board power-up
+ shuffleBoardBtn.addEventListener('click', () => {
+ if (shuffleUsed || locked) return;
+
+ // Get all unmatched cards
+ const unmatchedCards = Array.from(deckEl.querySelectorAll('.card:not(.matched)'));
+ if (unmatchedCards.length < 2) return;
+
+ // Prevent interactions during shuffle
+ locked = true;
+ shuffleUsed = true;
+ shuffleBoardBtn.disabled = true;
+ shuffleBoardBtn.textContent = 'Used';
+
+ // Flip all unmatched cards face down first
+ unmatchedCards.forEach(card => {
+ if (card.classList.contains('is-flip')) {
+ card.classList.remove('is-flip');
+ }
+ });
+
+ // Clear opened cards array
+ opened = [];
+
+ // Add shuffle animation to all unmatched cards
+ unmatchedCards.forEach((card, index) => {
+ setTimeout(() => {
+ card.classList.add('shuffling');
+ playTone(300 + (index * 20), 0.05); // Rising tone sequence
+ }, index * 50);
+ });
+
+ // Shuffle the card positions after animation starts
+ setTimeout(() => {
+ // Get current data-src values of unmatched cards
+ const cardData = unmatchedCards.map(card => card.getAttribute('data-src'));
+
+ // Shuffle the data array
+ const shuffledData = shuffle([...cardData]);
+
+ // Reassign shuffled data to cards
+ unmatchedCards.forEach((card, index) => {
+ card.setAttribute('data-src', shuffledData[index]);
+ const altText = shuffledData[index].split('.').slice(0, -1).join('.');
+ card.setAttribute('aria-label', `Card: ${altText}`);
+
+ // Update the image in the card-front
+ const img = card.querySelector('.card-front img');
+ if (img) {
+ img.src = `img/${shuffledData[index]}`;
+ img.alt = altText;
+ }
+ });
+
+ // Deduct points for using the power-up
+ moves += 2;
+ updateUI();
+
+ playTone(800, 0.15); // Success tone
+ }, 400);
+
+ // Remove shuffle animation and unlock after animation completes
+ setTimeout(() => {
+ unmatchedCards.forEach(card => {
+ card.classList.remove('shuffling');
+ });
+ locked = false;
+ }, 800);
+ });
+
// restart & modal handlers
restartBtn.addEventListener('click', resetGame);
restartBottom.addEventListener('click', resetGame);
From 22f79a7fce57594aae1b165858efca78caf03e5c Mon Sep 17 00:00:00 2001
From: JwelSrivastava <175561856+JwelSrivastava@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:02:45 +0530
Subject: [PATCH 2/3] Trigger GitHub conflict resolution refresh
From 9b931495a1cef88b024b7b3d766c5a46e4e1d757 Mon Sep 17 00:00:00 2001
From: JwelSrivastava <175561856+JwelSrivastava@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:04:42 +0530
Subject: [PATCH 3/3] Add PR update documentation - conflicts resolved
---
PR_UPDATE.md | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 PR_UPDATE.md
diff --git a/PR_UPDATE.md b/PR_UPDATE.md
new file mode 100644
index 00000000..4c071267
--- /dev/null
+++ b/PR_UPDATE.md
@@ -0,0 +1,32 @@
+# 🔀 Shuffle Board Power-Up - PR Update
+
+## ✅ Conflict Resolution Complete
+
+All Git conflicts have been resolved and the PR is ready for merge.
+
+## 🎮 Feature Summary
+
+### Shuffle Board Power-Up
+- **One-time use per game** - Button disables after use
+- **Smart shuffling** - Only reshuffles unmatched cards, preserves completed pairs
+- **Move penalty** - Adds 2 moves to score (affects star rating)
+- **Smooth animation** - Bounce effect with staggered card timing
+- **Audio feedback** - Rising tone sequence + success sound
+- **Responsive design** - Full button text visibility on all screen sizes
+
+### Technical Implementation
+- **HTML**: Added shuffle button to controls section
+- **JavaScript**: Complete shuffle logic with state management
+- **CSS**: Animation keyframes and responsive button layout
+- **Accessibility**: Proper ARIA labels and keyboard support
+
+### Files Modified
+- `index.html` - Added shuffle button
+- `src/js/app.js` - Shuffle functionality and game logic
+- `src/css/styles.css` - Animation and responsive styling
+
+## 🚀 Ready for Merge
+- ✅ No conflicts
+- ✅ All features tested
+- ✅ Responsive design verified
+- ✅ Clean commit history
\ No newline at end of file