Refine Bulgarian Solitaire animation
Update Bulgarian Solitaire animation to fade red highlight in, pause, move blocks to a new column, and then fade back to black.
This commit is contained in:
parent
748c12c3cf
commit
dc0872c3a8
2 changed files with 54 additions and 12 deletions
|
|
@ -24,6 +24,7 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
const [isComplete, setIsComplete] = useState(false);
|
const [isComplete, setIsComplete] = useState(false);
|
||||||
const [highlightedBoxes, setHighlightedBoxes] = useState<Set<number>>(new Set());
|
const [highlightedBoxes, setHighlightedBoxes] = useState<Set<number>>(new Set());
|
||||||
const [isAnimating, setIsAnimating] = useState(false);
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
const [animationPhase, setAnimationPhase] = useState<'idle' | 'fade-to-red' | 'red-pause' | 'moving' | 'fade-from-red'>('idle');
|
||||||
const [lastUsedColumn, setLastUsedColumn] = useState<number | null>(null);
|
const [lastUsedColumn, setLastUsedColumn] = useState<number | null>(null);
|
||||||
const [selectedBoxForMove, setSelectedBoxForMove] = useState<number | null>(null);
|
const [selectedBoxForMove, setSelectedBoxForMove] = useState<number | null>(null);
|
||||||
const [showInstructions, setShowInstructions] = useState(false);
|
const [showInstructions, setShowInstructions] = useState(false);
|
||||||
|
|
@ -232,10 +233,21 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
});
|
});
|
||||||
|
|
||||||
setHighlightedBoxes(topBoxes);
|
setHighlightedBoxes(topBoxes);
|
||||||
|
|
||||||
|
// Phase 1: Fade to red
|
||||||
|
setAnimationPhase('fade-to-red');
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
await sleep(800); // Show highlighting
|
await sleep(500); // Fade to red animation
|
||||||
await waitForUnpause(); // Check for pause
|
await waitForUnpause();
|
||||||
|
|
||||||
|
// Phase 2: Red pause
|
||||||
|
setAnimationPhase('red-pause');
|
||||||
|
await sleep(1000); // Pause for 1 second with red blocks
|
||||||
|
await waitForUnpause();
|
||||||
|
|
||||||
|
// Phase 3: Move blocks
|
||||||
|
setAnimationPhase('moving');
|
||||||
|
|
||||||
// Create new pile from non-empty columns
|
// Create new pile from non-empty columns
|
||||||
const newPile: number[] = [];
|
const newPile: number[] = [];
|
||||||
const updatedColumns = currentColumns.map(col => {
|
const updatedColumns = currentColumns.map(col => {
|
||||||
|
|
@ -257,12 +269,34 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentColumns = updatedColumns;
|
||||||
|
setColumns(currentColumns);
|
||||||
|
|
||||||
|
// Update box positions
|
||||||
|
setBoxes(prev => prev.map(box => {
|
||||||
|
for (let i = 0; i < currentColumns.length; i++) {
|
||||||
|
if (currentColumns[i].includes(box.id)) {
|
||||||
|
return { ...box, columnIndex: i };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}));
|
||||||
|
|
||||||
|
await sleep(500); // Show move
|
||||||
|
await waitForUnpause();
|
||||||
|
|
||||||
|
// Phase 4: Fade from red to normal
|
||||||
|
setAnimationPhase('fade-from-red');
|
||||||
|
await sleep(500); // Fade from red animation
|
||||||
|
await waitForUnpause();
|
||||||
|
|
||||||
// Clear highlighting and animation
|
// Clear highlighting and animation
|
||||||
setHighlightedBoxes(new Set());
|
setHighlightedBoxes(new Set());
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
|
setAnimationPhase('idle');
|
||||||
|
|
||||||
// Sort columns by height (tallest to left)
|
// Sort columns by height (tallest to left)
|
||||||
const columnData = updatedColumns.map((col, index) => ({ col, originalIndex: index }))
|
const columnData = currentColumns.map((col, index) => ({ col, originalIndex: index }))
|
||||||
.filter(item => item.col.length > 0)
|
.filter(item => item.col.length > 0)
|
||||||
.sort((a, b) => b.col.length - a.col.length);
|
.sort((a, b) => b.col.length - a.col.length);
|
||||||
|
|
||||||
|
|
@ -284,7 +318,7 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
return box;
|
return box;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await sleep(1200); // Wait to see the sorted result
|
await sleep(800); // Wait to see the sorted result
|
||||||
await waitForUnpause(); // Check for pause again
|
await waitForUnpause(); // Check for pause again
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,6 +350,7 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
setIsComplete(false);
|
setIsComplete(false);
|
||||||
setHighlightedBoxes(new Set());
|
setHighlightedBoxes(new Set());
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
|
setAnimationPhase('idle');
|
||||||
};
|
};
|
||||||
|
|
||||||
const randomStart = () => {
|
const randomStart = () => {
|
||||||
|
|
@ -440,10 +475,12 @@ const BulgarianSolitaire: React.FC<BulgarianSolitaireProps> = ({ showSocialShare
|
||||||
onDragStart={(e) => handleDragStart(e, boxId)}
|
onDragStart={(e) => handleDragStart(e, boxId)}
|
||||||
onDoubleClick={() => handleDoubleClick(boxId)}
|
onDoubleClick={() => handleDoubleClick(boxId)}
|
||||||
className={`w-8 h-8 rounded cursor-move transition-all duration-300 flex items-center justify-center text-xs font-medium mx-auto ${
|
className={`w-8 h-8 rounded cursor-move transition-all duration-300 flex items-center justify-center text-xs font-medium mx-auto ${
|
||||||
isAnimating && highlightedBoxes.has(boxId)
|
highlightedBoxes.has(boxId) && animationPhase === 'fade-to-red'
|
||||||
? 'animate-[fade-red_1.6s_ease-in-out_infinite] shadow-lg scale-110 text-white'
|
? 'animate-fade-to-red shadow-lg scale-110 text-white'
|
||||||
: highlightedBoxes.has(boxId)
|
: highlightedBoxes.has(boxId) && (animationPhase === 'red-pause' || animationPhase === 'moving')
|
||||||
? 'bg-red-500 text-white shadow-lg scale-110'
|
? 'bg-red-500 text-white shadow-lg scale-110'
|
||||||
|
: highlightedBoxes.has(boxId) && animationPhase === 'fade-from-red'
|
||||||
|
? 'animate-fade-from-red shadow-lg scale-110'
|
||||||
: 'bg-primary text-primary-foreground hover:bg-primary/80'
|
: 'bg-primary text-primary-foreground hover:bg-primary/80'
|
||||||
}`}
|
}`}
|
||||||
title="Drag to move or double-click to move to last-used column"
|
title="Drag to move or double-click to move to last-used column"
|
||||||
|
|
|
||||||
|
|
@ -101,15 +101,20 @@ export default {
|
||||||
height: '0'
|
height: '0'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'fade-red': {
|
'fade-to-red': {
|
||||||
'0%, 100%': { backgroundColor: 'hsl(var(--primary))' },
|
'0%': { backgroundColor: 'hsl(var(--primary))' },
|
||||||
'50%': { backgroundColor: 'hsl(0, 84%, 60%)' }
|
'100%': { backgroundColor: 'hsl(0, 84%, 60%)' }
|
||||||
|
},
|
||||||
|
'fade-from-red': {
|
||||||
|
'0%': { backgroundColor: 'hsl(0, 84%, 60%)' },
|
||||||
|
'100%': { backgroundColor: 'hsl(var(--primary))' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
'fade-red': 'fade-red 1.6s ease-in-out infinite'
|
'fade-to-red': 'fade-to-red 0.5s ease-out forwards',
|
||||||
|
'fade-from-red': 'fade-from-red 0.5s ease-out forwards'
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'sans': ['Lato', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif'],
|
'sans': ['Lato', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif'],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue