Regenbogenland entfernen — Sterne sind die höchste Stufe

This commit is contained in:
schmop 2026-05-31 16:58:13 +02:00
parent 60a0fe4297
commit 8947a28e59
8 changed files with 17 additions and 40 deletions

View File

@ -9,12 +9,7 @@
--c-rocket-fin: #4a4a6e; --c-rocket-fin: #4a4a6e;
--c-flame-inner: #fff4b3; --c-flame-inner: #fff4b3;
--c-flame-outer: #ff8a3d; --c-flame-outer: #ff8a3d;
--c-rainbow-1: #ff5555; --c-star: #ffe34a;
--c-rainbow-2: #ffa84a;
--c-rainbow-3: #ffe34a;
--c-rainbow-4: #6cdc6c;
--c-rainbow-5: #4ab3ff;
--c-rainbow-6: #b06cff;
--c-correct: #5fd07a; --c-correct: #5fd07a;
--c-text: #1f1f3a; --c-text: #1f1f3a;
--c-text-on-dark: #ffffff; --c-text-on-dark: #ffffff;

View File

@ -7,7 +7,7 @@
// boost — Raketenschub (Whoosh, niedrig) // boost — Raketenschub (Whoosh, niedrig)
// level — neue Stufe (Akkord aufsteigend) // level — neue Stufe (Akkord aufsteigend)
// countdown — letzte 5 Sekunden (Tick pro Sekunde, höher werdend) // countdown — letzte 5 Sekunden (Tick pro Sekunde, höher werdend)
// fanfare — Regenbogenland erreicht (kurze Tonfolge) // fanfare — Sterne erreicht / Runde gewonnen (kurze Tonfolge)
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { settings } from '../stores/settings'; import { settings } from '../stores/settings';

View File

@ -4,15 +4,15 @@
import Cloud from '../svg/Cloud.svelte'; import Cloud from '../svg/Cloud.svelte';
import Moon from '../svg/Moon.svelte'; import Moon from '../svg/Moon.svelte';
import Star from '../svg/Star.svelte'; import Star from '../svg/Star.svelte';
import Rainbow from '../svg/Rainbow.svelte';
import { STAGE_THRESHOLDS } from '../../game/stages'; import { STAGE_THRESHOLDS } from '../../game/stages';
type Props = { correct: number; won: boolean }; type Props = { correct: number; won: boolean };
let { correct, won }: Props = $props(); let { correct, won }: Props = $props();
// Höhenpositionen 0..1 entlang der Bahn (von unten = 0 nach oben = 1) // 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, 0.97] as const; 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 // Rakete bewegt sich kontinuierlich pro richtiger Antwort: bei den Schwellen-Counts
// sitzt sie genau auf der jeweiligen Stufen-Markierung, dazwischen wird interpoliert. // sitzt sie genau auf der jeweiligen Stufen-Markierung, dazwischen wird interpoliert.
@ -29,7 +29,7 @@
} }
return STAGE_Y[4]; return STAGE_Y[4];
} }
const rocketY = $derived(won ? STAGE_Y[5] : yFor(correct)); const rocketY = $derived(won ? WIN_Y : yFor(correct));
</script> </script>
<div class="track" class:won> <div class="track" class:won>
@ -48,9 +48,6 @@
<Star size={20} color="#fff" /> <Star size={20} color="#fff" />
<Star size={24} color="#a8e8ff" /> <Star size={24} color="#a8e8ff" />
</div> </div>
<div class="marker rainbow" style="bottom: {STAGE_Y[5] * 100}%" class:visible={won}>
<Rainbow size={140} />
</div>
<div class="rocket" style="bottom: calc({rocketY * 100}% - 60px)" aria-hidden="true"> <div class="rocket" style="bottom: calc({rocketY * 100}% - 60px)" aria-hidden="true">
<Rocket size={120} /> <Rocket size={120} />
@ -86,17 +83,6 @@
} }
.marker.stars { display: flex; gap: 6px; left: 60%; } .marker.stars { display: flex; gap: 6px; left: 60%; }
.rainbow {
left: 50%;
transform: translate(-50%, 50%);
opacity: 0;
transition: opacity 0.6s ease, transform 0.6s ease;
}
.rainbow.visible {
opacity: 1;
transform: translate(-50%, 50%) scale(1.1);
}
.rocket { .rocket {
position: absolute; position: absolute;
left: 50%; left: 50%;

View File

@ -8,7 +8,6 @@
import Cloud from '../svg/Cloud.svelte'; import Cloud from '../svg/Cloud.svelte';
import Moon from '../svg/Moon.svelte'; import Moon from '../svg/Moon.svelte';
import Star from '../svg/Star.svelte'; import Star from '../svg/Star.svelte';
import Rainbow from '../svg/Rainbow.svelte';
type Props = { target: Target; highlightDate?: string | null }; type Props = { target: Target; highlightDate?: string | null };
let { target, highlightDate = null }: Props = $props(); let { target, highlightDate = null }: Props = $props();
@ -24,7 +23,6 @@
2: { c: Cloud, size: 52 }, 2: { c: Cloud, size: 52 },
3: { c: Moon, size: 40 }, 3: { c: Moon, size: 40 },
4: { c: Star, size: 36 }, 4: { c: Star, size: 36 },
5: { c: Rainbow, size: 56 },
}; };
</script> </script>

View File

