Ergebnis-Ansicht: stufenpassende Animationen um die Highscore-Karten statt Regenbogen

This commit is contained in:
schmop 2026-05-31 16:58:08 +02:00
parent b54effef25
commit 60a0fe4297
3 changed files with 141 additions and 29 deletions

View File

@ -0,0 +1,139 @@
<script lang="ts">
import type { Stage } from '../../game/stages';
import Balloon from '../svg/Balloon.svelte';
import Cloud from '../svg/Cloud.svelte';
import Moon from '../svg/Moon.svelte';
import Star from '../svg/Star.svelte';
import Ground from '../svg/Ground.svelte';
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 };
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 },
];
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 },
];
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 },
];
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 },
];
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 },
];
function pos(d: Deco): string {
const vert = d.b != null ? `bottom:${d.b}%` : `top:${d.t}%`;
return `left:${d.l}%; ${vert}; --dur:${d.d}s; animation-delay:${d.dl}s`;
}
</script>
<div class="ambience" aria-hidden="true">
{#if stage === 1}
{#each balloons 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)}
<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)}
<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)}
<span class="deco twinkle" style={pos(d)}><Star size={d.s} /></span>
{/each}
{:else}
{#each grass as d, i (i)}
<span class="deco sway" style={pos(d)}><Ground size={d.s} /></span>
{/each}
{/if}
</div>
<style>
.ambience {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.deco {
position: absolute;
opacity: 0.9;
}
.bob { animation: bob var(--dur, 4s) ease-in-out infinite alternate; }
@keyframes bob {
from { transform: translateY(8px) rotate(-3deg); }
to { transform: translateY(-16px) rotate(3deg); }
}
.drift { animation: drift var(--dur, 9s) ease-in-out infinite alternate; }
@keyframes drift {
from { transform: translateX(-26px); }
to { transform: translateX(26px); }
}
.twinkle { animation: twinkle var(--dur, 2.4s) ease-in-out infinite; }
@keyframes twinkle {
0%, 100% { opacity: 0.3; transform: scale(0.75); }
50% { opacity: 1; transform: scale(1.15); }
}
.glow { animation: glow var(--dur, 5s) ease-in-out infinite alternate; }
@keyframes glow {
from { filter: drop-shadow(0 0 4px rgba(255, 233, 163, 0.4)); transform: translateY(0); }
to { filter: drop-shadow(0 0 16px rgba(255, 233, 163, 0.9)); transform: translateY(-8px); }
}
.sway { animation: sway var(--dur, 3s) ease-in-out infinite alternate; transform-origin: bottom center; }
@keyframes sway {
from { transform: rotate(-7deg); }
to { transform: rotate(7deg); }
}
@media (prefers-reduced-motion: reduce) {
.deco { animation: none; }
}
</style>

View File

@ -1,14 +0,0 @@
<script lang="ts">
let { size = 200 }: { size?: number } = $props();
</script>
<svg width={size} height={size * 0.6} viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g fill="none" stroke-width="14" stroke-linecap="round">
<path d="M20 110 A80 80 0 0 1 180 110" stroke="var(--c-rainbow-1)" />
<path d="M34 110 A66 66 0 0 1 166 110" stroke="var(--c-rainbow-2)" />
<path d="M48 110 A52 52 0 0 1 152 110" stroke="var(--c-rainbow-3)" />
<path d="M62 110 A38 38 0 0 1 138 110" stroke="var(--c-rainbow-4)" />
<path d="M76 110 A24 24 0 0 1 124 110" stroke="var(--c-rainbow-5)" />
<path d="M90 110 A10 10 0 0 1 110 110" stroke="var(--c-rainbow-6)" />
</g>
</svg>

View File

@ -5,8 +5,8 @@
import { startGame } from '../stores/game';
import { goHome, goGame } from '../stores/route';
import { play, unlockAudio } from '../audio/soundManager';
import Rainbow from '../components/svg/Rainbow.svelte';
import HighscoreColumn from '../components/home/HighscoreColumn.svelte';
import StageAmbience from '../components/home/StageAmbience.svelte';
type Props = { target: Target };
let { target }: Props = $props();
@ -15,7 +15,6 @@
const current = $derived($lastRun?.target === target ? $lastRun : null);
const lastRunDate = $derived(current?.date ?? null);
const lastStage = $derived(current?.stage ?? 0);
const isWin = $derived(lastStage >= 4);
function retry() {
unlockAudio();
@ -32,11 +31,7 @@
<div class="screen" in:fade={{ duration: 240 }}>
<div class="celebration">
{#if isWin}
<div class="rainbow-wrap" in:fly={{ y: 30, duration: 600 }}>
<Rainbow size={300} />
</div>
{/if}
<StageAmbience stage={lastStage} />
<div class="scores" in:fly={{ y: 40, duration: 500 }}>
<HighscoreColumn {target} highlightDate={lastRunDate} />
</div>
@ -71,14 +66,6 @@
position: relative;
min-height: 0;
}
.rainbow-wrap {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
opacity: 0.85;
}
.scores {
position: relative;
z-index: 2;