Skip to content

Commit e17714b

Browse files
jsollycursoragent
andauthored
Unlock all slices and remove the placement test (#40)
* Remove placement test gate; unlock all capture slices - PieHome: drop gated/needsFundamentalsPlacement derived, remove locked prop from PieWheel, always show tagline/daily/shaky sections, remove lifecycleLocked per-slice logic (Locked badge, disabled Quiz me, 'Complete Basics to unlock', 'Test out of basics' / gaps block) - PieWheel: remove locked prop, sliceIsLocked, dimming logic; simplify sliceSummary, sliceFill, createOption, attachChart, wheelDescription - ShelfView: remove routeLocked block, unlockHeadingEl, unlockStatusId, locked focus branch; always show 'Ready to prove it?' quiz button; convert bind:this to {@Attach captureHeading} - ChapterView: same lock-block removal; always enable quiz button; convert bind:this to {@Attach captureHeading} - SummaryView: remove TESTOUT_PASS import, unlockedByMastery, entire testout UI branch; score ring + headline are now always the main path; convert bind:this to {@Attach captureResultHeading} - QuizView: simplify missed derived — drop game.mode !== 'testout' guard * Remove testout engine and always-open capture slices Drop placement test mode, fundamentals unlock gate, and routeLocked gating. Quizzes on any slice still clear questions and fill the pie. Legacy progress fields are ignored on load. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 233b443 commit e17714b

13 files changed

Lines changed: 133 additions & 459 deletions

src/components/app/ChapterView.svelte

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
import TermRichText from "./TermRichText.svelte";
1111
1212
let chapter = $derived(game.chapter);
13-
let headingEl: HTMLHeadingElement | null = $state(null);
14-
let unlockHeadingEl: HTMLHeadingElement | null = $state(null);
15-
let unlockStatusId = $derived(
16-
chapter ? `chapter-unlock-status-${chapter.id}` : "chapter-unlock-status",
17-
);
13+
let headingEl: HTMLHeadingElement | null = null;
1814
let nextChapter = $derived(
1915
chapter && game.chapterKind === "shelf-chapter"
2016
? nextChapterOnShelf(chapter.id)
@@ -54,17 +50,19 @@
5450
};
5551
});
5652
53+
function captureHeading(element: HTMLHeadingElement) {
54+
headingEl = element;
55+
return () => {
56+
if (headingEl === element) headingEl = null;
57+
};
58+
}
59+
5760
$effect(() => {
5861
const current = chapter;
5962
if (!current) return;
60-
const locked = game.routeLocked;
6163
void tick().then(() => {
6264
if (game.chapter !== current) return;
63-
if (locked) {
64-
unlockHeadingEl?.focus();
65-
} else {
66-
headingEl?.focus();
67-
}
65+
headingEl?.focus();
6866
});
6967
});
7068
@@ -108,7 +106,7 @@
108106
{chapter.subtitle}
109107
</p>
110108
<h1
111-
bind:this={headingEl}
109+
{@attach captureHeading}
112110
tabindex="-1"
113111
class="mt-1 text-2xl font-bold tracking-tight outline-none sm:text-3xl"
114112
>
@@ -119,34 +117,6 @@
119117
</p>
120118
</header>
121119

