Show Color it optional step as complete if done, make levels accordion style collapsible

This commit is contained in:
iamdoubz committed 2026-06-17 21:22:17 -05:00
1 parent e868e2a2e7
commit 5a4d42137b
2 files changed
+33 -11

No files matched your search

+29 -11
View File
@@ -17,7 +17,7 @@ export default async function LearnPage() {
const isDone = (l: Lesson) => {
if (!user) return false;
if (l.badgeKey && hasBadge(user.id, l.badgeKey)) return true;
return l.phase === "extra" ? false : doneCount(l) >= stepCount(l);
return doneCount(l) >= stepCount(l);
};
return (
@@ -27,12 +27,16 @@ export default async function LearnPage() {
<h1>Learn to draw 🎨</h1>
<p className="muted">
Each picture has an <strong>Outline</strong> lesson and a <strong>Details</strong> lesson (earn the badge), plus an optional <strong>Color it!</strong> lesson.
{!user && " Log in to track progress and earn badges."}
{!user && " Log in to track your progress and earn badges."}
</p>
<div className="stack" style={{ marginTop: 18 }}>
{LEVELS.map((lvl, idx) => {
const subjects = getSubjects(lvl.key);
const complete = new Map<string, boolean>(subjects.map((s) => [s.key, s.lessons.every((l) => isDone(l))]));
// The first not-yet-finished phased subject is expanded; completed ones collapse.
const firstIncomplete = subjects.find((s) => (s.lessons.length > 1 || !!s.lessons[0].phase) && !complete.get(s.key))?.key;
return (
<div className="card" key={lvl.key}>
<div className="row" style={{ justifyContent: "space-between" }}>
@@ -44,8 +48,8 @@ export default async function LearnPage() {
<div className="stack" style={{ marginTop: 6 }}>
{subjects.map((subj) => {
const phased = subj.lessons.length > 1 || !!subj.lessons[0].phase;
if (!phased) {
// legacy single lesson
const l = subj.lessons[0];
const done = isDone(l);
const inProg = !done && doneCount(l) > 0;
@@ -53,26 +57,28 @@ export default async function LearnPage() {
<Link key={subj.key} href={`/learn/${l.level}/${l.slug}`} className="card" style={subCard(done)}>
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
<strong>{subj.emoji} {l.title}</strong>
<span>{done ? <span className="pill active">✓ Done</span> : inProg ? <span className="pill pending">Step {Math.min(doneCount(l) + 1, stepCount(l))}/{stepCount(l)}</span> : <span className="muted" style={{ fontSize: "0.85rem" }}>{stepCount(l)} steps</span>}</span>
{done ? <span className="pill active">✓ Done</span> : inProg ? <span className="pill pending">Step {Math.min(doneCount(l) + 1, stepCount(l))}/{stepCount(l)}</span> : <span className="muted" style={{ fontSize: "0.85rem" }}>{stepCount(l)} steps</span>}
</div>
</Link>
);
}
// phased subject (Outline / Details / Color it)
const byPhase = (p: string) => subj.lessons.find((l) => l.phase === p);
const outline = byPhase("outline");
const detail = byPhase("detail");
const extra = byPhase("extra");
const outlineDone = outline ? isDone(outline) : false;
const detailDone = detail ? isDone(detail) : false;
const subjComplete = !!complete.get(subj.key);
const phaseCard = (l: Lesson | undefined, locked: boolean, optional = false) => {
if (!l) return null;
const done = isDone(l);
const inProg = !done && !locked && doneCount(l) > 0;
const inner = (
<div style={{ position: "relative" }}>
<div>
<strong style={{ display: "block" }}>{PHASE_LABEL[l.phase ?? ""]}{optional ? " (optional)" : ""}</strong>
<span style={{ fontSize: "0.82rem" }} className="muted">
<span className="muted" style={{ fontSize: "0.82rem" }}>
{locked ? "🔒 Locked" : done ? "✓ Done" : inProg ? `Step ${Math.min(doneCount(l) + 1, stepCount(l))}/${stepCount(l)}` : l.phase === "extra" ? "Add color" : `${stepCount(l)} steps`}
</span>
</div>
@@ -83,15 +89,27 @@ export default async function LearnPage() {
<Link key={l.slug} href={`/learn/${l.level}/${l.slug}`} style={{ ...phaseBox, textDecoration: "none", color: "inherit", borderColor: done ? "var(--leaf)" : "var(--line)" }}>{inner}</Link>
);
};
return (
<div key={subj.key} className="card" style={{ border: "2px solid var(--line)", boxShadow: "none" }}>
<strong style={{ fontSize: "1.1rem" }}>{subj.emoji} {subj.name}</strong>
<div style={{ display: "grid", gap: 10, gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", marginTop: 10 }}>
<details
key={subj.key}
open={subj.key === firstIncomplete}
style={{ border: subjComplete ? "2px solid var(--leaf)" : "2px solid var(--line)", borderRadius: 14, padding: "10px 14px", background: "#fff" }}
>
<summary style={{ cursor: "pointer", fontWeight: 800, fontSize: "1.05rem" }}>
{subj.emoji} {subj.name}
{subjComplete ? (
<span className="pill active" style={{ marginLeft: 8 }}>✓ Done</span>
) : outlineDone || detailDone ? (
<span className="pill pending" style={{ marginLeft: 8 }}>In progress</span>
) : null}
</summary>
<div style={{ display: "grid", gap: 10, gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", marginTop: 12 }}>
{phaseCard(outline, false)}
{phaseCard(detail, !outlineDone)}
{phaseCard(extra, !detailDone, true)}
</div>
</div>
</details>
);
})}
</div>
+4
View File
@@ -217,6 +217,10 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
function toggleVisible(id: number) { const l = layersRef.current.find((x) => x.id === id); if (l) { l.visible = !l.visible; composite(); force((n) => n + 1); scheduleSave(); } }
async function finish() {
// Record completion of the Color it! lesson so the Learn page can mark it Done.
if (loggedIn) {
try { await fetch("/api/progress", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ level: meta.level, sublevel: meta.sublevel, step: 1 }) }); } catch { /* */ }
}
if (hasContent()) { const data = flatten(true); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); }
setDone(true);
}