feat: improve guesses collapse timing, win screen CTA, and progress page polish

- GuessesTable now accepts a `minimized` prop instead of deriving collapse from `isWon`, giving the parent control over timing
- Delay collapsing guesses grid until win animations complete (1800ms), skipped for already-completed puzzles
- Replace plain progress link on win screen with a styled green button matching other CTAs
- Progress page: remove redundant subtitle and nav button from header, add book status legend, add axis labels to guess history chart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
George Powell
2026-03-22 00:47:02 -04:00
parent 3eb3a968dc
commit 45d33b6bad
4 changed files with 104 additions and 19 deletions

View File

@@ -5,13 +5,17 @@
let { let {
guesses, guesses,
correctBookId, correctBookId,
isWon = false, minimized = false,
}: { guesses: Guess[]; correctBookId: string; isWon?: boolean } = $props(); }: { guesses: Guess[]; correctBookId: string; minimized?: boolean } = $props();
let hasGuesses = $derived(guesses.length > 0); let hasGuesses = $derived(guesses.length > 0);
let showMinimized = $derived(isWon && guesses.length > 3); let showMinimized = $derived(minimized);
let expanded = $state(false); let expanded = $state(false);
$effect(() => {
if (!minimized) expanded = false;
});
function getBoxColor(isCorrect: boolean, isAdjacent?: boolean): string { function getBoxColor(isCorrect: boolean, isAdjacent?: boolean): string {
if (isCorrect) return "bg-green-500 border-green-600"; if (isCorrect) return "bg-green-500 border-green-600";
if (isAdjacent) return "bg-yellow-500 border-yellow-600"; if (isAdjacent) return "bg-yellow-500 border-yellow-600";

View File

@@ -330,12 +330,9 @@
{/if} {/if}
{#if isLoggedIn} {#if isLoggedIn}
<a <div class="signin-prompt">
href="/progress" <a href="/progress" class="progress-btn"> 📈 See your progress </a>
class="text-sm text-center text-gray-500 dark:text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400 transition-colors" </div>
>
View your progress →
</a>
{:else} {:else}
<div class="signin-prompt"> <div class="signin-prompt">
<p class="signin-text"> <p class="signin-text">
@@ -712,4 +709,43 @@
height: 1.1rem; height: 1.1rem;
flex-shrink: 0; flex-shrink: 0;
} }
.progress-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.6rem 1rem;
width: 100%;
margin-bottom: 0.6rem;
background: #059669;
color: #fff;
border-radius: 0.5rem;
font-size: 0.95rem;
font-weight: 600;
text-decoration: none;
transition:
background 150ms ease,
transform 80ms ease;
}
.progress-btn:hover {
background: #047857;
transform: translateY(-1px);
}
.progress-btn:active {
background: #065f46;
transform: scale(0.98);
}
@media (prefers-color-scheme: dark) {
.progress-btn {
background: #10b981;
color: #fff;
}
.progress-btn:hover {
background: #059669;
}
.progress-btn:active {
background: #047857;
}
}
</style> </style>

View File

@@ -53,6 +53,7 @@
let statsData = $state<StatsData | null>(null); let statsData = $state<StatsData | null>(null);
let streak = $state(0); let streak = $state(0);
let streakPercentile = $state<number | null>(null); let streakPercentile = $state<number | null>(null);
let guessesMinimized = $state(false);
const persistence = createGamePersistence( const persistence = createGamePersistence(
() => dailyVerse.date, () => dailyVerse.date,
@@ -204,6 +205,23 @@
} }
}); });
// Delay collapsing the guesses grid until animations complete (mirrors showWinScreen delay)
$effect(() => {
if (!isWon || persistence.guesses.length <= 3) {
guessesMinimized = false;
return;
}
if (persistence.isWinAlreadyTracked()) {
guessesMinimized = true;
} else {
const animationDelay = 1800;
const timeoutId = setTimeout(() => {
guessesMinimized = true;
}, animationDelay);
return () => clearTimeout(timeoutId);
}
});
// Track win analytics // Track win analytics
$effect(() => { $effect(() => {
if (!browser || !isWon) return; if (!browser || !isWon) return;
@@ -333,7 +351,7 @@
<GuessesTable <GuessesTable
guesses={persistence.guesses} guesses={persistence.guesses}
{correctBookId} {correctBookId}
{isWon} minimized={guessesMinimized}
/> />
</div> </div>

View File

@@ -186,16 +186,11 @@
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
<!-- Header --> <!-- Header -->
<div class="text-center mb-6 md:mb-8"> <div class="text-center mb-6 md:mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-gray-100 mb-2"> <h1 class="text-3xl md:text-4xl font-bold text-gray-100 mb-4">
Your Progress Your Progress
</h1> </h1>
<p class="text-sm md:text-base text-gray-300 mb-4">
Your Bible knowledge journey <a href="/" class="p-2 px-20 w-full items-center text-gray-300">
</p>
<a
href="/"
class="inline-flex items-center px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-sm font-medium shadow-md"
>
&larr; Back to Game &larr; Back to Game
</a> </a>
</div> </div>
@@ -360,6 +355,16 @@
</div> </div>
{/each} {/each}
</div> </div>
<p class="text-xs text-gray-500 mt-3 leading-relaxed">
<span class="text-blue-400 font-medium">Explored</span>
— played at least once<br />
<span class="text-emerald-400 font-medium"
>Mastered</span
>
— avg &le; 3 guesses over 2+ plays<br />
<span class="text-amber-400 font-medium">Perfect</span>
mastered and guessed in 1 at least once
</p>
</Container> </Container>
</div> </div>
@@ -382,7 +387,7 @@
</span> </span>
</div> </div>
<svg <svg
viewBox="0 0 400 120" viewBox="0 0 400 135"
class="w-full" class="w-full"
aria-hidden="true" aria-hidden="true"
> >
@@ -406,6 +411,13 @@
/> />
</linearGradient> </linearGradient>
</defs> </defs>
<!-- Y-axis label -->
<text
transform="translate(8, 60) rotate(-90)"
text-anchor="middle"
font-size="8"
fill="#9ca3af"
>Guesses</text>
<!-- Fill polygon --> <!-- Fill polygon -->
<polygon <polygon
points="{polylinePoints} {svgX( points="{polylinePoints} {svgX(
@@ -454,12 +466,27 @@
> >
{chartPoints[chartPoints.length - 1].label} {chartPoints[chartPoints.length - 1].label}
</text> </text>
<!-- X-axis title -->
<text
x="200"
y="132"
font-size="8"
fill="#9ca3af"
text-anchor="middle"
>Date</text>
</svg> </svg>
{#if chartImproving} {#if chartImproving}
<p class="text-xs text-emerald-400 mt-1"> <p class="text-xs text-emerald-400 mt-1">
You're getting better! You're getting better!
</p> </p>
{/if} {/if}
<p
class="text-xs text-gray-500 mt-2 leading-relaxed"
>
Each point is your average guesses over a
rolling window of games. A downward trend means
you're improving.
</p>
</div> </div>
</Container> </Container>
</div> </div>