Compare commits

...
2 Commits
Author SHA1 Message Date
Whidix 42c4c7e20c feat: add DailyFruitHint component and integrate it into the daily fruit page
Build Docker Image / build (push) Successful in 1m32s
2026-06-23 19:57:24 +02:00
Whidix 2d9cd43def feat: implement character selection and filtering functionality in infinite game mode 2026-06-23 19:51:44 +02:00
8 changed files with 80 additions and 13 deletions
+59
View File
@@ -0,0 +1,59 @@
<script lang="ts">
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
import type { DevilFruit } from '$lib/server/db/schema';
import { t } from '$lib/i18n';
interface $$Props {
dailyFruit: DevilFruit | null;
selectedCharacters?: CharacterWithRelations[];
}
export let dailyFruit: $$Props['dailyFruit'];
export let selectedCharacters: NonNullable<$$Props['selectedCharacters']> = [];
let showFruitTypeHint = false;
$: isFruitTypeAvailable = selectedCharacters.length >= 3;
$: guessesBeforeUnlock = Math.max(0, 3 - selectedCharacters.length);
$: if (!isFruitTypeAvailable) {
showFruitTypeHint = false;
}
</script>
<svelte:head>
<style>
@keyframes hint-unlock {
0% {
box-shadow: 0 0 0 rgba(251, 146, 60, 0);
}
50% {
box-shadow: 0 0 20px rgba(251, 146, 60, 0.8);
}
100% {
box-shadow: 0 0 0 rgba(251, 146, 60, 0);
}
}
.hint-unlocking {
animation: hint-unlock 0.6s ease-out;
}
</style>
</svelte:head>
<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">
<button
type="button"
class="flex w-full flex-col items-center justify-center rounded-2xl border border-white/10 px-3 py-4 transition-colors hover:bg-slate-900/80 {isFruitTypeAvailable ? 'bg-slate-950/60' : 'cursor-not-allowed bg-slate-950/30 opacity-50 hover:bg-slate-950/30'}"
disabled={!isFruitTypeAvailable}
onclick={() => (showFruitTypeHint = !showFruitTypeHint)}
>
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.fruitKind}</p>
{#if showFruitTypeHint}
<p class="mt-2 text-xs font-semibold text-white">{dailyFruit?.type || $t.game.components.hints.unknown}</p>
{:else if guessesBeforeUnlock > 0}
<p class="mt-2 text-xs text-slate-400">{guessesBeforeUnlock} {$t.game.components.hints.beforeUnlock}</p>
{:else}
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
{/if}
</button>
</div>
+9 -9
View File
@@ -13,9 +13,9 @@
let showHintAffiliation = false; let showHintAffiliation = false;
// Hint availability - indices are available after a certain number of guesses // Hint availability - indices are available after a certain number of guesses
$: isOriginAvailable = selectedCharacters.length >= 5; $: isOriginAvailable = selectedCharacters.length >= 3;
$: isFruitAvailable = selectedCharacters.length >= 10; $: isFruitAvailable = selectedCharacters.length >= 6;
$: isAffiliationAvailable = selectedCharacters.length >= 15; $: isAffiliationAvailable = selectedCharacters.length >= 9;
$: isFrench = $language === 'fr'; $: isFrench = $language === 'fr';
function getDisplayOrigin(character: CharacterWithRelations): string | null { function getDisplayOrigin(character: CharacterWithRelations): string | null {
@@ -57,8 +57,8 @@
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.origin}</p> <p class="text-sm font-medium text-amber-100">{$t.game.components.hints.origin}</p>
{#if showHintOrigin} {#if showHintOrigin}
<p class="mt-2 text-xs text-white font-semibold">{getDisplayOrigin(dailyCharacter) || $t.game.components.hints.unknown}</p> <p class="mt-2 text-xs text-white font-semibold">{getDisplayOrigin(dailyCharacter) || $t.game.components.hints.unknown}</p>
{:else if Math.max(0, 5 - selectedCharacters.length) > 0} {:else if Math.max(0, 3 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 5 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p> <p class="mt-2 text-xs text-slate-400">{Math.max(0, 3 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
{:else} {:else}
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p> <p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
{/if} {/if}
@@ -72,8 +72,8 @@
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.devilFruit}</p> <p class="text-sm font-medium text-amber-100">{$t.game.components.hints.devilFruit}</p>
{#if showHintFruit} {#if showHintFruit}
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || $t.game.components.hints.none}</p> <p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || $t.game.components.hints.none}</p>
{:else if Math.max(0, 10 - selectedCharacters.length) > 0} {:else if Math.max(0, 6 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 10 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p> <p class="mt-2 text-xs text-slate-400">{Math.max(0, 6 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
{:else} {:else}
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p> <p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
{/if} {/if}
@@ -87,8 +87,8 @@
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.affiliation}</p> <p class="text-sm font-medium text-amber-100">{$t.game.components.hints.affiliation}</p>
{#if showHintAffiliation} {#if showHintAffiliation}
<p class="mt-2 text-xs text-white font-semibold">{isFrench && dailyCharacter.frAffiliation ? dailyCharacter.frAffiliation : dailyCharacter.affiliation || $t.game.components.hints.unknown}</p> <p class="mt-2 text-xs text-white font-semibold">{isFrench && dailyCharacter.frAffiliation ? dailyCharacter.frAffiliation : dailyCharacter.affiliation || $t.game.components.hints.unknown}</p>
{:else if Math.max(0, 15 - selectedCharacters.length) > 0} {:else if Math.max(0, 9 - selectedCharacters.length) > 0}
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 15 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p> <p class="mt-2 text-xs text-slate-400">{Math.max(0, 9 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
{:else} {:else}
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p> <p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
{/if} {/if}
+1
View File
@@ -160,6 +160,7 @@
"hints": { "hints": {
"origin": "Origin", "origin": "Origin",
"devilFruit": "Devil fruit", "devilFruit": "Devil fruit",
"fruitKind": "Fruit type",
"affiliation": "Affiliation", "affiliation": "Affiliation",
"unknown": "Unknown", "unknown": "Unknown",
"none": "None", "none": "None",
+1
View File
@@ -160,6 +160,7 @@
"hints": { "hints": {
"origin": "Origine", "origin": "Origine",
"devilFruit": "Fruit du demon", "devilFruit": "Fruit du demon",
"fruitKind": "Type du fruit",
"affiliation": "Affiliation", "affiliation": "Affiliation",
"unknown": "Inconnue", "unknown": "Inconnue",
"none": "Aucun", "none": "Aucun",
+1 -1
View File
@@ -93,7 +93,7 @@
<p class="mt-3 text-lg font-semibold text-white">{$t.game.home.infiniteSubtitle}</p> <p class="mt-3 text-lg font-semibold text-white">{$t.game.home.infiniteSubtitle}</p>
<p class="mt-2 text-sm text-slate-200">{$t.game.home.infiniteDescription}</p> <p class="mt-2 text-sm text-slate-200">{$t.game.home.infiniteDescription}</p>
<a <a
href={resolve("/infinite")} href={resolve("/infinite/character")}
class="mt-5 inline-flex w-full items-center justify-center 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" class="mt-5 inline-flex w-full items-center justify-center 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"
> >
{$t.game.home.infiniteCta} {$t.game.home.infiniteCta}
+9 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount } from 'svelte'; import { onMount } from 'svelte';
import YesterdayCharacter from '$lib/components/YesterdayCharacter.svelte'; import YesterdayCharacter from '$lib/components/YesterdayCharacter.svelte';
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte'; import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
import DailyFruitHint from '$lib/components/DailyFruitHint.svelte';
import SimpleGuessHistory from '$lib/components/SimpleGuessHistory.svelte'; import SimpleGuessHistory from '$lib/components/SimpleGuessHistory.svelte';
import WinPanel from '$lib/components/WinPanel.svelte'; import WinPanel from '$lib/components/WinPanel.svelte';
import type { CharacterWithRelations } from '$lib/server/daily-character.js'; import type { CharacterWithRelations } from '$lib/server/daily-character.js';
@@ -43,8 +44,6 @@
isLoaded = true; isLoaded = true;
}); });
onDestroy(() => {});
$: if (isLoaded && selectedCharacters) { $: if (isLoaded && selectedCharacters) {
const ids = selectedCharacters.map(char => char.id); const ids = selectedCharacters.map(char => char.id);
localStorage.setItem('dailyFruitHistory', JSON.stringify(ids)); localStorage.setItem('dailyFruitHistory', JSON.stringify(ids));
@@ -121,6 +120,13 @@
<p class="mt-2 text-center text-xl font-bold text-amber-50 sm:text-2xl">{dailyFruit?.name}</p> <p class="mt-2 text-center text-xl font-bold text-amber-50 sm:text-2xl">{dailyFruit?.name}</p>
</div> </div>
<section class="mt-6">
<DailyFruitHint
dailyFruit={dailyFruit}
{selectedCharacters}
/>
</section>
<section class="mt-6 grid gap-6"> <section class="mt-6 grid gap-6">
{#if hasWon} {#if hasWon}
<WinPanel <WinPanel