add score pulse and floating delta on each hit

Score text pulses (scale 1.3→1) on each point gain. A +delta floater
appears at a random offset near the score and fades upward over 0.8s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
main
name 2026-04-11 19:36:14 -07:00
parent 0b134d1dbe
commit d2630b1b64
2 changed files with 56 additions and 3 deletions

View File

@ -18,6 +18,31 @@
color: #fff;
text-shadow: 0 0 16px rgba(217, 70, 239, 0.6), 0 2px 4px rgba(0, 0, 0, 0.8);
font-variant-numeric: tabular-nums;
display: inline-block;
}
.scorePulse {
animation: score-beat 0.2s ease-out;
}
@keyframes score-beat {
0% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.scoreDelta {
position: absolute;
font-size: 20px;
font-weight: 700;
color: #00ffcc;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
pointer-events: none;
animation: delta-float 0.8s ease-out forwards;
}
@keyframes delta-float {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-30px); }
}
.menuBtn {

View File

@ -56,20 +56,35 @@ export function GamePage() {
const gameReady = calibrationStatus === 'ready'
// Score delta floater
const [scoreDelta, setScoreDelta] = useState<{ value: number; key: number; dx: number; dy: number } | null>(null)
const [scorePulse, setScorePulse] = useState(0)
// Scoring: base points + combo bonus (capped at 4x)
const handleJudgment = useCallback(
(j: 'perfect' | 'good' | 'miss', combo: number) => {
let delta = 0
if (j === 'perfect') {
addPerfectHit()
const multiplier = 1 + Math.min(combo, 30) / 10
addScore(Math.round(1000 * multiplier))
delta = Math.round(1000 * multiplier)
} else if (j === 'good') {
addGoodHit()
const multiplier = 1 + Math.min(combo, 30) / 10
addScore(Math.round(500 * multiplier))
delta = Math.round(500 * multiplier)
} else {
addMiss()
}
if (delta > 0) {
addScore(delta)
setScoreDelta({
value: delta,
key: Date.now(),
dx: 20 + Math.random() * 80,
dy: -10 + Math.random() * 40,
})
setScorePulse(Date.now())
}
},
[addPerfectHit, addGoodHit, addMiss, addScore],
)
@ -104,9 +119,22 @@ export function GamePage() {
{error && <div className="status error">{error}</div>}
<div className={styles.scoreHud}>
<span className={`${styles.scoreValue} font-title`}>
<span
key={scorePulse}
className={`${styles.scoreValue} ${scorePulse ? styles.scorePulse : ''} font-title`}
>
{currentScore.toLocaleString()}
</span>
{scoreDelta && (
<span
key={scoreDelta.key}
className={styles.scoreDelta}
style={{ left: scoreDelta.dx, top: scoreDelta.dy }}
onAnimationEnd={() => setScoreDelta(null)}
>
+{scoreDelta.value.toLocaleString()}
</span>
)}
</div>
<video ref={videoRef} className="webcam-video" playsInline muted />