Files
ClickerZ/Frontend/src/lib/components/CosmeticsPanel.svelte
Tetardtek 67931eeadb
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 21s
fix: refactor store to singleton class pattern (s.subscribe fix)
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.
2026-03-28 20:39:21 +01:00

55 lines
2.0 KiB
Svelte

<script lang="ts">
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { game } from '$lib/stores/game.svelte';
import { COSMETICS, type CosmeticSlot } from '$lib/core/cosmetics';
import CollapsiblePanel from './CollapsiblePanel.svelte';
const SLOT_LABELS: Record<CosmeticSlot, string> = {
hat: 'Tete', eyes: 'Yeux', body: 'Corps', tail: 'Queue', accessory: 'Aura',
};
const SLOT_ICONS: Record<CosmeticSlot, string> = {
hat: '👑', eyes: '👁', body: '🛡', tail: '🦎', accessory: '✨',
};
const SLOT_ORDER: CosmeticSlot[] = ['hat', 'eyes', 'body', 'tail', 'accessory'];
let inventory = $derived(game.state.cosmeticInventory);
let equipped = $derived(game.state.cosmeticEquipped);
let ownedCosmetics = $derived(COSMETICS.filter((c) => inventory.includes(c.id)));
</script>
{#if inventory.length > 0}
<CollapsiblePanel
title="Cosmetiques"
badge="{inventory.length}/{COSMETICS.length}"
defaultOpen={false}
>
{#each SLOT_ORDER as slot, si}
{@const slotCosmetics = ownedCosmetics.filter((c) => c.slot === slot)}
{#if slotCosmetics.length > 0}
<div
class="flex flex-col gap-0.5"
in:fly={{ y: 15, delay: si * 60, duration: 250, easing: quintOut }}
>
<span class="gp-zone-label">{SLOT_ICONS[slot]} {SLOT_LABELS[slot]}</span>
{#each slotCosmetics as cos}
{@const isEquipped = equipped[slot] === cos.id}
<div class="gp-row {isEquipped ? 'gp-row--unlocked' : 'gp-row--active'}">
<div class="flex flex-col min-w-0">
<span class="gp-value text-[0.7rem]!">{cos.name}</span>
<span class="gp-label">{cos.description}</span>
</div>
<button
onclick={() => isEquipped ? game.unequipCosmetic(slot) : game.equipCosmetic(cos.id)}
class="gp-btn {isEquipped ? 'gp-btn--disabled' : 'gp-btn--buy'}"
>
{isEquipped ? 'Retirer' : 'Equiper'}
</button>
</div>
{/each}
</div>
{/if}
{/each}
</CollapsiblePanel>
{/if}