122-
{#if game.routeLocked}
123-
<div class="mb-6 rounded-2xl border-2 border-border bg-card p-5 sm:mb-8 sm:p-6">
124-
<h2
125-
bind:this={unlockHeadingEl}
126-
tabindex="-1"
127-
class="font-semibold outline-none focus-visible:ring-2 focus-visible:ring-ring sm:text-lg"
128-
>
129-
Quiz unlocks after Basics
130-
</h2>
131-
<p
132-
id={unlockStatusId}
133-
class="mt-2 text-sm leading-6 text-muted-foreground sm:text-base sm:leading-7"
134-
role="status"
135-
>
136-
You can read this chapter now. Lifecycle quizzes open after you clear Master the
137-
Basics (or test out).
138-
</p>
139-
<div class="mt-5 flex flex-col gap-2 sm:flex-row sm:flex-wrap">
140-
<Button size="lg" class="w-full sm:w-auto" href={learnShelfPath("fundamentals")}>
141-
Open Basics shelf
142-
</Button>
143-
<Button size="lg" variant="outline" class="w-full sm:w-auto" href="/">
144-
Back to home
145-
</Button>
146-
</div>
147-
</div>
148-
{/if}
149-
150120
{#if chapter.pieces.length > 1}
151121
<nav class="mb-6 sm:mb-8" aria-label="On this page">
152122
<p class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
@@ -207,7 +177,7 @@
207177
<blockquote
208178
class="mt-5 border-l-4 border-primary/40 bg-muted/40 px-4 py-3 text-sm leading-6 sm:text-[0.95rem] sm:leading-7"
209179
>
210-
<p class="italic text-foreground/95">{piece.quote.text}</p>
180+
<p class="italic text-foreground/95">"{piece.quote.text}"</p>
211181
<footer class="mt-2 not-italic">
212182
<a
213183
class="text-xs font-medium text-primary underline underline-offset-2 sm:text-sm"
@@ -274,8 +244,6 @@
274244
<Button
275245
size="lg"
276246
class="w-full sm:h-11 sm:min-w-[12rem] sm:flex-1 sm:text-base"
277-
disabled={game.routeLocked}
278-
aria-describedby={game.routeLocked ? unlockStatusId : undefined}
279247
onclick={() => game.runChapterQuizAction(chapter.quizCta.action)}
280248
>
281249
{chapter.quizCta.label}

src/components/app/PieHome.svelte

Lines changed: 37 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import { Badge } from "$lib/components/ui/badge";
1010
1111
let stats = $derived(game.allStats);
12-
let gated = $derived(game.needsFundamentalsPlacement);
1312
let confirmReset = $state(false);
1413
let confirmYesEl: HTMLElement | null = $state(null);
1514
let resetButtonEl: HTMLElement | null = $state(null);
@@ -47,11 +46,9 @@
4746
>
4847
Learn The FAR
4948
</h1>
50-
{#if !gated}
51-
<p class="text-xs text-muted-foreground sm:text-sm">
52-
A fun way to learn the FAR, one scenario at a time.
53-
</p>
54-
{/if}
49+
<p class="text-xs text-muted-foreground sm:text-sm">
50+
A fun way to learn the FAR, one scenario at a time.
51+
</p>
5552
</div>
5653
<div
5754
class="flex items-center gap-1.5 rounded-full border bg-card px-3 py-1.5 text-sm font-semibold sm:gap-2 sm:px-4 sm:py-2 sm:text-base"
@@ -64,48 +61,46 @@
6461
</header>
6562

6663
<div class="mt-4 sm:mt-6">
67-
<PieWheel {stats} hubPercent={game.masteryPercent} locked={gated} />
64+
<PieWheel {stats} hubPercent={game.masteryPercent} />
6865
</div>
6966

70-
{#if !gated}
71-
<div
72-
class={`mt-4 grid gap-2 sm:mt-6 sm:gap-3 ${game.shakyQuestions.length > 0 ? "sm:grid-cols-2" : ""}`}
67+
<div
68+
class={`mt-4 grid gap-2 sm:mt-6 sm:gap-3 ${game.shakyQuestions.length > 0 ? "sm:grid-cols-2" : ""}`}
69+
>
70+
<button
71+
type="button"
72+
disabled={game.dailyDoneToday}
73+
onclick={() => game.startDaily()}
74+
class="flex items-center gap-3 rounded-2xl border-2 p-4 text-left transition-all enabled:hover:border-primary/50 enabled:hover:bg-muted/40 disabled:opacity-60 sm:gap-4 sm:p-5 lg:p-6"
7375
>
76+
<span class="text-2xl sm:text-3xl" aria-hidden="true">{game.dailyDoneToday ? "" : "📰"}</span>
77+
<div class="min-w-0 flex-1">
78+
<p class="font-semibold leading-tight sm:text-lg">Daily challenge</p>
79+
<p class="text-xs text-muted-foreground sm:text-sm">
80+
{game.dailyDoneToday ? "Done today — back tomorrow" : "5 mixed questions, feeds your streak"}
81+
</p>
82+
</div>
83+
<span class="shrink-0 text-lg text-muted-foreground sm:text-xl" aria-hidden="true">›</span>
84+
</button>
85+
86+
{#if game.shakyQuestions.length > 0}
7487
<button
7588
type="button"
76-
disabled={game.dailyDoneToday}
77-
onclick={() => game.startDaily()}
78-
class="flex items-center gap-3 rounded-2xl border-2 p-4 text-left transition-all enabled:hover:border-primary/50 enabled:hover:bg-muted/40 disabled:opacity-60 sm:gap-4 sm:p-5 lg:p-6"
89+
onclick={() => game.startStudyMisses()}
90+
class="flex items-center gap-3 rounded-2xl border-2 p-4 text-left transition-all hover:border-primary/50 hover:bg-muted/40 sm:gap-4 sm:p-5 lg:p-6"
7991
>
80-
<span class="text-2xl sm:text-3xl" aria-hidden="true">{game.dailyDoneToday ? "" : "📰"}</span>
92+
<span class="text-2xl sm:text-3xl" aria-hidden="true">📖</span>
8193
<div class="min-w-0 flex-1">
82-
<p class="font-semibold leading-tight sm:text-lg">Daily challenge</p>
94+
<p class="font-semibold leading-tight sm:text-lg">Here's the missing picture</p>
8395
<p class="text-xs text-muted-foreground sm:text-sm">
84-
{game.dailyDoneToday ? "Done today — back tomorrow" : "5 mixed questions, feeds your streak"}
96+
{game.shakyQuestions.length} concept{game.shakyQuestions.length === 1 ? "" : "s"} to
97+
re-read — not a soft quiz
8598
</p>
8699
</div>
87100
<span class="shrink-0 text-lg text-muted-foreground sm:text-xl" aria-hidden="true">›</span>
88101
</button>
89-
90-
{#if game.shakyQuestions.length > 0}
91-
<button
92-
type="button"
93-
onclick={() => game.startStudyMisses()}
94-
class="flex items-center gap-3 rounded-2xl border-2 p-4 text-left transition-all hover:border-primary/50 hover:bg-muted/40 sm:gap-4 sm:p-5 lg:p-6"
95-
>
96-
<span class="text-2xl sm:text-3xl" aria-hidden="true">📖</span>
97-
<div class="min-w-0 flex-1">
98-
<p class="font-semibold leading-tight sm:text-lg">Here’s the missing picture</p>
99-
<p class="text-xs text-muted-foreground sm:text-sm">
100-
{game.shakyQuestions.length} concept{game.shakyQuestions.length === 1 ? "" : "s"} to
101-
re-read — not a soft quiz
102-
</p>
103-
</div>
104-
<span class="shrink-0 text-lg text-muted-foreground sm:text-xl" aria-hidden="true">›</span>
105-
</button>
106-
{/if}
107-
</div>
108-
{/if}
102+
{/if}
103+
</div>
109104

110105
{#if showGlossaryOnHome}
111106
<button
@@ -130,44 +125,25 @@
130125
</h2>
131126
<div class="flex flex-col gap-2 sm:gap-3">
132127
{#each stats as s (s.unit.id)}
133-
{@const lifecycleLocked = game.isUnitLocked(s.unit.id)}
134-
<div
135-
class={`rounded-2xl border-2 p-3 sm:p-4 lg:p-5 ${
136-
lifecycleLocked
137-
? "border-border/50 bg-muted/40"
138-
: "border-border bg-card"
139-
}`}
140-
>
128+
<div class="rounded-2xl border-2 border-border bg-card p-3 sm:p-4 lg:p-5">
141129
<div class="flex items-start gap-3 sm:gap-4">
142130
<span
143-
class={`flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-sm font-bold sm:h-12 sm:w-12 sm:text-base ${
144-
lifecycleLocked ? "bg-muted text-foreground/80" : "text-white"
145-
}`}
146-
style={lifecycleLocked ? undefined : `background:hsl(${s.unit.hue} 70% 27%)`}
131+
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-sm font-bold text-white sm:h-12 sm:w-12 sm:text-base"
132+
style={`background:hsl(${s.unit.hue} 70% 27%)`}
147133
>
148134
{Math.round(s.ratio * 100)}%
149135
</span>
150136
<div class="min-w-0 flex-1">
151137
<div class="flex flex-wrap items-center gap-2 sm:gap-x-3">
152-
<h3
153-
class={`font-semibold leading-tight sm:text-lg ${
154-
lifecycleLocked ? "text-muted-foreground" : ""
155-
}`}
156-
>
138+
<h3 class="font-semibold leading-tight sm:text-lg">
157139
{s.unit.title}
158140
</h3>
159-
{#if lifecycleLocked}
160-
<Badge variant="secondary" class="shrink-0 text-[0.65rem] sm:text-xs">Locked</Badge>
161-
{:else if s.level !== "new"}
141+
{#if s.level !== "new"}
162142
<Badge variant="secondary" class="shrink-0 text-[0.65rem] sm:text-xs"
163143
>{s.levelLabel}</Badge
164144
>
165145
{/if}
166-
{#if lifecycleLocked}
167-
<span class="text-[0.65rem] text-muted-foreground sm:text-xs">
168-
Complete Basics to unlock
169-
</span>
170-
{:else if game.workingTier(s.unit.id) === "advanced"}
146+
{#if game.workingTier(s.unit.id) === "advanced"}
171147
<span class="text-[0.65rem] text-muted-foreground sm:text-xs">
172148
{DIFFICULTY_LABEL.advanced}
173149
</span>
@@ -176,7 +152,6 @@
176152
<p class="mt-0.5 text-xs text-muted-foreground sm:text-sm">{s.unit.blurb}</p>
177153
<div
178154
class="mt-1.5 flex h-1.5 w-full overflow-hidden rounded-full bg-muted sm:mt-2 sm:h-2"
179-
class:opacity-50={lifecycleLocked}
180155
>
181156
{#if s.masteredRatio > 0}
182157
<div
@@ -199,7 +174,6 @@
199174
<Button
200175
class="flex-1"
201176
size="sm"
202-
disabled={lifecycleLocked}
203177
onclick={() => game.startUnit(s.unit.id)}
204178
>
205179
Quiz me
@@ -213,28 +187,6 @@
213187
Start learning
214188
</Button>
215189
</div>
216-
{#if gated && s.unit.id === "fundamentals"}
217-
<div class="mt-2 flex flex-col gap-2 sm:mt-3 sm:gap-3">
218-
<Button
219-
size="sm"
220-
variant="secondary"
221-
class="w-full"
222-
onclick={() => game.startTestOut()}
223-
>
224-
Test out of basics
225-
</Button>
226-
{#if game.hasFundamentalsAttempt && game.fundamentalsGaps.length > 0}
227-
<Button
228-
size="sm"
229-
variant="outline"
230-
class="w-full"
231-
onclick={() => game.startStudyFundamentalsGaps()}
232-
>
233-
Here’s the missing picture
234-
</Button>
235-
{/if}
236-
</div>
237-
{/if}
238190
</div>
239191
{/each}
240192
</div>

0 commit comments

Comments
 (0)