Highscore-Hintergrund: größere, abwechslungsreichere und kräftigere Animationen; Mond zieht über die volle Breite

This commit is contained in:
schmop 2026-05-31 17:28:34 +02:00
parent 3922089eff
commit aceb783584
2 changed files with 107 additions and 105 deletions

View File

@ -60,9 +60,14 @@
gap: 10px;
height: 100%;
}
/* Karten sind bewusst deckend (dunkel, frosted): die Hintergrund-Animationen
laufen dahinter und können die Inhalte nie überdecken oder unleserlich machen. */
.slot {
position: relative;
background: rgba(255, 255, 255, 0.16);
background: rgba(16, 26, 54, 0.8);
border: 1px solid rgba(255, 255, 255, 0.16);
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
border-radius: 14px;
display: flex;
align-items: center;
@ -71,11 +76,11 @@
min-height: 56px;
}
.slot.empty {
background: rgba(255, 255, 255, 0.06);
background: rgba(16, 26, 54, 0.45);
}
.slot.current {
background: rgba(255, 229, 102, 0.3);
box-shadow: 0 0 0 3px var(--c-rocket-window);
border-color: var(--c-rocket-window);
box-shadow: 0 0 0 2px var(--c-rocket-window), 0 0 18px rgba(255, 229, 102, 0.5);
}
.crown {
position: absolute;

View File

@ -8,158 +8,155 @@
let { stage }: { stage: Stage } = $props();
// 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 };
// Animationen füllen die ganze Fläche hinter den (deckenden) Karten. l/t/b in %,
// s = Größe, d = Dauer (s), dl = Verzögerung (s), flip/rot = Variation, c/sc/cv = Symbol-Variante.
type Deco = {
l: number; t?: number; b?: number; s: number; d: number; dl: number;
flip?: boolean; rot?: number; c?: number; sc?: number; cv?: number;
};
// Ballon-Farbvarianten [Körper, Kontur].
const BALLOON_COLORS = [
['#ff8aa8', '#c25578'],
['#6cc2ff', '#3a82c2'],
['#7ad17a', '#3f8f3f'],
['#ffd24a', '#c79a2a'],
['#c79aff', '#8a5fd0'],
];
const STAR_COLORS = ['#ffe34a', '#ffffff', '#a8e8ff', '#ffd24a'];
const balloons: Deco[] = [
{ 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 },
{ l: 5, t: 52, s: 92, c: 0, rot: -5, d: 3.6, dl: 0 },
{ l: 17, t: 12, s: 62, c: 1, flip: true, rot: 7, d: 4.3, dl: 0.7 },
{ l: 31, t: 76, s: 76, c: 2, rot: 3, d: 3.9, dl: 0.3 },
{ l: 58, t: 28, s: 70, c: 3, flip: true, rot: -6, d: 4.6, dl: 1.0 },
{ l: 80, t: 60, s: 96, c: 4, rot: 4, d: 3.4, dl: 0.2 },
{ l: 91, t: 16, s: 58, c: 1, flip: true, rot: -3, d: 4.8, dl: 0.5 },
{ l: 69, t: 86, s: 60, c: 0, rot: 5, d: 4.0, dl: 0.9 },
];
const clouds: Deco[] = [
{ 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[] = [
{ 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 },
{ l: 3, t: 16, s: 124, cv: 0, d: 7.5, dl: 0 },
{ l: 24, t: 62, s: 102, cv: 1, flip: true, d: 9, dl: 0.6 },
{ l: 13, t: 82, s: 92, cv: 0, flip: true, d: 8.4, dl: 1.2 },
{ l: 57, t: 22, s: 146, cv: 1, d: 7, dl: 0.3 },
{ l: 79, t: 64, s: 112, cv: 0, flip: true, d: 9.4, dl: 0.9 },
{ l: 88, t: 38, s: 96, cv: 1, d: 8, dl: 1.5 },
];
const stars: Deco[] = [
{ 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 },
{ l: 7, t: 15, s: 42, sc: 0, rot: 0, d: 1.8, dl: 0 },
{ l: 19, t: 49, s: 28, sc: 1, rot: 18, d: 2.4, dl: 0.5 },
{ l: 11, t: 79, s: 36, sc: 2, rot: -12, d: 2.0, dl: 1.0 },
{ l: 34, t: 29, s: 24, sc: 0, rot: 8, d: 2.6, dl: 0.3 },
{ l: 29, t: 88, s: 32, sc: 3, rot: -8, d: 2.2, dl: 0.8 },
{ l: 61, t: 13, s: 34, sc: 0, rot: 14, d: 1.9, dl: 0.4 },
{ l: 73, t: 47, s: 48, sc: 1, rot: -10, d: 2.3, dl: 0.1 },
{ l: 66, t: 82, s: 26, sc: 2, rot: 6, d: 2.7, dl: 1.1 },
{ l: 87, t: 23, s: 40, sc: 0, rot: -16, d: 2.0, dl: 0.7 },
{ l: 93, t: 61, s: 30, sc: 3, rot: 10, d: 2.5, dl: 0.2 },
];
const grass: Deco[] = [
{ 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 },
{ l: 5, b: 1, s: 66, d: 2.4, dl: 0 },
{ l: 19, b: 2, s: 48, flip: true, d: 2.8, dl: 0.4 },
{ l: 33, b: 1, s: 56, d: 2.2, dl: 0.7 },
{ l: 63, b: 2, s: 50, flip: true, d: 2.6, dl: 0.2 },
{ l: 78, b: 1, s: 70, d: 2.3, dl: 0.9 },
{ l: 90, b: 2, s: 46, flip: true, d: 2.7, dl: 0.5 },
];
function pos(d: Deco): string {
function baseStyle(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`;
return `left:${d.l}%; ${vert}; transform: scaleX(${d.flip ? -1 : 1}) rotate(${d.rot ?? 0}deg);`;
}
const fx = (d: Deco) => `--dur:${d.d}s; animation-delay:${d.dl}s`;
</script>
{#snippet layer(side: Side)}
<div class="ambience" aria-hidden="true">
{#if stage === 1}
{#each balloons.filter((d) => d.side === side) as d, i (i)}
<span class="deco bob" style={pos(d)}><Balloon size={d.s} /></span>
{#each balloons as d, i (i)}
<span class="deco" style={baseStyle(d)}>
<span class="anim float" style={fx(d)}>
<Balloon size={d.s} color={BALLOON_COLORS[d.c ?? 0][0]} stroke={BALLOON_COLORS[d.c ?? 0][1]} />
</span>
</span>
{/each}
{:else if stage === 2}
{#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 clouds as d, i (i)}
<span class="deco" style={baseStyle(d)}>
<span class="anim drift" style={fx(d)}><Cloud size={d.s} variant={d.cv ?? 0} opacity={0.95} /></span>
</span>
{/each}
{:else if stage === 3}
{#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}
<!-- Mond zieht eine Bahn über die ganze Breite, keine Sterne (sonst Verwechslung mit dem Stern-Level). -->
<span class="moon"><Moon size={104} /></span>
{:else if stage >= 4}
{#each stars.filter((d) => d.side === side) as d, i (i)}
<span class="deco twinkle" style={pos(d)}><Star size={d.s} /></span>
{#each stars as d, i (i)}
<span class="deco" style={baseStyle(d)}>
<span class="anim twinkle" style={fx(d)}><Star size={d.s} color={STAR_COLORS[d.sc ?? 0]} /></span>
</span>
{/each}
{:else}
{#each grass.filter((d) => d.side === side) as d, i (i)}
<span class="deco sway" style={pos(d)}><Ground size={d.s} /></span>
{#each grass as d, i (i)}
<span class="deco" style={baseStyle(d)}>
<span class="anim sway" style={fx(d)}><Ground size={d.s} /></span>
</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;
opacity: 0.9;
}
.anim {
display: inline-block;
}
.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); }
.float { animation: float var(--dur, 3.6s) ease-in-out infinite alternate; }
@keyframes float {
0% { transform: translateY(18px) rotate(-5deg); }
100% { transform: translateY(-30px) rotate(5deg); }
}
.drift { animation: drift var(--dur, 9s) ease-in-out infinite alternate; }
.drift { animation: drift var(--dur, 8s) ease-in-out infinite alternate; }
@keyframes drift {
from { transform: translateX(-22px); }
to { transform: translateX(22px); }
from { transform: translateX(-55px); }
to { transform: translateX(55px); }
}
.twinkle { animation: twinkle var(--dur, 2.4s) ease-in-out infinite; }
.twinkle { animation: twinkle var(--dur, 2.2s) ease-in-out infinite; }
@keyframes twinkle {
0%, 100% { opacity: 0.3; transform: scale(0.75); }
50% { opacity: 1; transform: scale(1.15); }
0%, 100% { opacity: 0.25; transform: scale(0.5); }
50% { opacity: 1; transform: scale(1.35); }
}
.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; }
.sway { animation: sway var(--dur, 2.5s) ease-in-out infinite alternate; transform-origin: bottom center; }
@keyframes sway {
from { transform: rotate(-7deg); }
to { transform: rotate(7deg); }
from { transform: rotate(-12deg); }
to { transform: rotate(12deg); }
}
.moon {
position: absolute;
filter: drop-shadow(0 0 18px rgba(255, 233, 163, 0.85));
animation: moonpath 12s linear infinite;
}
@keyframes moonpath {
0% { left: -16%; top: 36%; }
50% { top: 14%; }
100% { left: 112%; top: 36%; }
}
@media (prefers-reduced-motion: reduce) {
.deco { animation: none; }
.anim, .moon { animation: none; }
}
</style>