Ergebnis-Animationen auf Seiten-Zonen begrenzen, damit sie die Karten nie überdecken

This commit is contained in:
schmop 2026-05-31 17:04:13 +02:00
parent 8947a28e59
commit dde6271cf1

View File

@ -8,55 +8,58 @@
let { stage }: { stage: Stage } = $props();
// Deko-Positionen rahmen die zentrierte Highscore-Spalte ein (Mitte bleibt frei).
// l/t/b in %, s = Symbolgröße, d = Animationsdauer (s), dl = Verzögerung (s).
type Deco = { l: number; t?: number; b?: number; s: number; d: number; dl: number };
// Deko lebt ausschließlich in zwei Seiten-Zonen links und rechts der zentrierten
// Highscore-Spalte. Die Zonen klammern die Spalte aus und sind geclippt, sodass
// keine Animation jemals über eine Karte läuft.
// l = Position in % innerhalb der Zone, t/b = oben/unten in %, s = Größe,
// d = Dauer (s), dl = Verzögerung (s), side = Zone.
type Side = 'l' | 'r';
type Deco = { side: Side; l: number; t?: number; b?: number; s: number; d: number; dl: number };
const balloons: Deco[] = [
{ l: 9, t: 60, s: 66, d: 4.5, dl: 0 },
{ l: 21, t: 26, s: 46, d: 5.2, dl: 0.6 },
{ l: 82, t: 54, s: 72, d: 4.0, dl: 0.3 },
{ l: 90, t: 22, s: 50, d: 5.6, dl: 0.9 },
{ l: 5, t: 36, s: 42, d: 4.8, dl: 1.2 },
{ l: 74, t: 80, s: 40, d: 5.0, dl: 0.4 },
{ side: 'l', l: 55, t: 58, s: 62, d: 4.5, dl: 0 },
{ side: 'l', l: 25, t: 26, s: 44, d: 5.2, dl: 0.6 },
{ side: 'l', l: 70, t: 82, s: 38, d: 5.0, dl: 0.4 },
{ side: 'r', l: 40, t: 54, s: 68, d: 4.0, dl: 0.3 },
{ side: 'r', l: 65, t: 22, s: 48, d: 5.6, dl: 0.9 },
{ side: 'r', l: 20, t: 78, s: 40, d: 4.8, dl: 1.2 },
];
const clouds: Deco[] = [
{ l: 3, t: 15, s: 96, d: 9, dl: 0 },
{ l: 58, t: 22, s: 120, d: 11, dl: 1 },
{ l: 11, t: 72, s: 84, d: 10, dl: 0.5 },
{ l: 70, t: 76, s: 104, d: 8.5, dl: 1.5 },
{ l: 83, t: 44, s: 76, d: 9.5, dl: 0.8 },
{ side: 'l', l: 18, t: 16, s: 88, d: 9, dl: 0 },
{ side: 'l', l: 42, t: 70, s: 78, d: 10, dl: 0.5 },
{ side: 'l', l: 58, t: 44, s: 68, d: 9.5, dl: 0.8 },
{ side: 'r', l: 22, t: 24, s: 100, d: 11, dl: 1 },
{ side: 'r', l: 40, t: 78, s: 92, d: 8.5, dl: 1.5 },
];
const moonStars: Deco[] = [
{ l: 14, t: 24, s: 24, d: 2.2, dl: 0 },
{ l: 24, t: 58, s: 18, d: 2.8, dl: 0.5 },
{ l: 8, t: 74, s: 22, d: 2.4, dl: 0.9 },
{ l: 88, t: 60, s: 20, d: 2.6, dl: 0.3 },
{ l: 18, t: 40, s: 14, d: 3.0, dl: 1.1 },
{ l: 72, t: 30, s: 16, d: 2.5, dl: 0.7 },
{ side: 'l', l: 30, t: 24, s: 24, d: 2.2, dl: 0 },
{ side: 'l', l: 55, t: 58, s: 18, d: 2.8, dl: 0.5 },
{ side: 'l', l: 18, t: 76, s: 22, d: 2.4, dl: 0.9 },
{ side: 'r', l: 65, t: 62, s: 20, d: 2.6, dl: 0.3 },
{ side: 'r', l: 35, t: 44, s: 16, d: 3.0, dl: 1.1 },
{ side: 'r', l: 72, t: 32, s: 16, d: 2.5, dl: 0.7 },
];
const stars: Deco[] = [
{ l: 10, t: 18, s: 28, d: 2.2, dl: 0 },
{ l: 22, t: 46, s: 20, d: 2.6, dl: 0.6 },
{ l: 8, t: 70, s: 24, d: 2.4, dl: 1.0 },
{ l: 30, t: 80, s: 16, d: 3.0, dl: 0.3 },
{ l: 90, t: 20, s: 26, d: 2.3, dl: 0.8 },
{ l: 78, t: 46, s: 18, d: 2.7, dl: 0.2 },
{ l: 92, t: 70, s: 22, d: 2.5, dl: 1.2 },
{ l: 70, t: 80, s: 16, d: 2.9, dl: 0.5 },
{ l: 16, t: 32, s: 14, d: 3.1, dl: 1.4 },
{ l: 84, t: 34, s: 14, d: 2.8, dl: 0.9 },
{ side: 'l', l: 25, t: 18, s: 28, d: 2.2, dl: 0 },
{ side: 'l', l: 55, t: 46, s: 20, d: 2.6, dl: 0.6 },
{ side: 'l', l: 18, t: 70, s: 24, d: 2.4, dl: 1.0 },
{ side: 'l', l: 65, t: 82, s: 16, d: 3.0, dl: 0.3 },
{ side: 'l', l: 40, t: 32, s: 14, d: 3.1, dl: 1.4 },
{ side: 'r', l: 60, t: 20, s: 26, d: 2.3, dl: 0.8 },
{ side: 'r', l: 35, t: 46, s: 18, d: 2.7, dl: 0.2 },
{ side: 'r', l: 70, t: 70, s: 22, d: 2.5, dl: 1.2 },
{ side: 'r', l: 30, t: 82, s: 16, d: 2.9, dl: 0.5 },
{ side: 'r', l: 55, t: 34, s: 14, d: 2.8, dl: 0.9 },
];
const grass: Deco[] = [
{ l: 7, b: 2, s: 48, d: 3.0, dl: 0 },
{ l: 23, b: 2, s: 38, d: 3.4, dl: 0.4 },
{ l: 69, b: 2, s: 44, d: 2.8, dl: 0.2 },
{ l: 86, b: 2, s: 36, d: 3.2, dl: 0.6 },
{ l: 47, b: 2, s: 34, d: 3.0, dl: 0.5 },
{ side: 'l', l: 25, b: 2, s: 46, d: 3.0, dl: 0 },
{ side: 'l', l: 60, b: 2, s: 38, d: 3.4, dl: 0.4 },
{ side: 'r', l: 30, b: 2, s: 44, d: 2.8, dl: 0.2 },
{ side: 'r', l: 65, b: 2, s: 36, d: 3.2, dl: 0.6 },
];
function pos(d: Deco): string {
@ -65,38 +68,61 @@
}
</script>
<div class="ambience" aria-hidden="true">
{#snippet layer(side: Side)}
{#if stage === 1}
{#each balloons as d, i (i)}
{#each balloons.filter((d) => d.side === side) as d, i (i)}
<span class="deco bob" style={pos(d)}><Balloon size={d.s} /></span>
{/each}
{:else if stage === 2}
{#each clouds as d, i (i)}
{#each clouds.filter((d) => d.side === side) as d, i (i)}
<span class="deco drift" style={pos(d)}><Cloud size={d.s} opacity={0.92} /></span>
{/each}
{:else if stage === 3}
<span class="deco glow" style="left:80%; top:14%; --dur:5s; animation-delay:0s"><Moon size={86} /></span>
{#each moonStars as d, i (i)}
{#if side === 'r'}
<span class="deco glow" style="left:42%; top:14%; --dur:5s; animation-delay:0s"><Moon size={80} /></span>
{/if}
{#each moonStars.filter((d) => d.side === side) as d, i (i)}
<span class="deco twinkle" style={pos(d)}><Star size={d.s} color="#fff7c8" /></span>
{/each}
{:else if stage >= 4}
{#each stars as d, i (i)}
{#each stars.filter((d) => d.side === side) as d, i (i)}
<span class="deco twinkle" style={pos(d)}><Star size={d.s} /></span>
{/each}
{:else}
{#each grass as d, i (i)}
{#each grass.filter((d) => d.side === side) as d, i (i)}
<span class="deco sway" style={pos(d)}><Ground size={d.s} /></span>
{/each}
{/if}
{/snippet}
<div class="ambience" aria-hidden="true">
<div class="zone left">{@render layer('l')}</div>
<div class="zone right">{@render layer('r')}</div>
</div>
<style>
/* --reserve = halbe Kartenbreite (60px) + Abstand. So bleibt die zentrierte
Spalte garantiert frei und die Zonen clippen alles, was hineinragen würde. */
.ambience {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
--reserve: 80px;
}
.zone {
position: absolute;
top: 0;
bottom: 0;
overflow: hidden;
}
.zone.left {
left: 0;
right: calc(50% + var(--reserve));
}
.zone.right {
left: calc(50% + var(--reserve));
right: 0;
}
.deco {
position: absolute;
@ -111,8 +137,8 @@
.drift { animation: drift var(--dur, 9s) ease-in-out infinite alternate; }
@keyframes drift {
from { transform: translateX(-26px); }
to { transform: translateX(26px); }
from { transform: translateX(-22px); }
to { transform: translateX(22px); }
}
.twinkle { animation: twinkle var(--dur, 2.4s) ease-in-out infinite; }