piclets / src /lib /components /Battle /BattleEffects.svelte
Fraser's picture
GBA style flash
5207761
raw
history blame
5.98 kB
<script lang="ts">
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
export let effects: Array<{type: string, emoji: string, duration: number}> = [];
export let flash: boolean = false;
// GBA-style flicker animation parameters
const flickerCount = 19;
const frameDelay = 2;
const flickerDuration = 600; // milliseconds
// Flicker state management
let isFlickering = false;
let flickerVisible = true;
let flickerFrame = 0;
let flickerInterval: number;
// Watch for flash changes to trigger flicker animation
$: if (flash && !isFlickering) {
startFlickerAnimation();
}
function startFlickerAnimation() {
isFlickering = true;
flickerFrame = 0;
// Calculate frame duration based on total duration and frame count
const totalFrames = flickerCount * (frameDelay + 1);
const frameDuration = flickerDuration / totalFrames;
flickerInterval = setInterval(() => {
if (flickerFrame >= totalFrames) {
// Animation finished, always visible
clearInterval(flickerInterval);
isFlickering = false;
flickerVisible = true;
return;
}
// Toggle visibility every frameDelay frames
const flickerCycle = Math.floor(flickerFrame / (frameDelay + 1));
flickerVisible = flickerCycle % 2 === 0;
flickerFrame++;
}, frameDuration);
}
onMount(() => {
return () => {
if (flickerInterval) {
clearInterval(flickerInterval);
}
};
});
</script>
<!-- Effects wrapper with relative positioning for particles -->
<div class="effects-wrapper">
<!-- GBA-style flicker effect -->
<div class="effects-container" style="opacity: {(flash && isFlickering) ? (flickerVisible ? 1 : 0) : 1};">
<slot />
</div>
<!-- Particle effects -->
{#each effects as effect (effect)}
<div class="effect-particle {effect.type}" style="animation-duration: {effect.duration}ms">
<span class="effect-emoji">{effect.emoji}</span>
</div>
{/each}
</div>
<style>
.effects-wrapper {
position: relative;
display: inline-block;
}
.effects-container {
position: relative;
display: inline-block;
transition: opacity 0.05s ease;
}
.effect-particle {
position: absolute;
pointer-events: none;
z-index: 5;
animation-fill-mode: forwards;
}
.effect-emoji {
font-size: 24px;
display: block;
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.3));
}
/* Damage and status effects */
.effect-particle.burn,
.effect-particle.poison,
.effect-particle.paralyze,
.effect-particle.sleep,
.effect-particle.freeze {
top: 20%;
left: 50%;
animation: statusEffect linear;
}
/* Stat changes */
.effect-particle.attackUp,
.effect-particle.defenseUp,
.effect-particle.speedUp,
.effect-particle.accuracyUp {
bottom: 30%;
left: 50%;
animation: statIncrease ease-out;
}
.effect-particle.attackDown,
.effect-particle.defenseDown,
.effect-particle.speedDown,
.effect-particle.accuracyDown {
top: 30%;
left: 50%;
animation: statDecrease ease-out;
}
/* Special effects */
.effect-particle.critical,
.effect-particle.superEffective {
top: 10%;
left: 50%;
animation: criticalEffect ease-out;
}
.effect-particle.notVeryEffective,
.effect-particle.miss {
top: 40%;
left: 50%;
animation: missEffect ease-out;
}
.effect-particle.heal {
bottom: 20%;
left: 50%;
animation: healEffect ease-out;
}
/* Animations */
@keyframes statusEffect {
0% {
transform: translate(-50%, 0) scale(0.5);
opacity: 0;
}
20% {
transform: translate(-50%, -10px) scale(1.2);
opacity: 1;
}
40% {
transform: translate(-50%, -5px) scale(1);
opacity: 1;
}
100% {
transform: translate(-50%, -20px) scale(0.8);
opacity: 0;
}
}
@keyframes statIncrease {
0% {
transform: translate(-50%, 0) scale(0.5);
opacity: 0;
}
30% {
transform: translate(-50%, -30px) scale(1.3);
opacity: 1;
}
70% {
transform: translate(-50%, -40px) scale(1.1);
opacity: 1;
}
100% {
transform: translate(-50%, -60px) scale(0.7);
opacity: 0;
}
}
@keyframes statDecrease {
0% {
transform: translate(-50%, 0) scale(0.5);
opacity: 0;
}
30% {
transform: translate(-50%, 20px) scale(1.3);
opacity: 1;
}
70% {
transform: translate(-50%, 30px) scale(1.1);
opacity: 1;
}
100% {
transform: translate(-50%, 50px) scale(0.7);
opacity: 0;
}
}
@keyframes criticalEffect {
0% {
transform: translate(-50%, 0) scale(0.3) rotate(-10deg);
opacity: 0;
}
20% {
transform: translate(-50%, -20px) scale(1.5) rotate(5deg);
opacity: 1;
}
40% {
transform: translate(-50%, -15px) scale(1.3) rotate(-2deg);
opacity: 1;
}
60% {
transform: translate(-50%, -25px) scale(1.4) rotate(1deg);
opacity: 1;
}
100% {
transform: translate(-50%, -40px) scale(0.8) rotate(0deg);
opacity: 0;
}
}
@keyframes missEffect {
0% {
transform: translate(-50%, 0) scale(1);
opacity: 0;
}
20% {
transform: translate(-50%, 0) scale(1.2);
opacity: 0.7;
}
80% {
transform: translate(-50%, 0) scale(1.1);
opacity: 0.3;
}
100% {
transform: translate(-50%, 0) scale(1);
opacity: 0;
}
}
@keyframes healEffect {
0% {
transform: translate(-50%, 20px) scale(0.5);
opacity: 0;
}
30% {
transform: translate(-50%, -10px) scale(1.2);
opacity: 1;
}
70% {
transform: translate(-50%, -30px) scale(1);
opacity: 1;
}
100% {
transform: translate(-50%, -50px) scale(0.6);
opacity: 0;
}
}
</style>