streak: replace burst flame with rising particle effect
All checks were successful
CI / update (push) Successful in 1m23s

Rework the burst mode in FireEffect to use 24 data-driven particles
instead of the old scale-and-pop flame. Each particle has unique
position, size, delay, and duration for an organic rising effect.
Latch burst state in StreakAura so the animation plays its full
duration regardless of when the parent resets the prop.
This commit is contained in:
2026-02-03 08:00:57 +01:00
parent 52ae9659b8
commit 649bd19287
2 changed files with 85 additions and 19 deletions

View File

@@ -6,9 +6,51 @@
} }
let { holy = false, burst = false }: Props = $props(); let { holy = false, burst = false }: Props = $props();
const burstParticles = [
{ x: 10, y: 0, size: 8, delay: 0, dur: 1.6 },
{ x: 25, y: 5, size: 10, delay: 0.05, dur: 1.8 },
{ x: 40, y: 10, size: 12, delay: 0.02, dur: 2.0 },
{ x: 55, y: 3, size: 7, delay: 0.1, dur: 1.7 },
{ x: 70, y: 8, size: 9, delay: 0.08, dur: 1.9 },
{ x: 85, y: 2, size: 11, delay: 0.12, dur: 1.6 },
{ x: 15, y: 15, size: 6, delay: 0.15, dur: 1.5 },
{ x: 35, y: 20, size: 10, delay: 0.18, dur: 1.8 },
{ x: 50, y: 12, size: 8, delay: 0.07, dur: 2.0 },
{ x: 65, y: 18, size: 7, delay: 0.22, dur: 1.7 },
{ x: 80, y: 25, size: 9, delay: 0.1, dur: 1.9 },
{ x: 20, y: 30, size: 11, delay: 0.25, dur: 1.6 },
{ x: 45, y: 22, size: 6, delay: 0.03, dur: 1.8 },
{ x: 60, y: 28, size: 10, delay: 0.2, dur: 2.0 },
{ x: 75, y: 15, size: 8, delay: 0.14, dur: 1.5 },
{ x: 30, y: 35, size: 12, delay: 0.28, dur: 1.7 },
{ x: 5, y: 10, size: 7, delay: 0.06, dur: 1.9 },
{ x: 90, y: 20, size: 9, delay: 0.16, dur: 1.6 },
{ x: 48, y: 32, size: 8, delay: 0.3, dur: 2.0 },
{ x: 22, y: 8, size: 10, delay: 0.11, dur: 1.8 },
{ x: 68, y: 35, size: 6, delay: 0.23, dur: 1.5 },
{ x: 38, y: 5, size: 11, delay: 0.04, dur: 1.7 },
{ x: 82, y: 30, size: 7, delay: 0.26, dur: 1.9 },
{ x: 52, y: 18, size: 9, delay: 0.09, dur: 1.6 },
];
</script> </script>
<div class="fire" class:burst class:holy-fire={holy}> {#if burst}
<div class="burst-particles" class:holy-fire={holy}>
{#each burstParticles as p}
<div
class="bp"
style:left="{p.x}%"
style:bottom="{p.y}%"
style:width="{p.size}px"
style:height="{p.size}px"
style:animation-delay="{p.delay}s"
style:animation-duration="{p.dur}s"
></div>
{/each}
</div>
{:else}
<div class="fire" class:holy-fire={holy}>
<div class="fire-left"> <div class="fire-left">
<div class="main-fire"></div> <div class="main-fire"></div>
<div class="particle-fire"></div> <div class="particle-fire"></div>
@@ -28,6 +70,7 @@
<div class="main-fire"></div> <div class="main-fire"></div>
</div> </div>
</div> </div>
{/if}
<style> <style>
/* ===================== /* =====================
@@ -186,35 +229,46 @@
} }
/* ===================== /* =====================
BURST ANIMATION BURST particles only
===================== */ ===================== */
.fire.burst { .burst-particles {
animation: flame-burst 600ms ease-out forwards; position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 80px;
pointer-events: none;
z-index: 6;
} }
.fire.burst .particle-fire { .burst-particles .bp {
animation: particleBurst 600ms ease-out forwards; position: absolute;
background-color: var(--nord13);
filter: drop-shadow(0 0 4px var(--nord12));
border-radius: 50%;
opacity: 0;
animation: burstParticleUp 2s ease-out forwards;
} }
@keyframes flame-burst { .burst-particles.holy-fire .bp {
background-color: #eaf6ff;
filter: drop-shadow(0 0 12px rgba(180,220,255,.9));
}
@keyframes burstParticleUp {
0% { 0% {
transform: translateX(-50%) scale(0.1) translateY(0%); transform: translateY(0) scale(1);
opacity: 0; opacity: 0;
} }
30% { 10% {
opacity: 1; opacity: 1;
} }
100% { 60% {
transform: translateX(-50%) scale(2) translateY(-10%); opacity: 0.8;
opacity: 0;
} }
}
@keyframes particleBurst {
0% { opacity: 0; }
30% { opacity: 1; }
100% { 100% {
transform: translateY(-120px) scale(0.3); transform: translateY(-80px) scale(0.3);
opacity: 0; opacity: 0;
} }
} }

View File

@@ -8,6 +8,18 @@
let { value = 0, burst = false }: Props = $props(); let { value = 0, burst = false }: Props = $props();
// Latch burst so the FireEffect stays mounted for the full animation
let showBurst = $state(false);
let burstTimer: ReturnType<typeof setTimeout> | undefined;
$effect(() => {
if (burst) {
clearTimeout(burstTimer);
showBurst = true;
burstTimer = setTimeout(() => showBurst = false, 2000);
}
});
const phase = $derived( const phase = $derived(
value >= 365 ? 6 : value >= 365 ? 6 :
value >= 180 ? 5 : value >= 180 ? 5 :
@@ -45,7 +57,7 @@
<FireEffect holy={phase>=4} /> <FireEffect holy={phase>=4} />
{/if} {/if}
{#if burst} {#if showBurst}
<FireEffect holy={phase>=4} burst /> <FireEffect holy={phase>=4} burst />
{/if} {/if}