@ -9,9 +9,9 @@
const bestStage = $derived(($progress.perTarget[target]?.top5[0]?.stage ?? 0) as number); const bestStage = $derived(($progress.perTarget[target]?.top5[0]?.stage ?? 0) as number);
const runs = $derived($progress.perTarget[target]?.runs ?? 0); const runs = $derived($progress.perTarget[target]?.runs ?? 0);
// Fortschritt 0..1 → Höhe der Mini-Rakete in der Karte // Fortschritt 0..1 → Höhe der Mini-Rakete in der Karte (Stufe 4 = Sterne = ganz oben)
const progressFraction = $derived(Math.min(bestStage / 5, 1)); const progressFraction = $derived(Math.min(bestStage / 4, 1));
const masteredAtRainbow = $derived(bestStage >= 5); const masteredAtStars = $derived(bestStage >= 4);
</script> </script>
<button class="card" type="button" onclick={() => onClick(target)} aria-label={`Zielzahl ${target}`}> <button class="card" type="button" onclick={() => onClick(target)} aria-label={`Zielzahl ${target}`}>
@ -27,7 +27,7 @@
<div class="dot dot-5"></div> <div class="dot dot-5"></div>
</div> </div>
<div class="footer"> <div class="footer">
{#if masteredAtRainbow} {#if masteredAtStars}
<Crown size={28} /> <Crown size={28} />
{/if} {/if}
<span class="runs" aria-label={`${runs} Runden gespielt`}> <span class="runs" aria-label={`${runs} Runden gespielt`}>
@ -87,7 +87,7 @@
.dot-2 { bottom: 38%; } .dot-2 { bottom: 38%; }
.dot-3 { bottom: 58%; } .dot-3 { bottom: 58%; }
.dot-4 { bottom: 78%; } .dot-4 { bottom: 78%; }
.dot-5 { bottom: 92%; background: var(--c-rainbow-3); width: 6px; height: 6px; } .dot-5 { bottom: 92%; background: var(--c-star); width: 6px; height: 6px; }
.footer { .footer {
display: flex; display: flex;

View File

@ -37,7 +37,7 @@ describe('progress', () => {
it('limits top5 to five entries', () => { it('limits top5 to five entries', () => {
let p = emptyProgress(); let p = emptyProgress();
for (let i = 0; i < 10; i++) p = recordRun(p, 6, ((i % 5) + 1) as 1 | 2 | 3 | 4 | 5); for (let i = 0; i < 10; i++) p = recordRun(p, 6, ((i % 4) + 1) as 1 | 2 | 3 | 4);
expect(p.perTarget[6].top5.length).toBe(5); expect(p.perTarget[6].top5.length).toBe(5);
expect(p.perTarget[6].runs).toBe(10); expect(p.perTarget[6].runs).toBe(10);
}); });

View File

@ -1,13 +1,11 @@
// Schwellen aus BRAINSTORM.md: 2/4/7/10 richtige Aufgaben pro Stufenaufstieg. // Schwellen aus BRAINSTORM.md: 2/4/7/10 richtige Aufgaben pro Stufenaufstieg.
// Während der Runde zeigt die Rakete Stufen 0..4: // Stufen 0..4: 0 Boden, 1 Luftballon, 2 Wolken, 3 Mond, 4 Sterne.
// 0 Boden, 1 Luftballon, 2 Wolken, 3 Mond, 4 Sterne. // Die Sterne sind die höchste Stufe und zugleich der Sieg (isWin).
// Stufe 5 (Regenbogenland) ist die Win-Celebration — wird nicht durch stageFor zurückgegeben,
// sondern durch isWin() signalisiert und im ResultScreen dargestellt.
export const STAGE_THRESHOLDS = [2, 4, 7, 10] as const; export const STAGE_THRESHOLDS = [2, 4, 7, 10] as const;
export const TOTAL_TO_WIN = 10; export const TOTAL_TO_WIN = 10;
export type Stage = 0 | 1 | 2 | 3 | 4 | 5; export type Stage = 0 | 1 | 2 | 3 | 4;
export const STAGE_NAMES: Record<Stage, string> = { export const STAGE_NAMES: Record<Stage, string> = {
0: 'boden', 0: 'boden',
@ -15,7 +13,6 @@ export const STAGE_NAMES: Record<Stage, string> = {
2: 'wolken', 2: 'wolken',
3: 'mond', 3: 'mond',
4: 'sterne', 4: 'sterne',
5: 'regenbogenland',
}; };
export function stageFor(correct: number): Stage { export function stageFor(correct: number): Stage {

View File

@ -10,6 +10,7 @@
import Timer from '../components/game/Timer.svelte'; import Timer from '../components/game/Timer.svelte';
import BurstFx from '../components/game/BurstFx.svelte'; import BurstFx from '../components/game/BurstFx.svelte';
import IconButton from '../components/shared/IconButton.svelte'; import IconButton from '../components/shared/IconButton.svelte';
import Star from '../components/svg/Star.svelte';
type Props = { target: Target }; type Props = { target: Target };
let { target }: Props = $props(); let { target }: Props = $props();
@ -84,7 +85,7 @@
/> />
{:else if $game.status === 'won'} {:else if $game.status === 'won'}
<div class="end-message"> <div class="end-message">
<span class="big">🌈</span> <span class="big"><Star size={200} /></span>
</div> </div>
{/if} {/if}
</main> </main>