zahlzerlegung/src/lib/components/game/HeightTrack.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>