126 lines
4.2 KiB
Svelte
126 lines
4.2 KiB
Svelte
<script lang="ts">
|
|
import Rocket from '../svg/Rocket.svelte';
|
|
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 { STAGE_THRESHOLDS } from '../../game/stages';
|
|
|
|
type Props = { correct: number; won: boolean };
|
|
let { correct, won }: Props = $props();
|
|
|
|
// Höhenpositionen 0..1 entlang der Bahn (von unten = 0 nach oben = 1); Sterne sind oben.
|
|
const STAGE_Y = [0.05, 0.25, 0.45, 0.65, 0.85] as const;
|
|
const WIN_Y = 0.95; // beim Sieg fliegt die Rakete hoch hinauf zu den Sternen
|
|
|
|
// Rakete bewegt sich kontinuierlich pro richtiger Antwort: bei den Schwellen-Counts
|
|
// sitzt sie genau auf der jeweiligen Stufen-Markierung, dazwischen wird interpoliert.
|
|
// So steigt sie schon ab der ersten richtigen Antwort sichtbar.
|
|
const BREAKPOINTS = [0, ...STAGE_THRESHOLDS] as const; // [0, 2, 4, 7, 10]
|
|
function yFor(c: number): number {
|
|
if (c <= 0) return STAGE_Y[0];
|
|
for (let i = 1; i < BREAKPOINTS.length; i++) {
|
|
if (c <= BREAKPOINTS[i]) {
|
|
const lo = BREAKPOINTS[i - 1];
|
|
const t = (c - lo) / (BREAKPOINTS[i] - lo);
|
|
return STAGE_Y[i - 1] + t * (STAGE_Y[i] - STAGE_Y[i - 1]);
|
|
}
|
|
}
|
|
return STAGE_Y[4];
|
|
}
|
|
const rocketY = $derived(won ? WIN_Y : yFor(correct));
|
|
|
|
// Sterne der obersten Stufe: oberhalb der letzten Linie (>85%) frei im ganzen
|
|
// oberen Bereich verteilt, unterschiedliche Höhen, einheitliche Farbe. l/b in %.
|
|
const STARS = [
|
|
{ l: 11, b: 97, s: 26 },
|
|
{ l: 27, b: 90, s: 32 },
|
|
{ l: 45, b: 98, s: 22 },
|
|
{ l: 62, b: 91, s: 30 },
|
|
{ l: 80, b: 97, s: 24 },
|
|
{ l: 89, b: 88, s: 26 },
|
|
{ l: 19, b: 87, s: 20 },
|
|
{ l: 71, b: 87, s: 22 },
|
|
{ l: 38, b: 89, s: 18 },
|
|
];
|
|
</script>
|
|
|
|
<div class="track" class:won>
|
|
<div class="layer ground"></div>
|
|
<div class="layer sky-low"></div>
|
|
<div class="layer sky-mid"></div>
|
|
<div class="layer sky-high"></div>
|
|
<div class="layer space"></div>
|
|
|
|
<!-- Stufen-Linien: jede erreichbare Stufe über die gesamte Breite -->
|
|
{#each [STAGE_Y[1], STAGE_Y[2], STAGE_Y[3], STAGE_Y[4]] as y (y)}
|
|
<div class="stage-line" style="bottom: {y * 100}%"></div>
|
|
{/each}
|
|
|
|
<!-- Markierungen -->
|
|
<div class="marker" style="bottom: {STAGE_Y[1] * 100}%"><Balloon size={78} /></div>
|
|
<div class="marker" style="bottom: {STAGE_Y[2] * 100}%; left: 70%"><Cloud size={92} /></div>
|
|
<div class="marker" style="bottom: {STAGE_Y[3] * 100}%; left: 30%"><Moon size={78} /></div>
|
|
<!-- Sterne-Stufe: oberhalb der letzten Linie im ganzen oberen Bereich verteilt, einheitliche Farbe. -->
|
|
{#each STARS as s, i (i)}
|
|
<div class="star" style="left: {s.l}%; bottom: {s.b}%"><Star size={s.s} /></div>
|
|
{/each}
|
|
|
|
<div class="rocket" style="bottom: calc({rocketY * 100}% - 60px)" aria-hidden="true">
|
|
<Rocket size={120} />
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.track {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 18px;
|
|
overflow: hidden;
|
|
background: var(--c-sky-top);
|
|
}
|
|
.layer {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
}
|
|
.ground { bottom: 0; height: 8%; background: linear-gradient(to top, #4ea34e, var(--c-ground)); }
|
|
.sky-low { bottom: 8%; height: 22%; background: linear-gradient(to top, #b6e3ff, #6fb3ff); }
|
|
.sky-mid { bottom: 30%; height: 30%; background: linear-gradient(to top, #6fb3ff, #4a7ec4); }
|
|
.sky-high { bottom: 60%; height: 25%; background: linear-gradient(to top, #4a7ec4, #2a3f7a); }
|
|
.space { bottom: 85%; height: 15%; background: linear-gradient(to top, #2a3f7a, #0a0e2a); }
|
|
|
|
.stage-line {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
border-top: 2px dotted rgba(255, 255, 255, 0.3);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.marker {
|
|
position: absolute;
|
|
left: 18%;
|
|
transform: translate(-50%, 50%);
|
|
pointer-events: none;
|
|
}
|
|
.star {
|
|
position: absolute;
|
|
transform: translate(-50%, 50%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.rocket {
|
|
position: absolute;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
transition: bottom 0.6s cubic-bezier(0.2, 0.9, 0.2, 1);
|
|
filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.35));
|
|
}
|
|
.track.won .rocket {
|
|
transition: bottom 1.4s cubic-bezier(0.18, 0.7, 0.2, 1);
|
|
}
|
|
</style>
|