From 329afd5b0ffff62660124927dd4e363b614da2d0 Mon Sep 17 00:00:00 2001 From: Sterling Archer Date: Wed, 27 May 2026 14:23:29 -0700 Subject: [PATCH] Add curved pokeball throw animation with spin and burst effect on catch attempt --- game.js | 39 +++++++++++++++++++++++++++++++++++++++ style.css | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/game.js b/game.js index 33bed39..4e756e4 100644 --- a/game.js +++ b/game.js @@ -1329,6 +1329,45 @@ class Game { this.state.pokeballs--; SFX.pokeball(); + const catchScreen = document.getElementById('screen-catch'); + const pokemonEl = document.getElementById('catch-wild-pokemon'); + + const ball = document.createElement('img'); + ball.src = './favicon.ico'; + ball.className = 'pokeball-throw'; + catchScreen.appendChild(ball); + + const pokemonRect = pokemonEl.getBoundingClientRect(); + const screenRect = catchScreen.getBoundingClientRect(); + const targetX = pokemonRect.left - screenRect.left + pokemonRect.width / 2 - 16; + const targetY = pokemonRect.top - screenRect.top + pokemonRect.height / 2 - 16; + + ball.style.setProperty('--target-x', targetX + 'px'); + ball.style.setProperty('--target-y', targetY + 'px'); + + setTimeout(() => { + const burst = document.createElement('div'); + burst.className = 'pokeball-burst'; + burst.style.left = targetX + 'px'; + burst.style.top = targetY + 'px'; + catchScreen.appendChild(burst); + + ball.remove(); + pokemonEl.style.opacity = '0'; + pokemonEl.style.transform = 'scale(0.3)'; + pokemonEl.style.transition = 'opacity .3s, transform .3s'; + + setTimeout(() => { + burst.remove(); + pokemonEl.style.opacity = '1'; + pokemonEl.style.transform = ''; + pokemonEl.style.transition = ''; + this._showCatchResult(wild, canCatch); + }, 600); + }, 700); + } + + _showCatchResult(wild, canCatch) { this.show('catch-result'); this._showController(true); diff --git a/style.css b/style.css index d6cc732..d80c3a5 100644 --- a/style.css +++ b/style.css @@ -1266,3 +1266,39 @@ html, body { display:inline-block; margin-right:2px; } + +/* ─── POKEBALL THROW ANIMATION ───────────────────────────── */ +.pokeball-throw { + position:absolute; + width:32px; + height:32px; + bottom:10%; + right:10%; + z-index:200; + image-rendering:pixelated; + animation:pokeThrow .7s cubic-bezier(.2,.8,.3,1) forwards; +} +@keyframes pokeThrow { + 0% { transform:translate(0, 0) rotate(0deg) scale(1); opacity:1; } + 30% { transform:translate(calc(var(--target-x) * 0.3 - 80px), calc(var(--target-y) * 0.5 - 180px)) rotate(-180deg) scale(1.2); } + 60% { transform:translate(calc(var(--target-x) * 0.7 - 40px), calc(var(--target-y) * 0.8 - 100px)) rotate(-360deg) scale(1); } + 100% { transform:translate(calc(var(--target-x) - 100%), calc(var(--target-y) - 90%)) rotate(-540deg) scale(0.8); opacity:1; } +} + +.pokeball-burst { + position:absolute; + width:64px; + height:64px; + margin-left:-32px; + margin-top:-32px; + z-index:200; + border-radius:50%; + background:radial-gradient(circle, rgba(255,255,255,1) 0%, rgba(255,255,200,.8) 30%, rgba(255,200,100,.4) 60%, transparent 80%); + animation:burstAnim .6s ease-out forwards; + pointer-events:none; +} +@keyframes burstAnim { + 0% { transform:scale(0.3); opacity:1; } + 40% { transform:scale(2.5); opacity:.9; } + 100% { transform:scale(4); opacity:0; } +}