All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 21s
Exported $state proxies were confused with Svelte stores by SvelteKit
runtime, causing "s.subscribe is not a function" on /jeu.
Fix: encapsulate all $state fields in a Game class, export singleton.
Components import { game } and access game.state, game.click(), etc.
Class fields are proper $state — no raw proxy exported.
163 lines
5.9 KiB
Svelte
163 lines
5.9 KiB
Svelte
<script lang="ts">
|
|
import { fly, scale, fade } from 'svelte/transition';
|
|
import { quintOut, backOut } from 'svelte/easing';
|
|
import { game } from '$lib/stores/game.svelte';
|
|
import { computePrestigeDna, getPrestigeDnaBonus, getPrestigeThreshold } from '$lib/core/economy';
|
|
import { formatNumber } from '$lib/utils/formatNumber';
|
|
|
|
function formatDuration(ms: number): string {
|
|
const totalSeconds = Math.floor(ms / 1000);
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
return `${seconds}s`;
|
|
}
|
|
|
|
let baseDna = $derived(computePrestigeDna(game.game.state.lifetimeTadpoles, game.game.state.prestigeCount));
|
|
let dnaBonus = $derived(getPrestigeDnaBonus(game.state.evolutionTree));
|
|
let dnaPreview = $derived(Math.floor(baseDna * (1 + dnaBonus)));
|
|
let threshold = $derived(getPrestigeThreshold(game.state));
|
|
let canPrestige = $derived(game.game.state.lifetimeTadpoles >= threshold);
|
|
let runDuration = $derived(Date.now() - game.state.runStats.startedAt);
|
|
let bestRun = $derived(game.state.runStats.bestRun);
|
|
let isBestAdn = $derived(!bestRun || dnaPreview > bestRun.adn);
|
|
let isBestTadpoles = $derived(!bestRun || game.game.state.lifetimeTadpoles > bestRun.tadpoles);
|
|
|
|
function handlePrestige() {
|
|
if (canPrestige) game.prestige();
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onkeydown={(e) => { if (e.key === 'Escape' && game.showPrestigeScreen) game.game.closePrestige(); }} />
|
|
|
|
{#if game.showPrestigeScreen}
|
|
<!-- Backdrop -->
|
|
<div
|
|
class="fixed inset-0 z-50 flex items-center justify-center"
|
|
style="background: rgba(0,0,0,0.85); backdrop-filter: blur(8px);"
|
|
transition:fade={{ duration: 300 }}
|
|
>
|
|
<!-- Modal card -->
|
|
<div
|
|
class="gp max-w-md w-full mx-4"
|
|
in:scale={{ duration: 400, start: 0.85, easing: backOut }}
|
|
out:scale={{ duration: 200, start: 0.95 }}
|
|
>
|
|
<!-- Header with generation number -->
|
|
<div class="text-center" in:fly={{ y: -20, delay: 100, duration: 400, easing: quintOut }}>
|
|
<span class="gp-title text-lg!">Nouvelle Generation</span>
|
|
<p class="gp-label mt-1">Generation #{game.state.prestigeCount + 1}</p>
|
|
</div>
|
|
|
|
<div class="gp-sep"></div>
|
|
|
|
<!-- ADN Preview — the hero number -->
|
|
<div
|
|
class="flex flex-col items-center gap-1 py-3"
|
|
in:scale={{ delay: 200, duration: 500, start: 0.5, easing: backOut }}
|
|
>
|
|
<span class="gp-label">ADN Ancestral</span>
|
|
<span
|
|
class="text-4xl font-extrabold"
|
|
style="color: #a78bfa; font-family: var(--font); text-shadow: 0 0 20px rgba(167,139,250,0.4);"
|
|
>
|
|
+{formatNumber(dnaPreview)}
|
|
</span>
|
|
{#if dnaBonus > 0}
|
|
<span class="gp-label">(base {formatNumber(baseDna)} + {Math.round(dnaBonus * 100)}% arbre)</span>
|
|
{/if}
|
|
<span class="gp-label mt-1">Total apres : {formatNumber(game.state.ancestralDna + dnaPreview)} ADN</span>
|
|
</div>
|
|
|
|
<div class="gp-sep"></div>
|
|
|
|
<!-- Run Stats -->
|
|
<div class="flex flex-col gap-2" in:fly={{ y: 20, delay: 300, duration: 400, easing: quintOut }}>
|
|
<span class="gp-zone-label">Stats de la run</span>
|
|
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">Duree</span>
|
|
<span class="gp-value">{formatDuration(runDuration)}</span>
|
|
</div>
|
|
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">Tetards produits</span>
|
|
<span class="gp-value {isBestTadpoles ? 'gp-accent-green' : ''}">
|
|
{formatNumber(game.state.lifetimeTadpoles)}
|
|
{#if isBestTadpoles && bestRun} ★{/if}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">ADN cette run</span>
|
|
<span class="gp-value {isBestAdn ? 'gp-accent-green' : ''}">
|
|
{formatNumber(dnaPreview)}
|
|
{#if isBestAdn && bestRun} ★{/if}
|
|
</span>
|
|
</div>
|
|
|
|
{#if bestRun}
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">Vitesse vs meilleure</span>
|
|
<span class="gp-value {runDuration < bestRun.duration ? 'gp-accent-green' : 'gp-accent-amber'}">
|
|
{#if runDuration < bestRun.duration}
|
|
{Math.round((1 - runDuration / bestRun.duration) * 100)}% plus rapide
|
|
{:else if runDuration > bestRun.duration}
|
|
{Math.round((runDuration / bestRun.duration - 1) * 100)}% plus lent
|
|
{:else}
|
|
identique
|
|
{/if}
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if bestRun}
|
|
<div class="gp-sep"></div>
|
|
<div class="flex flex-col gap-1" in:fly={{ y: 15, delay: 400, duration: 300, easing: quintOut }}>
|
|
<span class="gp-zone-label">Meilleure run</span>
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">Duree</span>
|
|
<span class="gp-value">{formatDuration(bestRun.duration)}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="gp-label">ADN</span>
|
|
<span class="gp-value gp-accent-purple">{formatNumber(bestRun.adn)}</span>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="gp-sep"></div>
|
|
|
|
<!-- Reset info -->
|
|
<div class="text-center" in:fade={{ delay: 350, duration: 300 }}>
|
|
<p class="gp-label">Tetards et generateurs remis a zero.</p>
|
|
<p class="gp-label">Arbre d'Evolution et cosmetiques conserves.</p>
|
|
<p class="gp-label mt-1 gp-accent-green">+1 reset d'arbre gratuit offert.</p>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex gap-2 mt-1" in:fly={{ y: 20, delay: 450, duration: 300, easing: quintOut }}>
|
|
<button
|
|
onclick={() => game.closePrestige()}
|
|
class="gp-btn flex-1 py-2.5! text-[0.8rem]!"
|
|
style="background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6);"
|
|
>
|
|
Annuler
|
|
</button>
|
|
{#if canPrestige}
|
|
<button onclick={handlePrestige} class="gp-btn gp-btn--prestige flex-1 py-2.5! text-[0.8rem]!">
|
|
Nouvelle Generation
|
|
</button>
|
|
{:else}
|
|
<button class="gp-btn gp-btn--disabled flex-1 py-2.5!" disabled>
|
|
{formatNumber(threshold - game.state.lifetimeTadpoles)} manquants
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|