diff --git a/src/components/NavLinks.tsx b/src/components/NavLinks.tsx
index e008e69..00bebf7 100644
--- a/src/components/NavLinks.tsx
+++ b/src/components/NavLinks.tsx
@@ -34,7 +34,9 @@ export default function NavLinks({
Report a problem
- {user.username}
+
+ πΌοΈ {user.username}
+
>
) : (
diff --git a/src/lib/curriculum.ts b/src/lib/curriculum.ts
index 353c61d..8dfb1b0 100644
--- a/src/lib/curriculum.ts
+++ b/src/lib/curriculum.ts
@@ -1,7 +1,7 @@
/**
* The DrawIt curriculum. Levels describe the learner; each level has numbered sub-levels (lessons).
- * Each lesson is ordered steps built from simple shapes. SVG fragments use a 400x300 viewBox and
- * STACK: rendering steps 1..n draws the whole picture (powers the slideshow and the lesson guide).
+ * Draw lessons: SVG fragments STACK (steps 1..n = full picture). Shade lessons (mode "shade"):
+ * each step's svg is a full guide (light source + zone hints), not cumulative.
*/
export interface LevelMeta { key: string; name: string; emoji: string; blurb: string; }
@@ -20,7 +20,7 @@ export type StepKind = "construct" | "outline" | "color" | "shade" | "detail";
export interface LessonStep { n: number; title: string; instruction: string; tip?: string; kind?: StepKind; svg: string; }
-export interface Lesson { level: string; sublevel: number; slug: string; title: string; subject: string; emoji: string; intro: string; badgeKey: string; badgeName: string; steps: LessonStep[]; }
+export interface Lesson { level: string; sublevel: number; slug: string; title: string; subject: string; emoji: string; intro: string; badgeKey: string; badgeName: string; mode?: "draw" | "shade"; steps: LessonStep[]; }
const FISH_STEPS: LessonStep[] = [
@@ -41,7 +41,7 @@ const FISH_STEPS: LessonStep[] = [
svg: `` },
];
-export const FISH_LESSON: Lesson = { level: "early-beginner", sublevel: 1, slug: "fish", title: "Draw a Fish", subject: "a happy fish", emoji: "π", intro: "We'll build a friendly fish from a few simple shapes. Six easy steps!", badgeKey: "early-beginner-1-fish", badgeName: "Fish Friend", steps: FISH_STEPS };
+export const FISH_LESSON: Lesson = { level: "early-beginner", sublevel: 1, slug: "fish", title: "Draw a Fish", subject: "a happy fish", emoji: "π", intro: "We'll build a friendly fish from a few simple shapes. Six easy steps!", badgeKey: "early-beginner-1-fish", badgeName: "Fish Friend", mode: "draw", steps: FISH_STEPS };
const PANDA_STEPS: LessonStep[] = [
{ n: 1, kind: "construct", title: "The head", instruction: "Draw one big round face.",
@@ -61,7 +61,7 @@ const PANDA_STEPS: LessonStep[] = [
svg: `` },
];
-export const PANDA_LESSON: Lesson = { level: "early-beginner", sublevel: 2, slug: "panda", title: "Draw a Panda", subject: "a cuddly panda", emoji: "πΌ", intro: "A panda is just circles and ovals. Let's make a cuddly friend!", badgeKey: "early-beginner-2-panda", badgeName: "Panda Pal", steps: PANDA_STEPS };
+export const PANDA_LESSON: Lesson = { level: "early-beginner", sublevel: 2, slug: "panda", title: "Draw a Panda", subject: "a cuddly panda", emoji: "πΌ", intro: "A panda is just circles and ovals. Let's make a cuddly friend!", badgeKey: "early-beginner-2-panda", badgeName: "Panda Pal", mode: "draw", steps: PANDA_STEPS };
const FLOWER_STEPS: LessonStep[] = [
{ n: 1, kind: "construct", title: "The middle", instruction: "Draw a circle in the center of the page.",
@@ -83,7 +83,7 @@ const FLOWER_STEPS: LessonStep[] = [
svg: `` },
];
-export const FLOWER_LESSON: Lesson = { level: "early-beginner", sublevel: 3, slug: "flower", title: "Draw a Flower", subject: "a cheerful flower", emoji: "πΈ", intro: "A circle in the middle and petals all around β then a stem and leaves.", badgeKey: "early-beginner-3-flower", badgeName: "Flower Power", steps: FLOWER_STEPS };
+export const FLOWER_LESSON: Lesson = { level: "early-beginner", sublevel: 3, slug: "flower", title: "Draw a Flower", subject: "a cheerful flower", emoji: "πΈ", intro: "A circle in the middle and petals all around β then a stem and leaves.", badgeKey: "early-beginner-3-flower", badgeName: "Flower Power", mode: "draw", steps: FLOWER_STEPS };
const UNICORN_STEPS: LessonStep[] = [
{ n: 1, kind: "construct", title: "The head", instruction: "Draw a rounded head that's a little narrower at the chin.",
@@ -103,7 +103,7 @@ const UNICORN_STEPS: LessonStep[] = [
svg: `` },
];
-export const UNICORN_LESSON: Lesson = { level: "early-beginner", sublevel: 4, slug: "unicorn", title: "Draw a Unicorn", subject: "a magical unicorn", emoji: "π¦", intro: "A rounded head, a sparkly horn, and a rainbow mane. Let's make magic!", badgeKey: "early-beginner-4-unicorn", badgeName: "Unicorn Magic", steps: UNICORN_STEPS };
+export const UNICORN_LESSON: Lesson = { level: "early-beginner", sublevel: 4, slug: "unicorn", title: "Draw a Unicorn", subject: "a magical unicorn", emoji: "π¦", intro: "A rounded head, a sparkly horn, and a rainbow mane. Let's make magic!", badgeKey: "early-beginner-4-unicorn", badgeName: "Unicorn Magic", mode: "draw", steps: UNICORN_STEPS };
const TREE_STEPS: LessonStep[] = [
{ n: 1, kind: "construct", title: "The trunk", instruction: "Draw a trunk that's a little wider at the bottom.",
@@ -125,7 +125,7 @@ const TREE_STEPS: LessonStep[] = [
svg: `` },
];
-export const TREE_LESSON: Lesson = { level: "early-beginner", sublevel: 5, slug: "tree", title: "Draw a Tree", subject: "a leafy tree", emoji: "π³", intro: "A trunk, some branches, and big clumps of leaves make a leafy tree.", badgeKey: "early-beginner-5-tree", badgeName: "Tree Top", steps: TREE_STEPS };
+export const TREE_LESSON: Lesson = { level: "early-beginner", sublevel: 5, slug: "tree", title: "Draw a Tree", subject: "a leafy tree", emoji: "π³", intro: "A trunk, some branches, and big clumps of leaves make a leafy tree.", badgeKey: "early-beginner-5-tree", badgeName: "Tree Top", mode: "draw", steps: TREE_STEPS };
const DINO_STEPS: LessonStep[] = [
{ n: 1, kind: "construct", title: "The body", instruction: "Draw a big rounded body.",
@@ -149,9 +149,28 @@ const DINO_STEPS: LessonStep[] = [
svg: `` },
];
-export const DINO_LESSON: Lesson = { level: "early-beginner", sublevel: 6, slug: "dinosaur", title: "Draw a Dinosaur", subject: "a friendly dinosaur", emoji: "π¦", intro: "A round body, a long neck, a small head, and a swooshy tail!", badgeKey: "early-beginner-6-dinosaur", badgeName: "Dino Master", steps: DINO_STEPS };
+export const DINO_LESSON: Lesson = { level: "early-beginner", sublevel: 6, slug: "dinosaur", title: "Draw a Dinosaur", subject: "a friendly dinosaur", emoji: "π¦", intro: "A round body, a long neck, a small head, and a swooshy tail!", badgeKey: "early-beginner-6-dinosaur", badgeName: "Dino Master", mode: "draw", steps: DINO_STEPS };
-export const LESSONS: Lesson[] = [FISH_LESSON, PANDA_LESSON, FLOWER_LESSON, UNICORN_LESSON, TREE_LESSON, DINO_LESSON];
+const SHADE_STEPS: LessonStep[] = [
+ { n: 1, kind: "shade", title: "How to hold your pencil", instruction: "Watch the demo above. Holding near the tip and pressing hard makes DARK shades; holding high up and pressing soft makes LIGHT shades.",
+ tip: "Try it in the air first!",
+ svg: `` },
+ { n: 2, kind: "shade", title: "Find the light", instruction: "The sun is up and to the left. The side of the ball facing the sun stays the brightest. That bright spot is the highlight.",
+ svg: `` },
+ { n: 3, kind: "shade", title: "Soft shade all over", instruction: "Pick a light tone and, with soft gentle passes, shade the whole ball β but leave the bright highlight spot white.",
+ tip: "Hold high and go easy.",
+ svg: `` },
+ { n: 4, kind: "shade", title: "Build up the shadow", instruction: "Pick a darker tone and press a little harder on the side AWAY from the sun. Go over it a few times to build it up slowly.",
+ tip: "More passes = darker.",
+ svg: `` },
+ { n: 5, kind: "shade", title: "Cast shadow & finish", instruction: "Shade the ground just under the ball on the shadow side. Keep that highlight bright and clean. You did it!",
+ tip: "π",
+ svg: `` },
+];
+
+export const SHADE_BALL_LESSON: Lesson = { level: "learner", sublevel: 1, slug: "shade-ball", title: "Light & Shadow: Shade a Ball", subject: "a shaded ball", emoji: "\u{1F317}", intro: "Time to make a flat circle look like a round ball β using light and shadow! First, learn how to hold your pencil.", badgeKey: "learner-1-shading", badgeName: "Shading Star", mode: "shade", steps: SHADE_STEPS };
+
+export const LESSONS: Lesson[] = [FISH_LESSON, PANDA_LESSON, FLOWER_LESSON, UNICORN_LESSON, TREE_LESSON, DINO_LESSON, SHADE_BALL_LESSON];
export function getLesson(level: string, sublevel: number): Lesson | undefined { return LESSONS.find((l) => l.level === level && l.sublevel === sublevel); }
export function getLessonBySlug(slug: string): Lesson | undefined { return LESSONS.find((l) => l.slug === slug); }
diff --git a/src/lib/db.ts b/src/lib/db.ts
index f6ce669..9d9aa07 100644
--- a/src/lib/db.ts
+++ b/src/lib/db.ts
@@ -99,10 +99,34 @@ function migrate(db: Database.Database) {
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
+ CREATE TABLE IF NOT EXISTS drawings (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ level TEXT NOT NULL,
+ sublevel INTEGER NOT NULL,
+ status TEXT NOT NULL, -- 'progress' | 'final'
+ kind TEXT NOT NULL, -- 'canvas' | 'upload'
+ image TEXT NOT NULL, -- data URL (PNG/JPEG)
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
CREATE INDEX IF NOT EXISTS idx_tokens_token ON tokens(token);
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
CREATE INDEX IF NOT EXISTS idx_progress_user ON progress(user_id);
CREATE INDEX IF NOT EXISTS idx_reports_status ON reports(status);
+ CREATE TABLE IF NOT EXISTS completions (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ level TEXT NOT NULL,
+ sublevel INTEGER NOT NULL,
+ badge_key TEXT NOT NULL,
+ completed_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_drawings_user ON drawings(user_id);
+ CREATE INDEX IF NOT EXISTS idx_drawings_progress ON drawings(user_id, level, sublevel, status);
+ CREATE INDEX IF NOT EXISTS idx_completions_user ON completions(user_id);
`);
}
diff --git a/src/lib/progress.ts b/src/lib/progress.ts
index 926e2ce..eae950b 100644
--- a/src/lib/progress.ts
+++ b/src/lib/progress.ts
@@ -52,3 +52,28 @@ export function listBadges(userId: number): { badge_key: string; awarded_at: str
.prepare("SELECT badge_key, awarded_at FROM badges WHERE user_id = ? ORDER BY awarded_at")
.all(userId) as { badge_key: string; awarded_at: string }[];
}
+
+/** Records one row per lesson completion, so the profile can show every date completed. */
+export function recordCompletion(userId: number, level: string, sublevel: number, badgeKey: string): void {
+ getDb()
+ .prepare(
+ `INSERT INTO completions (user_id, level, sublevel, badge_key) VALUES (?, ?, ?, ?)`
+ )
+ .run(userId, level, sublevel, badgeKey);
+}
+
+export interface CompletionRow {
+ badge_key: string;
+ level: string;
+ sublevel: number;
+ completed_at: string;
+}
+
+export function listCompletions(userId: number): CompletionRow[] {
+ return getDb()
+ .prepare(
+ `SELECT badge_key, level, sublevel, completed_at FROM completions
+ WHERE user_id = ? ORDER BY completed_at DESC`
+ )
+ .all(userId) as CompletionRow[];
+}