fix(matching): replace mismatch banner with card shake animation

Remove the obtrusive "Not a match! Try again." banner overlay that
appeared over cards when incorrect pairs were selected. Replace with
a kinetic shake animation applied directly to the mismatched cards
before they flip back over.

Changes:
- Remove banner overlay from both arcade and games MemoryGrid components
- Add shouldShake state to identify mismatched cards
- Apply cardShake animation (horizontal wiggle + rotation) to cards
- Update animation keyframes from banner shake to card shake

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-11 15:06:06 -05:00
parent 1948ba2dde
commit 804096fd8a
2 changed files with 18 additions and 72 deletions

View File

@@ -252,6 +252,8 @@ export function MemoryGrid() {
{state.gameCards.map((card) => {
const isFlipped = state.flippedCards.some((c) => c.id === card.id) || card.matched
const isMatched = card.matched
const shouldShake =
state.showMismatchFeedback && state.flippedCards.some((c) => c.id === card.id)
// Smart card filtering for abacus-numeral mode
let isValidForSelection = true
@@ -304,6 +306,8 @@ export function MemoryGrid() {
transition: 'opacity 0.3s ease',
filter: isDimmed ? 'grayscale(0.7)' : 'none',
position: 'relative',
// Shake animation for mismatched cards
animation: shouldShake ? 'cardShake 0.5s ease-in-out' : 'none',
})}
onMouseEnter={() => {
// Only send hover if it's your turn and card is not matched
@@ -330,37 +334,6 @@ export function MemoryGrid() {
})}
</div>
{/* Mismatch Feedback */}
{state.showMismatchFeedback && (
<div
className={css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: 'linear-gradient(135deg, #ff6b6b, #ee5a24)',
color: 'white',
padding: '16px 24px',
borderRadius: '16px',
fontSize: '18px',
fontWeight: 'bold',
boxShadow: '0 8px 25px rgba(255, 107, 107, 0.4)',
zIndex: 1000,
animation: 'shake 0.5s ease-in-out',
})}
>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '8px',
})}
>
<span></span>
<span>Not a match! Try again.</span>
</div>
</div>
)}
{/* Processing Overlay */}
{state.isProcessingMove && (
@@ -421,12 +394,12 @@ export function MemoryGrid() {
)
}
// Add animations for mismatch feedback and hover avatars
// Add animations for mismatched cards and hover avatars
const gridAnimations = `
@keyframes shake {
0%, 100% { transform: translate(-50%, -50%) translateX(0); }
25% { transform: translate(-50%, -50%) translateX(-5px); }
75% { transform: translate(-50%, -50%) translateX(5px); }
@keyframes cardShake {
0%, 100% { transform: translateX(0) rotate(0deg); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-8px) rotate(-2deg); }
20%, 40%, 60%, 80% { transform: translateX(8px) rotate(2deg); }
}
@keyframes hoverFloat {

View File

@@ -122,6 +122,8 @@ export function MemoryGrid() {
{state.gameCards.map((card) => {
const isFlipped = state.flippedCards.some((c) => c.id === card.id) || card.matched
const isMatched = card.matched
const shouldShake =
state.showMismatchFeedback && state.flippedCards.some((c) => c.id === card.id)
// Smart card filtering for abacus-numeral mode
let isValidForSelection = true
@@ -172,6 +174,8 @@ export function MemoryGrid() {
opacity: isDimmed ? 0.3 : 1,
transition: 'opacity 0.3s ease',
filter: isDimmed ? 'grayscale(0.7)' : 'none',
// Shake animation for mismatched cards
animation: shouldShake ? 'cardShake 0.5s ease-in-out' : 'none',
})}
>
<GameCard
@@ -186,37 +190,6 @@ export function MemoryGrid() {
})}
</div>
{/* Mismatch Feedback */}
{state.showMismatchFeedback && (
<div
className={css({
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: 'linear-gradient(135deg, #ff6b6b, #ee5a24)',
color: 'white',
padding: '16px 24px',
borderRadius: '16px',
fontSize: '18px',
fontWeight: 'bold',
boxShadow: '0 8px 25px rgba(255, 107, 107, 0.4)',
zIndex: 1000,
animation: 'shake 0.5s ease-in-out',
})}
>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '8px',
})}
>
<span></span>
<span>Not a match! Try again.</span>
</div>
</div>
)}
{/* Processing Overlay */}
{state.isProcessingMove && (
@@ -237,12 +210,12 @@ export function MemoryGrid() {
)
}
// Add shake animation for mismatch feedback
// Add shake animation for mismatched cards
const shakeAnimation = `
@keyframes shake {
0%, 100% { transform: translate(-50%, -50%) translateX(0); }
25% { transform: translate(-50%, -50%) translateX(-5px); }
75% { transform: translate(-50%, -50%) translateX(5px); }
@keyframes cardShake {
0%, 100% { transform: translateX(0) rotate(0deg); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-8px) rotate(-2deg); }
20%, 40%, 60%, 80% { transform: translateX(8px) rotate(2deg); }
}
`