Compare commits

..

2 Commits

Author SHA1 Message Date
bfc6d76dfe feat: add win count display for today's character in the UI
All checks were successful
Build Docker Image / build (push) Successful in 1m24s
2026-03-03 19:13:45 +01:00
4ee7445b68 feat: refactor WinPanel to use selectedCharacter prop for improved clarity and consistency 2026-03-03 19:11:08 +01:00
4 changed files with 102 additions and 136 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
export let dailyCharacter: any;
export let selectedCharacter: any;
export let selectedCharacters: any[];
export let isGeckoMoriaWin: boolean = false;
@@ -49,21 +49,21 @@
<p class="text-sm text-slate-400">Vous avez succombé à l'ombre en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
<p class="text-xs text-slate-300 mt-1">{attemptMessage}</p>
<div class="mt-3">
{#if dailyCharacter.pictureUrl}
{#if selectedCharacter.pictureUrl}
<a
href={"https://onepiece.fandom.com/fr/wiki/" + dailyCharacter.url}
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
target="_blank"
rel="noopener noreferrer"
class="inline-block"
>
<img
src={dailyCharacter.pictureUrl}
alt={dailyCharacter.name}
src={selectedCharacter.pictureUrl}
alt={selectedCharacter.name}
class="w-20 h-20 mx-auto rounded-full border-2 border-slate-600 shadow-lg object-cover hover:border-slate-500 transition-colors cursor-pointer opacity-80"
/>
</a>
{/if}
<p class="mt-2 text-lg font-bold text-slate-200">{dailyCharacter.name}</p>
<p class="mt-2 text-lg font-bold text-slate-200">{selectedCharacter.name}</p>
</div>
</div>
</div>
@@ -75,21 +75,21 @@
<p class="text-sm text-emerald-300">Vous avez trouvé le personnage en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
<p class="text-xs text-emerald-200 mt-1">{attemptMessage}</p>
<div class="mt-3">
{#if dailyCharacter.pictureUrl}
{#if selectedCharacter.pictureUrl}
<a
href={"https://onepiece.fandom.com/fr/wiki/" + dailyCharacter.url}
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
target="_blank"
rel="noopener noreferrer"
class="inline-block"
>
<img
src={dailyCharacter.pictureUrl}
alt={dailyCharacter.name}
src={selectedCharacter.pictureUrl}
alt={selectedCharacter.name}
class="w-20 h-20 mx-auto rounded-full border-2 border-emerald-400 shadow-lg object-cover hover:border-emerald-300 transition-colors cursor-pointer"
/>
</a>
{/if}
<p class="mt-2 text-lg font-bold text-white">{dailyCharacter.name}</p>
<p class="mt-2 text-lg font-bold text-white">{selectedCharacter.name}</p>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
import { error } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { config } from '$lib/server/db/schema';
import { getDailyModeCharacters, getOrCreateTodayCharacter, getYesterdayCharacter } from '$lib/server/daily-character';
import { getDailyModeCharacters, getOrCreateTodayCharacter, getYesterdayCharacter, getTodayCharacterWinsCount } from '$lib/server/daily-character';
import { like } from 'drizzle-orm';
export async function load() {
@@ -14,6 +14,9 @@ export async function load() {
const yesterdayCharacter = await getYesterdayCharacter(new Date(), characters);
// Load the win count for today
const winCount = await getTodayCharacterWinsCount(dailyCharacter.id);
// Load column visibility config
const columnConfig = await db
.select()
@@ -33,6 +36,7 @@ export async function load() {
characters,
dailyCharacter,
yesterdayCharacter,
columnVisibility
columnVisibility,
winCount
};
}

View File

@@ -203,9 +203,14 @@
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
<header class="flex flex-col items-start gap-6 w-full">
<div class="flex w-full items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
Personnage du jour
</h1>
<p class="mt-2 text-sm text-amber-300">
{data.winCount} {data.winCount > 1 ? 'personnes' : 'personne'} ont trouvé aujourd'hui 🎉
</p>
</div>
{#if hasWon}
<button
class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
@@ -233,7 +238,7 @@
{#if hasWon}
<WinPanel
{dailyCharacter}
selectedCharacter={dailyCharacter}
{selectedCharacters}
{isGeckoMoriaWin}
/>

View File

@@ -1,8 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
import { formatBounty } from '$lib';
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
import WinPanel from '$lib/components/WinPanel.svelte';
import HintsPanel from '$lib/components/HintsPanel.svelte';
export let data;
@@ -29,6 +30,7 @@
let showOriginUnlock = false;
let showFruitUnlock = false;
let showAffiliationUnlock = false;
let isGeckoMoriaWin = false;
// Load from localStorage on mount
onMount(() => {
@@ -104,6 +106,11 @@
$: characters = data.characters || [];
$: hasWon = currentCharacter && selectedCharacters.some(char => char.id === currentCharacter.id);
$: if (hasWon && currentCharacter?.id === 'gecko_moria_gecko_moria') {
isGeckoMoriaWin = true;
} else if (!hasWon) {
isGeckoMoriaWin = false;
}
// Hint availability tracking for unlock animations
$: isOriginAvailable = selectedCharacters.length >= 5;
@@ -193,11 +200,56 @@
.gecko-moria-effect {
animation: shadow-pulse 1.5s ease-in-out infinite;
}
@keyframes moria-chaos {
0% {
transform: rotate(0deg) scale(1);
filter: invert(0%) hue-rotate(0deg) blur(0px);
}
10% {
transform: rotate(15deg) scale(1.02);
filter: invert(30%) hue-rotate(45deg) blur(2px);
}
20% {
transform: rotate(-10deg) scale(0.98);
filter: invert(60%) hue-rotate(90deg) blur(1px);
}
30% {
transform: rotate(25deg) scale(1.05);
filter: invert(100%) hue-rotate(180deg) blur(3px);
}
40% {
transform: rotate(-20deg) scale(0.95);
filter: invert(80%) hue-rotate(270deg) blur(2px);
}
50% {
transform: rotate(30deg) scale(1.08);
filter: invert(100%) hue-rotate(0deg) blur(4px);
}
60% {
transform: rotate(-25deg) scale(0.92);
filter: invert(70%) hue-rotate(90deg) blur(2px);
}
70% {
transform: rotate(20deg) scale(1.03);
filter: invert(50%) hue-rotate(180deg) blur(3px);
}
80% {
transform: rotate(-15deg) scale(1.01);
filter: invert(80%) hue-rotate(270deg) blur(1px);
}
100% {
transform: rotate(360deg) scale(1);
filter: invert(0%) hue-rotate(360deg) blur(0px);
}
}
.moria-screen-chaos {
animation: moria-chaos 4s ease-in-out;
}
</style>
</svelte:head>
<main
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100"
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin ? 'moria-screen-chaos' : ''}"
>
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
<div
@@ -231,125 +283,30 @@
<section class="mt-10 grid gap-6">
{#if currentCharacter}
{#if hasWon}
<div
class="rounded-3xl border border-emerald-500/50 bg-emerald-500/10 p-4 shadow-[0_24px_60px_rgba(16,185,129,0.3)] backdrop-blur"
>
<div class="text-center">
<div class="text-3xl mb-2">🎉</div>
<h2 class="text-xl font-bold text-emerald-400 mb-1">Bien joué !</h2>
<p class="text-sm text-emerald-300">
Vous avez trouvé le personnage en {selectedCharacters.length}
{selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !
</p>
<div class="mt-3">
{#if currentCharacter.pictureUrl}
<a
href={'https://onepiece.fandom.com/fr/wiki/' + currentCharacter.url}
target="_blank"
rel="noopener noreferrer"
class="inline-block"
>
<img
src={currentCharacter.pictureUrl}
alt={currentCharacter.name}
class="w-20 h-20 mx-auto rounded-full border-2 border-emerald-400 shadow-lg object-cover hover:border-emerald-300 transition-colors cursor-pointer"
<div>
<WinPanel
selectedCharacter={currentCharacter}
{selectedCharacters}
{isGeckoMoriaWin}
/>
</a>
{/if}
<p class="mt-2 text-lg font-bold text-white">{currentCharacter.name}</p>
</div>
<button
type="button"
onclick={nextCharacter}
class="mt-4 rounded-full bg-emerald-500 px-6 py-2 text-sm font-semibold text-white transition hover:bg-emerald-600"
class="mt-4 w-full rounded-full bg-emerald-500 px-6 py-2 text-sm font-semibold text-white transition hover:bg-emerald-600"
>
Recommencer
</button>
</div>
</div>
{:else}
<div
class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
>
<div class="grid gap-3 sm:grid-cols-3">
<button
type="button"
class="rounded-2xl border border-white/10 px-3 py-3 flex flex-col items-center justify-center cursor-pointer hover:bg-slate-900/80 transition-colors {isOriginAvailable
? 'bg-slate-950/60'
: 'bg-slate-950/30 opacity-50 cursor-not-allowed hover:bg-slate-950/30'} {showOriginUnlock
? 'hint-unlocking'
: ''}"
disabled={!isOriginAvailable}
onclick={() => (showOriginUnlock = !showOriginUnlock)}
>
<p class="text-sm font-medium text-amber-100">Origine</p>
{#if showOriginUnlock}
<p class="mt-2 text-xs text-white font-semibold">
{currentCharacter.origin || 'Inconnue'}
</p>
{:else if Math.max(0, 5 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">
{Math.max(0, 5 - selectedCharacters.length)} essais avant déblocage
</p>
{:else}
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
{#if selectedCharacters.length > 0}
<HintsPanel
dailyCharacter={currentCharacter}
{selectedCharacters}
{showOriginUnlock}
{showFruitUnlock}
{showAffiliationUnlock}
/>
{/if}
</button>
<button
type="button"
class="rounded-2xl border border-white/10 px-3 py-3 flex flex-col items-center justify-center cursor-pointer hover:bg-slate-900/80 transition-colors {isFruitAvailable
? 'bg-slate-950/60'
: 'bg-slate-950/30 opacity-50 cursor-not-allowed hover:bg-slate-950/30'} {showFruitUnlock
? 'hint-unlocking'
: ''}"
disabled={!isFruitAvailable}
onclick={() => (showFruitUnlock = !showFruitUnlock)}
>
<p class="text-sm font-medium text-amber-100">Fruit du démon</p>
{#if showFruitUnlock}
<p class="mt-2 text-xs text-white font-semibold">
{currentCharacter.devilFruitName || 'Aucun'}
</p>
{:else if Math.max(0, 10 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">
{Math.max(0, 10 - selectedCharacters.length)} essais avant déblocage
</p>
{:else}
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
{/if}
</button>
<button
type="button"
class="rounded-2xl border border-white/10 px-3 py-3 flex flex-col items-center justify-center cursor-pointer hover:bg-slate-900/80 transition-colors {isAffiliationAvailable
? 'bg-slate-950/60'
: 'bg-slate-950/30 opacity-50 cursor-not-allowed hover:bg-slate-950/30'} {showAffiliationUnlock
? 'hint-unlocking'
: ''}"
disabled={!isAffiliationAvailable}
onclick={() => (showAffiliationUnlock = !showAffiliationUnlock)}
>
<p class="text-sm font-medium text-amber-100">Affiliation</p>
{#if showAffiliationUnlock}
{@const affiliations = typeof currentCharacter.affiliations === 'string'
? currentCharacter.affiliations.includes('[')
? JSON.parse(currentCharacter.affiliations)
: currentCharacter.affiliations
.split(',')
.map((a: string) => a.trim())
: currentCharacter.affiliations}
<p class="mt-2 text-xs text-white font-semibold">
{Array.isArray(affiliations) ? affiliations[0] : affiliations || 'Inconnue'}
</p>
{:else if Math.max(0, 15 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">
{Math.max(0, 15 - selectedCharacters.length)} essais avant déblocage
</p>
{:else}
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
{/if}
</button>
</div>
</div>
<CharacterSearchInput
{characters}
{selectedCharacters}