+ );
+}
diff --git a/client/src/components/ScenarioCard.jsx b/client/src/components/ScenarioCard.jsx
new file mode 100644
index 0000000..dc30a26
--- /dev/null
+++ b/client/src/components/ScenarioCard.jsx
@@ -0,0 +1,238 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { ArrowRight, ChevronRight } from 'lucide-react';
+
+const DIFFICULTY_BADGE = {
+ Beginner: 'badge-beginner',
+ Explorer: 'badge-explorer',
+ Builder: 'badge-builder',
+};
+
+const THEME_COLORS = {
+ 'chai-stall': 'var(--chai-color)',
+ 'isro': 'var(--isro-color)',
+ 'instagram': 'var(--insta-color)',
+ 'food-delivery': 'var(--food-color)',
+ 'ai-playlist': 'var(--playlist-color)',
+ 'kota': 'var(--kota-color)',
+ 'classic': 'var(--text-muted)',
+};
+
+const THEME_EMOJIS = {
+ 'chai-stall': '🍵',
+ 'isro': '🚀',
+ 'instagram': '📸',
+ 'food-delivery': '🍕',
+ 'ai-playlist': '🎵',
+ 'kota': '📚',
+ 'classic': '⚡',
+};
+
+export default function ScenarioCard({ scenario, onClick, compact = false }) {
+ const navigate = useNavigate();
+ const themeColor = THEME_COLORS[scenario.theme] || 'var(--text-muted)';
+ const themeEmoji = THEME_EMOJIS[scenario.theme] || '⚡';
+ const badgeClass = DIFFICULTY_BADGE[scenario.difficulty] || 'badge-beginner';
+
+ const handleClick = () => {
+ if (onClick) onClick(scenario);
+ else navigate(`/learn/${scenario._id}`);
+ };
+
+ if (compact) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/client/src/context/AuthContext.jsx b/client/src/context/AuthContext.jsx
new file mode 100644
index 0000000..d916770
--- /dev/null
+++ b/client/src/context/AuthContext.jsx
@@ -0,0 +1,110 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const AuthContext = createContext(null);
+
+const STORAGE_KEY = 'pybe_user';
+const PROGRESS_KEY = 'pybe_progress';
+
+export function AuthProvider({ children }) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) setUser(JSON.parse(stored));
+ } catch {
+ localStorage.removeItem(STORAGE_KEY);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ function signup({ name, email, password }) {
+ const existing = getAllUsers();
+ if (existing.find(u => u.email === email)) {
+ throw new Error('An account with this email already exists.');
+ }
+ const newUser = {
+ id: crypto.randomUUID(),
+ name,
+ email,
+ password, // not encrypted — prototype only
+ createdAt: new Date().toISOString(),
+ avatar: name.charAt(0).toUpperCase(),
+ };
+ const updated = [...existing, newUser];
+ localStorage.setItem('pybe_all_users', JSON.stringify(updated));
+ const { password: _, ...safeUser } = newUser;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(safeUser));
+ setUser(safeUser);
+ return safeUser;
+ }
+
+ function login({ email, password }) {
+ const existing = getAllUsers();
+ const found = existing.find(u => u.email === email && u.password === password);
+ if (!found) throw new Error('Incorrect email or password. Please try again.');
+ const { password: _, ...safeUser } = found;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(safeUser));
+ setUser(safeUser);
+ return safeUser;
+ }
+
+ function logout() {
+ localStorage.removeItem(STORAGE_KEY);
+ setUser(null);
+ }
+
+ // Progress management
+ function getProgress() {
+ try {
+ if (!user) return {};
+ const all = JSON.parse(localStorage.getItem(PROGRESS_KEY) || '{}');
+ return all[user.id] || {};
+ } catch { return {}; }
+ }
+
+ function markChapterComplete(chapterId) {
+ if (!user) return;
+ const all = JSON.parse(localStorage.getItem(PROGRESS_KEY) || '{}');
+ if (!all[user.id]) all[user.id] = {};
+ all[user.id][chapterId] = {
+ status: 'completed',
+ completedAt: new Date().toISOString(),
+ };
+ localStorage.setItem(PROGRESS_KEY, JSON.stringify(all));
+ }
+
+ function getChapterStatus(chapterId) {
+ const progress = getProgress();
+ return progress[chapterId]?.status || 'not-started';
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+function getAllUsers() {
+ try {
+ return JSON.parse(localStorage.getItem('pybe_all_users') || '[]');
+ } catch { return []; }
+}
+
+export function useAuth() {
+ const ctx = useContext(AuthContext);
+ if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
+ return ctx;
+}
diff --git a/client/src/context/ThemeContext.jsx b/client/src/context/ThemeContext.jsx
new file mode 100644
index 0000000..ca1937a
--- /dev/null
+++ b/client/src/context/ThemeContext.jsx
@@ -0,0 +1,44 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+const ThemeContext = createContext(null);
+
+export function ThemeProvider({ children }) {
+ const [theme, setTheme] = useState(() => {
+ return localStorage.getItem('pybe_theme') || 'dark';
+ });
+
+ const [dashboardTheme, setDashboardThemeState] = useState(() => {
+ return localStorage.getItem('pybe_dashboard_theme') || 'default';
+ });
+
+ useEffect(() => {
+ document.documentElement.setAttribute('data-theme', theme);
+ localStorage.setItem('pybe_theme', theme);
+ }, [theme]);
+
+ function toggleTheme() {
+ setTheme(t => t === 'dark' ? 'light' : 'dark');
+ }
+
+ function setDashboardTheme(newTheme) {
+ setDashboardThemeState(newTheme);
+ localStorage.setItem('pybe_dashboard_theme', newTheme);
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ return useContext(ThemeContext);
+}
+
diff --git a/client/src/data/curriculum.js b/client/src/data/curriculum.js
new file mode 100644
index 0000000..fd94751
--- /dev/null
+++ b/client/src/data/curriculum.js
@@ -0,0 +1,1117 @@
+// ─────────────────────────────────────────────────────────────────
+// PyBe Curriculum — Chapter definitions, concept mappings & theory
+// ─────────────────────────────────────────────────────────────────
+
+export const CURRICULUM = [
+ {
+ id: 'getting-started',
+ title: 'Getting Started',
+ emoji: '🌱',
+ color: '#A8FF3E',
+ chapters: [
+ {
+ id: 'variables',
+ title: 'Variables',
+ subtitle: 'Giving names to things',
+ emoji: '📦',
+ concepts: ['variables'],
+ intro: {
+ hook: "Imagine trying to remember 10 phone numbers in your head — without writing them down. That's what a computer without variables would be.",
+ whatYoullFigureOut: "How to give a name to any piece of information so you can use it later.",
+ vibe: "Think like Ramu, who just started his chai stall and is trying to keep track of everything.",
+ },
+ theory: {
+ headline: "You just discovered Variables! 🎉",
+ concept: "Variable",
+ beforeVsAfter: {
+ beforeTitle: "Without Variables (Hardcoded Raw Values)",
+ beforeCode: `print(12 * 47 - 200)\nprint("Chai price is 12") # What is 12? What is 47?`,
+ beforePain: "Every number is a mystery. If price changes to 15, you must manually rewrite it in 50 places!",
+ afterTitle: "With Variables (Named Memory Boxes)",
+ afterCode: `price = 15\ncups_sold = 47\nprofit = (price * cups_sold) - 200 # Clear names!`,
+ afterGain: "Change `price = 15` in ONE place and every calculation updates automatically across your entire program.",
+ },
+ explanation: `A variable is just a named box for storing information.
+
+When you write:
+ name = "Ramu"
+
+You're telling Python: "Create a box, label it 'name', and put the word Ramu inside."
+
+Later, whenever you need that information, you just say the box's name — and Python opens it and hands you what's inside.
+
+That's literally it. No magic. Just named boxes.`,
+ codeExample: `# Three named boxes
+chai_type = "Ginger Chai"
+price = 12
+cups_sold = 47
+
+# Use them later
+print("Today's chai:", chai_type)
+print("Total earned: ₹", price * cups_sold)`,
+ realWorldConnection: "Remember when Ramu had to remember his chai's price in his head? Variables let the computer remember for him — so Ramu can focus on making great chai.",
+ keyTakeaway: "Variables = named boxes that store information for you.",
+ },
+ },
+ {
+ id: 'operators',
+ title: 'Operators & Math',
+ subtitle: 'Making the computer calculate',
+ emoji: '➕',
+ concepts: ['arithmetic', 'subtraction'],
+ intro: {
+ hook: "You already know math. Python just lets you make the computer do it — so you never have to.",
+ whatYoullFigureOut: "How to add, subtract, multiply, and divide with Python — and what happens when things don't add up perfectly.",
+ vibe: "Help Ramu figure out his daily profit without using a calculator.",
+ },
+ theory: {
+ headline: "You just discovered Operators! 🧮",
+ concept: "Operators",
+ beforeVsAfter: {
+ beforeTitle: "Without Operators (Mental Math)",
+ beforeCode: `# Manually calculating in head: 12 * 47 = 564\nearnings = 564`,
+ beforePain: "Human mental math is slow, stressful, and error-prone when handling thousands of transactions.",
+ afterTitle: "With Operators (Computer Calculations)",
+ afterCode: `earnings = price * cups_sold\nprofit = earnings - daily_cost`,
+ afterGain: "Python computes math equations in nanoseconds with 100% mathematical precision.",
+ },
+ explanation: `Operators are just the math symbols you already know — Python understands them too.
+
+ + means add
+ - means subtract
+ * means multiply
+ / means divide
+ ** means "to the power of" (like 2**3 = 8)
+ % means "remainder" (12 % 5 = 2, because 12 ÷ 5 has remainder 2)
+
+Python follows the same order of operations you learned in school — BODMAS still applies!`,
+ codeExample: `price = 12
+cups_sold = 47
+daily_cost = 200
+
+earnings = price * cups_sold # 564
+profit = earnings - daily_cost # 364
+
+print("Ramu earned: ₹", earnings)
+print("Ramu's profit: ₹", profit)`,
+ realWorldConnection: "The moment Ramu wanted to know 'how much did I earn minus what I spent' — he needed operators. Python can do that calculation a million times faster than any mental math.",
+ keyTakeaway: "Operators let you do math on your variables — Python handles the calculation.",
+ },
+ },
+ {
+ id: 'strings',
+ title: 'Strings & Text',
+ subtitle: 'Working with words and sentences',
+ emoji: '💬',
+ concepts: ['strings'],
+ intro: {
+ hook: "Numbers are easy for computers. But what about words? Names? Messages? That's where strings come in.",
+ whatYoullFigureOut: "How Python stores and handles text — and how to stick pieces of text together.",
+ vibe: "Help create personalized greeting messages for Ramu's chai stall regulars.",
+ },
+ theory: {
+ headline: "You just discovered Strings! 📝",
+ concept: "String",
+ beforeVsAfter: {
+ beforeTitle: "Without Strings (Raw Numeric ASCII Codes)",
+ beforeCode: `# Computers originally only understood raw numbers\nmsg_code = [72, 101, 108, 108, 111] # ASCII codes!`,
+ beforePain: "Writing and reading human words as numeric codes makes reading code impossible.",
+ afterTitle: "With Strings (Quotes & Concatenation)",
+ afterCode: `name = "Priya"\ngreeting = f"Hello {name}! Your chai is ready."`,
+ afterGain: "Manipulate text, names, and sentences directly using intuitive quotes and f-strings.",
+ },
+ explanation: `A string is just a piece of text in Python. You wrap it in quotes and Python treats everything inside as text — not a calculation, not a variable name, just text.
+
+ "Hello, Ramu!" → a string
+ 'Ginger Chai' → also a string (single quotes work too)
+ "12" → this is the TEXT "12", not the number 12
+
+You can join strings together using +:
+ first = "Ginger"
+ second = " Chai"
+ full = first + second → "Ginger Chai"
+
+This is called concatenation (a fancy word for "sticking text together").`,
+ codeExample: `name = "Priya"
+chai = "Masala Chai"
+price = 15
+
+# Building a message
+greeting = "Hello " + name + "! Your " + chai + " is ready."
+print(greeting)
+# → Hello Priya! Your Masala Chai is ready.
+
+# f-strings (the modern, cleaner way)
+bill = f"Your total is ₹{price}. Thank you!"
+print(bill)`,
+ realWorldConnection: "Every time Ramu wants to print a receipt, send a message, or display a menu — he needs strings. You just figured out how Python talks in words, not just numbers.",
+ keyTakeaway: "Strings are text wrapped in quotes. Use + or f-strings to combine them.",
+ },
+ },
+ ],
+ },
+
+ {
+ id: 'basics',
+ title: 'Basics',
+ emoji: '📚',
+ color: '#60A5FA',
+ chapters: [
+ {
+ id: 'conditionals',
+ title: 'Making Decisions',
+ subtitle: 'if this, then that',
+ emoji: '🔀',
+ concepts: ['conditionals', 'comparisons'],
+ intro: {
+ hook: "Every single day you make hundreds of decisions: if it's raining, carry an umbrella. If the price is right, buy it. If your score is above 40, you pass.",
+ whatYoullFigureOut: "How to teach Python to make decisions — so it can handle different situations on its own.",
+ vibe: "Help a student figure out if they passed their exam — without manually checking every student's marks.",
+ },
+ theory: {
+ headline: "You just discovered If/Else — Decisions! 🔀",
+ concept: "Conditionals (if/else)",
+ beforeVsAfter: {
+ beforeTitle: "Without Conditionals (Flat Code Execution)",
+ beforeCode: `print("Student Passed!")\nprint("Student Failed!") # Both lines print every time!`,
+ beforePain: "Without branching rules, your program runs every line blindly regardless of the actual situation.",
+ afterTitle: "With Conditionals (if / elif / else)",
+ afterCode: `if score >= 60:\n print("Student Passed!")\nelse:\n print("Student Failed!")`,
+ afterGain: "Your program evaluates runtime inputs and makes smart decisions automatically.",
+ },
+ explanation: `Python makes decisions with "if":
+
+ if :
+ do this
+ else:
+ do that instead
+
+The key is the colon (:) and the indent (spaces before the next line). Python is very particular about this — it's how it knows which code belongs to the "if" and which belongs to the "else".
+
+You can also chain conditions:
+ if score >= 90:
+ grade = "A"
+ elif score >= 60:
+ grade = "B"
+ else:
+ grade = "C"
+
+elif means "else if" — another condition to check when the first one fails.`,
+ codeExample: `score = 73
+
+if score >= 80:
+ print("Great job! 🌟")
+elif score >= 60:
+ print("You passed! Keep it up.")
+elif score >= 40:
+ print("Just passed. Study harder next time.")
+else:
+ print("Better luck next time. Don't give up!")`,
+ realWorldConnection: "The moment you wanted the computer to treat different students differently based on their marks — you needed if/else. Python calls it a conditional, and it's the foundation of all smart programs.",
+ keyTakeaway: "if/else lets your code make decisions — different inputs, different outputs.",
+ },
+ },
+ {
+ id: 'loops',
+ title: 'Loops',
+ subtitle: 'Repeating without copying',
+ emoji: '🔁',
+ concepts: ['loops', 'while loops'],
+ intro: {
+ hook: "What if you had to check 30 students' attendance one by one, writing the same code 30 times? There's a better way.",
+ whatYoullFigureOut: "How to make Python repeat an action automatically — for a list of items, or until a condition is met.",
+ vibe: "Help a teacher automate the attendance roll call for a whole class.",
+ },
+ theory: {
+ headline: "You just discovered Loops! 🔁",
+ concept: "Loops (for / while)",
+ beforeVsAfter: {
+ beforeTitle: "Without Loops (Copy-Pasting Code Lines)",
+ beforeCode: `print(students[0], "Present")\nprint(students[1], "Present")\nprint(students[2], "Present") # x100 lines!`,
+ beforePain: "Processing 100 items requires copy-pasting code 100 times. Change 1 requirement = edit 100 lines!",
+ afterTitle: "With Loops (for / while)",
+ afterCode: `for s in students:\n print(s, "Present ✓")`,
+ afterGain: "Write the action ONCE, and Python repeats it for 10 or 10,000 items automatically.",
+ },
+ explanation: `A loop tells Python: "Do this thing for each item" or "Keep doing this until I say stop."
+
+There are two kinds:
+
+FOR loop — "do this for each item in a list":
+ for student in attendance_list:
+ print(student + " - Present")
+
+WHILE loop — "keep going as long as this is true":
+ attempts = 0
+ while attempts < 3:
+ print("Try again!")
+ attempts = attempts + 1
+
+The for loop is more common when you know what you're looping over.
+The while loop is for "keep going until something changes."`,
+ codeExample: `students = ["Priya", "Arjun", "Dev", "Meera"]
+
+# Check everyone without writing 4 separate lines
+for name in students:
+ print(name, "- Present ✓")
+
+# Count total attendance
+total = len(students)
+print(f"Total present: {total}")`,
+ realWorldConnection: "The moment you wanted to do the same thing for every student without copy-pasting code 30 times — you invented loops. Python has had them since day one.",
+ keyTakeaway: "Loops repeat an action — for each item in a collection, or while a condition is true.",
+ },
+ },
+ {
+ id: 'lists',
+ title: 'Lists',
+ subtitle: 'Storing many things together',
+ emoji: '📝',
+ concepts: ['lists', 'indexing', 'counting'],
+ intro: {
+ hook: "You have 8 chai types, 8 separate variables. You need to add a 9th. Then a 10th. Something has to change.",
+ whatYoullFigureOut: "How to group many related things into one single container — instead of creating a new variable for each one.",
+ vibe: "Ramu's chai menu keeps growing. Help him organize it properly.",
+ },
+ theory: {
+ headline: "You just discovered Lists! 📝",
+ concept: "List",
+ beforeVsAfter: {
+ beforeTitle: "Without Lists (Separate Variables)",
+ beforeCode: `item1 = "Chai"\nitem2 = "Coffee"\nitem3 = "Samosa" # Need item4, item5... item100?`,
+ beforePain: "Creating separate variables for 100 items makes passing data and counting total items impossible.",
+ afterTitle: "With Lists (Ordered Collection Container)",
+ afterCode: `menu = ["Chai", "Coffee", "Samosa"]\nmenu.append("Bun Maska")`,
+ afterGain: "Store thousands of items in one named list container with instant index access.",
+ },
+ explanation: `A list is like a row of boxes — instead of one named box, you have many boxes in a line, all under one name.
+
+ chai_menu = ["Ginger Chai", "Masala Chai", "Plain Tea", "Green Tea"]
+
+Now chai_menu holds ALL four, under one name.
+
+You get individual items by their position (starting from 0 — Python counts from zero, not one):
+ chai_menu[0] → "Ginger Chai" (first item)
+ chai_menu[1] → "Masala Chai" (second item)
+ chai_menu[-1] → "Green Tea" (last item, a shortcut!)
+
+You can add to the list: chai_menu.append("Lemon Tea")
+You can check the length: len(chai_menu) → 5`,
+ codeExample: `chai_menu = ["Ginger Chai", "Masala Chai", "Plain Tea"]
+
+# See everything
+for chai in chai_menu:
+ print("•", chai)
+
+# Add a new item
+chai_menu.append("Lemon Tea")
+print(f"Now we have {len(chai_menu)} types of chai!")
+
+# Get the first one
+print("Our signature chai:", chai_menu[0])`,
+ realWorldConnection: "The moment Ramu had 8 separate chai variables and realized they all belonged together — he reinvented the list. That pain you felt managing separate variables? That's exactly why Python has lists.",
+ keyTakeaway: "Lists hold many items under one name — indexed from 0, grow with .append().",
+ },
+ },
+ ],
+ },
+
+ {
+ id: 'intermediate',
+ title: 'Intermediate',
+ emoji: '⚙️',
+ color: '#F59E0B',
+ chapters: [
+ {
+ id: 'dictionaries',
+ title: 'Dictionaries',
+ subtitle: 'Looking up by name, not position',
+ emoji: '📖',
+ concepts: ['dictionaries'],
+ intro: {
+ hook: "Lists are great, but what happens when you need to know the *price* of a specific chai? You'd have to count its position first. That breaks. Fast.",
+ whatYoullFigureOut: "How to store key-value pairs — like a real dictionary where you look up a word and get its meaning.",
+ vibe: "A critical FAIL at ISRO Mission Control at 2 AM shows why position-based lookup breaks — and how dictionaries fix it.",
+ },
+ theory: {
+ headline: "You just discovered Dictionaries! 📖",
+ concept: "Dictionary",
+ beforeVsAfter: {
+ beforeTitle: "Without Dictionaries (Parallel Lists)",
+ beforeCode: `items = ["Chai", "Coffee"]\nprices = [12, 25]\n# Index 0 must match Index 0! Danger!`,
+ beforePain: "Parallel lists fall out of sync if an item is deleted or sorted, corrupting your application data.",
+ afterTitle: "With Dictionaries (Key-Value Direct Lookup)",
+ afterCode: `prices = {"Chai": 12, "Coffee": 25}\nprint(prices["Chai"]) # Direct O(1) lookup!`,
+ afterGain: "Associate names directly with values for instant O(1) key lookup by name.",
+ },
+ explanation: `A dictionary stores pairs: a key and a value. Just like a real dictionary — you look up a word (key) and get its definition (value).
+
+ prices = {
+ "Ginger Chai": 12,
+ "Masala Chai": 15,
+ "Plain Tea": 8,
+ }
+
+Now to find Masala Chai's price, you don't count positions — you just ask by name:
+ prices["Masala Chai"] → 15
+
+This is much safer than lists when things have names. No more "wait, was it index 4 or 5?"
+
+You can add entries: prices["Lemon Tea"] = 10
+Check if key exists: "Plain Tea" in prices → True
+Get all keys: prices.keys()`,
+ codeExample: `# ISRO telemetry — safe lookup by name
+subsystems = {
+ "fuel_pressure": 98.6,
+ "temperature": 72.3,
+ "signal_strength": 94.1,
+}
+
+# Look up by name, not position!
+print("Fuel pressure:", subsystems["fuel_pressure"])
+
+# Add a new subsystem easily
+subsystems["battery"] = 87.5
+
+# Check everything
+for system, value in subsystems.items():
+ status = "OK ✓" if value > 80 else "WARNING ⚠️"
+ print(f"{system}: {value} — {status}")`,
+ realWorldConnection: "When the ISRO intern needed to find fuel pressure among 12 subsystems at 2 AM, counting list positions was too risky. Dictionaries let you look up by name — because names don't change even when you add new items.",
+ keyTakeaway: "Dictionaries store key-value pairs — look up by name, not position.",
+ },
+ },
+ {
+ id: 'sets',
+ title: 'Sets',
+ subtitle: 'Unique collections — no duplicates allowed',
+ emoji: '🎯',
+ concepts: ['sets'],
+ intro: {
+ hook: '"Shape of You" plays for the 23rd time. Your playlist has 200 songs — 40 of them are duplicates. A set would have fixed this in one line.',
+ whatYoullFigureOut: "How to store things where each item can only appear once — and how to find what's missing or what's shared.",
+ vibe: "The AI Playlist that keeps repeating songs — and the duplicate biryani crisis that crashed the food delivery startup.",
+ },
+ theory: {
+ headline: "You just discovered Sets! 🎯",
+ concept: "Set",
+ beforeVsAfter: {
+ beforeTitle: "Without Sets (Manual Loop Duplicate Checks)",
+ beforeCode: `unique = []\nfor x in items:\n if x not in unique:\n unique.append(x) # Slow & verbose!`,
+ beforePain: "Checking for duplicates manually requires slow nested loops and messy boilerplate code.",
+ afterTitle: "With Sets (Unique Element Buckets)",
+ afterCode: `unique_items = set(items) # Instant 1-line deduplication!`,
+ afterGain: "Guarantees 100% unique elements automatically using mathematical set operations.",
+ },
+ explanation: `A set is like a list, but with one golden rule: every item can only appear ONCE.
+
+ played = {"Shape of You", "Blinding Lights", "Shape of You"}
+
+ Actually stored: {"Shape of You", "Blinding Lights"}
+ (The duplicate is automatically thrown out)
+
+Sets are great for:
+ • Removing duplicates from a list
+ • Checking if something is in a group (very fast!)
+ • Finding what two groups share (intersection)
+ • Finding what one group has that the other doesn't (difference)
+
+Convert a list to a set: set(my_list)
+Convert back: list(my_set)`,
+ codeExample: `# Fix a playlist with duplicates
+songs_played = ["Shape of You", "Blinding Lights", "Shape of You", "Levitating", "Blinding Lights"]
+
+# Remove all duplicates instantly
+unique_songs = set(songs_played)
+print(f"Unique songs: {len(unique_songs)}") # → 3
+
+# Find songs NOT yet played today
+all_songs = {"Shape of You", "Blinding Lights", "Levitating", "Stay", "Peaches"}
+unplayed = all_songs - unique_songs
+print("Songs to play next:", unplayed)`,
+ realWorldConnection: "The moment 'Shape of You' played for the 23rd time, you needed a set — a collection that just refuses to store duplicates. Python sets do this automatically, with zero extra code from you.",
+ keyTakeaway: "Sets store unique items only — perfect for removing duplicates and finding differences.",
+ },
+ },
+ {
+ id: 'functions',
+ title: 'Functions',
+ subtitle: 'Write once, use everywhere',
+ emoji: '🔧',
+ concepts: ['functions'],
+ intro: {
+ hook: "You've written the same discount calculation in 5 different places in your code. Then the discount rule changes. Now you have to fix it in 5 places. There must be a better way.",
+ whatYoullFigureOut: "How to package a piece of code into a named box — so you can run it anytime without rewriting it.",
+ vibe: "Ramu's chai stall needs a consistent discount system — and copy-pasting the formula everywhere is already causing bugs.",
+ },
+ theory: {
+ headline: "You just discovered Functions! 🔧",
+ concept: "Function",
+ beforeVsAfter: {
+ beforeTitle: "Without Functions (Duplicate Code Blocks)",
+ beforeCode: `# Table 1 bill calculation\ntax = 15 * 0.05\ntotal = 15 + tax\n# Repeat same 3 lines for Table 2, Table 3...`,
+ beforePain: "Duplicating calculation logic across your app means bug fixes must be repeated everywhere.",
+ afterTitle: "With Functions (Reusable Logic Blocks)",
+ afterCode: `def get_total(price):\n return price * 1.05\n\nbill1 = get_total(15)`,
+ afterGain: "Package logic into reusable functions with inputs and outputs — write once, run anywhere.",
+ },
+ explanation: `A function is a named, reusable block of code. You define it once, call it anywhere.
+
+ def calculate_bill(price, quantity):
+ total = price * quantity
+ discount = total * 0.1 # 10% off
+ return total - discount
+
+Now whenever you need the bill:
+ bill = calculate_bill(12, 5) → 54.0
+
+You gave the function two inputs (price, quantity), it ran the calculation and gave you back an answer (54.0).
+
+ def → how you declare a function
+ return → how the function sends the answer back to you
+ Indented code → the "body" — what runs when you call it`,
+ codeExample: `def chai_bill(price, cups, has_loyalty_card):
+ total = price * cups
+ if has_loyalty_card:
+ total = total * 0.9 # 10% loyalty discount
+ return total
+
+# Use it for any order — no copy-paste!
+order1 = chai_bill(12, 3, True) # → 32.4
+order2 = chai_bill(15, 2, False) # → 30.0
+order3 = chai_bill(8, 5, True) # → 36.0
+
+print(f"Order 1: ₹{order1}")`,
+ realWorldConnection: "The moment you had the same discount formula in 5 places and one change broke 4 of them — you needed a function. Package the logic once, name it, call it everywhere. Fix it once, fixed everywhere.",
+ keyTakeaway: "Functions are reusable code blocks — define once with def, call them anywhere.",
+ },
+ },
+ {
+ id: 'search-filter',
+ title: 'Search & Filter',
+ subtitle: 'Finding exactly what you need',
+ emoji: '🔍',
+ concepts: ['search', 'filtering', 'modulo'],
+ intro: {
+ hook: "You have 30 students. A parent just called and wants their child's rank. You're staring at 30 names in a list. How do you find the right one — fast?",
+ whatYoullFigureOut: "How to search through data, filter it by conditions, and pick out exactly what you need.",
+ vibe: "The Kota coaching center needs to find any student's rank in seconds — with the director watching.",
+ },
+ theory: {
+ headline: "You just discovered Search & Filter! 🔍",
+ concept: "Search & Filter",
+ beforeVsAfter: {
+ beforeTitle: "Without Search Algorithms (Manual Inspection)",
+ beforeCode: `# Checking elements one by one manually without pattern matching`,
+ beforePain: "Scanning large datasets without systematic algorithms results in missed data and slowness.",
+ afterTitle: "With Search & Filter Algorithms",
+ afterCode: `found = [item for item in items if query in item]`,
+ afterGain: "Find target items or filter collections in milliseconds using pattern matching.",
+ },
+ explanation: `Searching means finding an item that matches what you're looking for.
+Filtering means keeping only the items that pass a condition.
+
+Python gives you powerful tools for both:
+
+ # Search: find one item
+ for student in students:
+ if student["name"] == "Priya":
+ print("Found her!")
+
+ # Filter: keep only items that match
+ top_students = [s for s in students if s["rank"] <= 10]
+
+That last line is called a "list comprehension" — it's Python's elegant way to filter in one line.
+
+You can also use:
+ any() → is at least one item true?
+ all() → are all items true?
+ min() → find the smallest
+ max() → find the largest`,
+ codeExample: `students = [
+ {"name": "Priya", "rank": 3},
+ {"name": "Arjun", "rank": 12},
+ {"name": "Dev", "rank": 1},
+ {"name": "Meera", "rank": 7},
+]
+
+# Find one student by name
+def find_student(name):
+ for s in students:
+ if s["name"] == name:
+ return s
+ return None
+
+result = find_student("Priya")
+print(f"{result['name']} is ranked #{result['rank']}")
+
+# Filter: who's in top 5?
+top5 = [s for s in students if s["rank"] <= 5]
+print("Top 5:", [s["name"] for s in top5])`,
+ realWorldConnection: "When the director walked in and asked 'where is Priya's rank?' — you needed search. When you wanted only the top 10 students — you needed filter. These two operations underlie every app you've ever used.",
+ keyTakeaway: "Search finds one item; Filter keeps only items that match a condition.",
+ },
+ },
+ ],
+ },
+
+ {
+ id: 'advanced',
+ title: 'Advanced',
+ emoji: '⚡',
+ color: '#EC4899',
+ chapters: [
+ {
+ id: 'error-handling',
+ title: 'Error Handling',
+ subtitle: "Expecting the unexpected",
+ emoji: '🛡️',
+ concepts: ['error handling', 'validation'],
+ intro: {
+ hook: "Someone enters -36 as a quantity. Your app crashes. 47 orders are wiped. Three friends lose their startup's first night of orders.",
+ whatYoullFigureOut: "How to write code that handles bad inputs, crashes gracefully, and never loses data — even when users do the wrong thing.",
+ vibe: "The HungerFix food delivery startup almost closed on its first night because of one missing input check.",
+ },
+ theory: {
+ headline: "You just discovered Error Handling! 🛡️",
+ concept: "try / except",
+ beforeVsAfter: {
+ beforeTitle: "Without Try/Except (Uncaught Crashes)",
+ beforeCode: `result = 10 / 0 # Unhandled ZeroDivisionError -> APP CRASHES!`,
+ beforePain: "An unexpected invalid input or zero division crashes your entire application for all users.",
+ afterTitle: "With Try/Except (Error Shields)",
+ afterCode: `try:\n result = 10 / 0\nexcept ZeroDivisionError:\n result = 0 # Handled safely!`,
+ afterGain: "Shield your program against crashes — catch errors gracefully and keep running.",
+ },
+ explanation: `Real code gets real inputs — and real inputs are often wrong. Error handling lets your code deal with problems without crashing.
+
+ try:
+ risky code here
+ except SomeError:
+ code to run when it goes wrong
+
+It's like a safety net. Python tries the risky thing, and if it fails, instead of crashing — it catches the fall and does something smarter.
+
+Common errors you'll catch:
+ ValueError → wrong type of value ("abc" where a number was expected)
+ ZeroDivisionError → dividing by zero
+ KeyError → looking up a key that doesn't exist in a dict
+ FileNotFoundError → file you tried to open doesn't exist`,
+ codeExample: `def place_order(item, quantity):
+ # Validate input BEFORE anything breaks
+ if not isinstance(quantity, (int, float)):
+ return "Error: Quantity must be a number!"
+ if quantity <= 0:
+ return "Error: Quantity must be positive!"
+ if quantity > 100:
+ return "Error: Can't order more than 100 at once."
+
+ try:
+ total = calculate_price(item, quantity)
+ return f"Order placed! ₹{total}"
+ except KeyError:
+ return f"Error: '{item}' not found on our menu."
+ except Exception as e:
+ return f"Unexpected error: {e}"`,
+ realWorldConnection: "That -36 quantity that crashed the HungerFix startup? One if-statement would have caught it. Error handling is the difference between a toy project and a real app. Real code always has it.",
+ keyTakeaway: "try/except catches errors before they crash your program — validate inputs early!",
+ },
+ },
+ {
+ id: 'algorithms',
+ title: 'Algorithms',
+ subtitle: 'Thinking cleverly about steps',
+ emoji: '🧠',
+ concepts: ['algorithms', 'sorting', 'adaptive logic'],
+ intro: {
+ hook: "30 students' ranks. You need to find the top 3, then sort the whole list. Do you check every pair? Or is there a smarter way?",
+ whatYoullFigureOut: "How to think algorithmically — breaking a problem into efficient steps instead of brute-force checking everything.",
+ vibe: "The Kota coaching center needs its merit list sorted in 10 seconds — the director is already on his way up.",
+ },
+ theory: {
+ headline: "You just discovered Algorithms! 🧠",
+ concept: "Algorithms & Sorting",
+ beforeVsAfter: {
+ beforeTitle: "Without Sorting Algorithms (Unordered Chaos)",
+ beforeCode: `scores = [73, 98, 45, 88] # Finding top score requires scanning all items`,
+ beforePain: "Unsorted data makes finding ranks, medians, and top performers extremely slow.",
+ afterTitle: "With Sorting Algorithms",
+ afterCode: `scores.sort(reverse=True) # [98, 88, 73, 45]`,
+ afterGain: "Instantly organize unstructured data into sorted order for instant ranking.",
+ },
+ explanation: `An algorithm is just a set of steps to solve a problem. But the key is: some step sequences are WAY smarter than others.
+
+Sorting is one of the most classic algorithmic problems:
+
+Python's built-in sort is extremely fast:
+ students.sort(key=lambda s: s["rank"])
+
+But understanding WHY it works matters more than memorizing it.
+
+The simplest sorting idea (Bubble Sort) goes through the list repeatedly, swapping adjacent items that are out of order. Python's actual sort (Timsort) is much smarter — but the IDEA is the same: compare, swap, repeat.
+
+Lambda is just a tiny function you write inline:
+ lambda s: s["rank"]
+means "for each student s, sort by their rank value."`,
+ codeExample: `students = [
+ {"name": "Priya", "marks": 287},
+ {"name": "Dev", "marks": 312},
+ {"name": "Meera", "marks": 245},
+ {"name": "Arjun", "marks": 298},
+]
+
+# Sort by marks — highest first
+sorted_students = sorted(students, key=lambda s: s["marks"], reverse=True)
+
+# Print merit list
+print("=== MERIT LIST ===")
+for rank, student in enumerate(sorted_students, start=1):
+ print(f"#{rank} {student['name']} — {student['marks']}")`,
+ realWorldConnection: "When the director needed the merit list in 10 seconds, you needed to sort 30 entries efficiently. Python's sort handles this in microseconds — built on decades of computer science research. But now you understand the idea behind it.",
+ keyTakeaway: "Algorithms are step-by-step solutions. Python's sort() is built-in, efficient, and customizable with key=.",
+ },
+ },
+ {
+ id: 'files',
+ title: 'Files & Data',
+ subtitle: 'Saving things so they last',
+ emoji: '💾',
+ concepts: ['file I/O', 'averages'],
+ intro: {
+ hook: "Every time you restart your app, all the data is gone. Ramu loses his entire order history. There has to be a way to make things permanent.",
+ whatYoullFigureOut: "How to read from and write to files — so your program's data survives even after you close it.",
+ vibe: "Ramu needs to save his daily sales records to a file so he can review them the next morning.",
+ },
+ theory: {
+ headline: "You just discovered File I/O! 💾",
+ concept: "Files & I/O",
+ beforeVsAfter: {
+ beforeTitle: "Without Files (Transient RAM Memory)",
+ beforeCode: `# Program ends -> all variables erased from RAM permanently!`,
+ beforePain: "When the app closes or the computer restarts, all student progress and data is lost forever.",
+ afterTitle: "With File I/O (Permanent Disk Storage)",
+ afterCode: `with open("data.json", "w") as f:\n json.dump(records, f)`,
+ afterGain: "Save data permanently to disk so records persist across sessions and restarts.",
+ },
+ explanation: `File I/O (Input/Output) means reading from and writing to files on your computer.
+
+Writing to a file:
+ with open("sales.txt", "w") as file:
+ file.write("Day 1: ₹564\n")
+
+Reading from a file:
+ with open("sales.txt", "r") as file:
+ content = file.read()
+
+The "with" statement is the safe way — it automatically closes the file when you're done.
+
+Modes:
+ "w" → write (starts fresh, overwrites existing)
+ "a" → append (adds to the end, keeps existing)
+ "r" → read (only reading, can't write)
+
+For structured data, Python's "json" module lets you save dictionaries and lists directly:
+ import json
+ json.dump(my_dict, file) # save
+ json.load(file) # load back`,
+ codeExample: `import json
+
+def save_daily_sales(day, earnings, cups_sold):
+ record = {
+ "day": day,
+ "earnings": earnings,
+ "cups_sold": cups_sold,
+ }
+ with open("ramu_sales.json", "a") as f:
+ f.write(json.dumps(record) + "\\n")
+
+def load_all_sales():
+ try:
+ with open("ramu_sales.json", "r") as f:
+ return [json.loads(line) for line in f if line.strip()]
+ except FileNotFoundError:
+ return [] # No records yet, that's fine
+
+save_daily_sales("Monday", 564, 47)
+all_records = load_all_sales()
+print(f"Total days recorded: {len(all_records)}")`,
+ realWorldConnection: "The moment Ramu wanted to check last Tuesday's sales — he needed files. Without file I/O, every program forgets everything the second you close it. Files make data permanent.",
+ keyTakeaway: "Files persist data between runs. Use 'with open()' to read/write safely. JSON handles structured data.",
+ },
+ },
+ ],
+ },
+];
+
+// Flat chapter list for easy lookup
+export const CHAPTERS_MAP = {};
+CURRICULUM.forEach(section => {
+ section.chapters.forEach(chapter => {
+ CHAPTERS_MAP[chapter.id] = { ...chapter, sectionId: section.id, sectionTitle: section.title, sectionColor: section.color };
+ });
+});
+
+// All chapters in order
+export const ALL_CHAPTERS = CURRICULUM.flatMap(s => s.chapters.map(c => ({
+ ...c,
+ sectionId: s.id,
+ sectionTitle: s.title,
+ sectionColor: s.color,
+})));
+
+// ── Theme Variations for Chapter Intro & Theory ────────────────────
+const THEMED_CHAPTER_VARIANTS = {
+ potterheads: {
+ variables: {
+ vibe: "Think like a Potions Apprentice at Hogwarts keeping track of ingredients for Severus Snape's cauldron.",
+ realWorldConnection: "When Snape measures Boomslang skin and Lacewing flies, variables let Hogwarts magic track the exact dosage — so your cauldron doesn't explode.",
+ codeExample: `# Hogwarts Potion Ingredients
+ingredient = "Boomslang Skin"
+grams = 15
+potency = 98
+
+print("Brewing:", ingredient)
+print("Total dosage:", grams * potency)`,
+ },
+ operators: {
+ vibe: "Help Hermione calculate House points and potion brewing times without a Time-Turner.",
+ realWorldConnection: "Calculating House point totals and potion brewing duration requires Python math operators.",
+ codeExample: `base_points = 100
+bonus = 45
+penalty = 15
+
+final_points = base_points + bonus - penalty
+print("Gryffindor total:", final_points)`,
+ },
+ strings: {
+ vibe: "Craft incantation formulas and Marauder's Map scroll greetings.",
+ realWorldConnection: "Spells like 'Expelliarmus' and Marauder's Map scroll messages are strings in Python.",
+ codeExample: `wizard = "Harry"
+spell = "Expelliarmus"
+
+greeting = f"I solemnly swear {wizard} casts {spell}!"
+print(greeting)`,
+ },
+ conditionals: {
+ vibe: "Help the Sorting Hat assign students to Gryffindor, Ravenclaw, Hufflepuff, or Slytherin.",
+ realWorldConnection: "The Sorting Hat uses if/elif/else to evaluate student traits and assign their house.",
+ codeExample: `bravery = 88
+wisdom = 72
+
+if bravery >= 80:
+ house = "Gryffindor"
+elif wisdom >= 80:
+ house = "Ravenclaw"
+else:
+ house = "Hufflepuff"
+
+print("Assigned to:", house)`,
+ },
+ loops: {
+ vibe: "Automate stirring the Felix Felicis cauldron 50 times in a clockwise direction.",
+ realWorldConnection: "Potion brewing requires repetitive clockwise stirs — loops repeat the action automatically.",
+ codeExample: `for stir in range(1, 6):
+ print(f"Stirring Felix Felicis... Turn {stir} ✨")`,
+ },
+ lists: {
+ vibe: "Organize Harry's DADA Defense Against the Dark Arts spellbook list.",
+ realWorldConnection: "Storing spell books and potion ingredients in a Python list keeps Hogwarts inventories organized.",
+ codeExample: `spells = ["Lumos", "Alohomora", "Expecto Patronum"]
+spells.append("Stupefy")
+
+print("Known spells:", spells)`,
+ },
+ dictionaries: {
+ vibe: "Manage Dumbledore's House Points tally dictionary.",
+ realWorldConnection: "Looking up House Points by house name ('Gryffindor') requires a dictionary lookup.",
+ codeExample: `house_points = {"Gryffindor": 450, "Slytherin": 420, "Ravenclaw": 390}
+print("Gryffindor points:", house_points["Gryffindor"])`,
+ },
+ sets: {
+ vibe: "Filter out duplicate curse owl reports in the Ministry of Magic.",
+ realWorldConnection: "Sets eliminate duplicate owl telemetry reports automatically.",
+ codeExample: `curse_reports = {"Curse_A", "Curse_B", "Curse_A"}
+print("Unique curses:", curse_reports)`,
+ },
+ functions: {
+ vibe: "Package reusable potion stir and incantation power calculations into magic functions.",
+ realWorldConnection: "Defining a brewing function lets any wizard re-use complex magic logic anywhere.",
+ codeExample: `def cast_patronus(happy_memory):
+ return f"Expecto Patronum! Powered by {happy_memory}"
+
+print(cast_patronus("Flying on Buckbeak"))`,
+ },
+ search: {
+ vibe: "Find a specific dark magic scroll in the Hogwarts Restricted Section.",
+ realWorldConnection: "Search algorithms locate rare magical artifacts in vast library scrolls.",
+ codeExample: `library = ["Standard Spells", "Dark Curses", "Alchemy"]
+found = "Dark Curses" in library
+print("Found in Restricted Section:", found)`,
+ },
+ error: {
+ vibe: "Handle explosive cauldron failures gracefully with try/except.",
+ realWorldConnection: "When a potion fails, try/except prevents the entire dungeon from blowing up.",
+ codeExample: `try:
+ heat = 500
+ if heat > 300:
+ raise ValueError("Cauldron Overheat!")
+except ValueError as e:
+ print("Shield charm cast! 🛡️", e)`,
+ },
+ algorithms: {
+ vibe: "Sort the House Cup leaderboard before Dumbledore awards the trophy.",
+ realWorldConnection: "Algorithms sort House points from highest to lowest instantly.",
+ codeExample: `scores = [420, 450, 390]
+scores.sort(reverse=True)
+print("Leaderboard:", scores)`,
+ },
+ files: {
+ vibe: "Save Ministry of Magic secret archives to permanent scroll files.",
+ realWorldConnection: "File I/O persists magical records across Hogwarts terms.",
+ codeExample: `with open("scrolls.txt", "w") as f:
+ f.write("Secret Spell Archive v1")`,
+ },
+ },
+
+ marvel: {
+ variables: {
+ vibe: "Think like Tony Stark's AI engineer, tracking Mark 85 Arc Reactor voltage percentage.",
+ realWorldConnection: "Tracking Arc Reactor output and repulsor battery levels requires named variables.",
+ codeExample: `# J.A.R.V.I.S. Suit Status
+armor_model = "Mark 85"
+arc_reactor_pct = 98.5
+status = "ONLINE"
+
+print("Armor:", armor_model)
+print("Power:", arc_reactor_pct, "%")`,
+ },
+ operators: {
+ vibe: "Help J.A.R.V.I.S. calculate flight thruster force and repulsor power drain.",
+ realWorldConnection: "Computing nanotech suit energy depletion requires Python operators.",
+ codeExample: `max_power = 1000
+thrust_cost = 250
+shield_cost = 180
+
+remaining = max_power - thrust_cost - shield_cost
+print("Power remaining:", remaining)`,
+ },
+ strings: {
+ vibe: "Configure J.A.R.V.I.S. voice alerts and Avengers Assembly broadcast messages.",
+ realWorldConnection: "Suit HUD status updates and AI voice responses are strings.",
+ codeExample: `hero = "Tony"
+alert = f"J.A.R.V.I.S.: Welcome back Mr. {hero}. All systems nominal."
+print(alert)`,
+ },
+ conditionals: {
+ vibe: "Program Stark defense suit automated threat level protocols.",
+ realWorldConnection: "Suit nanotech decides whether to deploy repulsors or shields using if/elif/else.",
+ codeExample: `threat_level = 85
+
+if threat_level > 80:
+ weapon = "Nanotech Shield"
+elif threat_level > 50:
+ weapon = "Repulsor Beam"
+else:
+ weapon = "Scan Only"
+
+print("Deployed:", weapon)`,
+ },
+ loops: {
+ vibe: "Scan 100 Iron Man armor modules for subsystem damage.",
+ realWorldConnection: "Scanning armor thruster arrays one by one is done in a loop.",
+ codeExample: `modules = ["Helmet", "Chest", "Repulsor L", "Repulsor R"]
+for m in modules:
+ print(f"Scanning {m}... OK ✓")`,
+ },
+ lists: {
+ vibe: "Maintain the Avengers active emergency team roster.",
+ realWorldConnection: "Storing hero names in a list lets J.A.R.V.I.S. broadcast missions instantaneously.",
+ codeExample: `avengers = ["Iron Man", "Thor", "Captain America"]
+avengers.append("Spider-Man")
+
+print("Active Avengers:", avengers)`,
+ },
+ dictionaries: {
+ vibe: "Build J.A.R.V.I.S. weapon status telemetry lookup dictionary.",
+ realWorldConnection: "Looking up weapon status by name ('unibeam') needs a dictionary.",
+ codeExample: `suit_status = {"unibeam": "CHARGED", "thrusters": "ACTIVE"}
+print("Unibeam status:", suit_status["unibeam"])`,
+ },
+ sets: {
+ vibe: "Filter duplicate cosmic energy spikes from Wakanda vibranium sensors.",
+ realWorldConnection: "Sets eliminate duplicate satellite energy readings automatically.",
+ codeExample: `signals = {"Gamma_88", "Cosmic_X", "Gamma_88"}
+print("Unique energy spikes:", signals)`,
+ },
+ functions: {
+ vibe: "Package repulsor thrust and trajectory calculations into J.A.R.V.I.S. functions.",
+ realWorldConnection: "Encapsulating flight telemetry calculations inside functions keeps suit AI fast.",
+ codeExample: `def compute_thrust(mass, accel):
+ return mass * accel
+
+print("Flight thrust:", compute_thrust(85, 9.8))`,
+ },
+ search: {
+ vibe: "Locate Infinity Stone energy signatures across planetary grids.",
+ realWorldConnection: "Filtering cosmic radar scans locates hostiles instantly.",
+ codeExample: `targets = ["Power Stone", "Space Stone", "Mind Stone"]
+found = "Space Stone" in targets
+print("Target locked:", found)`,
+ },
+ error: {
+ vibe: "Handle power overload power surges without crashing J.A.R.V.I.S.",
+ realWorldConnection: "Using try/except ensures suit AI re-routes power safely during an overload.",
+ codeExample: `try:
+ power_surge = 1200
+ if power_surge > 1000:
+ raise OverflowError("Arc Reactor Surge!")
+except OverflowError as e:
+ print("Diverting to heat sinks!", e)`,
+ },
+ algorithms: {
+ vibe: "Sort gauntlet energy stability levels in ascending threat order.",
+ realWorldConnection: "Sorting algorithms help Stark tech rank threat levels in milliseconds.",
+ codeExample: `threats = [95, 40, 88, 12]
+threats.sort(reverse=True)
+print("Priority threat order:", threats)`,
+ },
+ files: {
+ vibe: "Save J.A.R.V.I.S. telemetry logs to permanent Stark Cloud files.",
+ realWorldConnection: "File storage saves suit performance logs across missions.",
+ codeExample: `with open("jarvis_logs.json", "w") as f:
+ f.write('{"mission": "Endgame", "status": "Success"}')`,
+ },
+ },
+
+ anime: {
+ variables: {
+ vibe: "Think like a Shinobi Academy trainee tracking Naruto's Nine-Tails chakra reserves.",
+ realWorldConnection: "Storing chakra points, stamina, and ninja rank requires named variables.",
+ codeExample: `# Shinobi Status
+ninja = "Naruto"
+chakra_level = 9000
+rank = "Genin"
+
+print("Ninja:", ninja)
+print("Chakra:", chakra_level)`,
+ },
+ operators: {
+ vibe: "Calculate chakra consumption per shadow clone and jutsu stamina cost.",
+ realWorldConnection: "Multiplying chakra cost by shadow clone count uses Python operators.",
+ codeExample: `chakra_per_clone = 150
+clones_wanted = 12
+
+total_chakra = chakra_per_clone * clones_wanted
+print("Chakra required:", total_chakra)`,
+ },
+ strings: {
+ vibe: "Write jutsu incantations and Secret Leaf Scroll messages.",
+ realWorldConnection: "Jutsu names like 'Rasengan' and secret scroll texts are strings.",
+ codeExample: `jutsu = "Shadow Clone Jutsu"
+user = "Naruto"
+
+print(f"{user} invokes {jutsu}!")`,
+ },
+ conditionals: {
+ vibe: "Determine Chunin exam promotion results based on chakra scores.",
+ realWorldConnection: "Evaluating whether a shinobi advances uses if/elif/else.",
+ codeExample: `chakra_score = 88
+
+if chakra_score >= 85:
+ rank = "Chunin"
+elif chakra_score >= 60:
+ rank = "Genin"
+else:
+ rank = "Academy Student"
+
+print("Promoted to:", rank)`,
+ },
+ loops: {
+ vibe: "Train 100 shadow clones simultaneously in parallel jutsu loops.",
+ realWorldConnection: "Repeating physical training reps across all clones uses loops.",
+ codeExample: `for clone in range(1, 6):
+ print(f"Clone #{clone} practicing Rasengan... Complete! 🌀")`,
+ },
+ lists: {
+ vibe: "Organize Squad 7's ninja scroll inventory.",
+ realWorldConnection: "Keeping track of squad ninja weapons in a list makes preparation seamless.",
+ codeExample: `squad_7 = ["Naruto", "Sasuke", "Sakura"]
+squad_7.append("Kakashi-sensei")
+
+print("Squad roster:", squad_7)`,
+ },
+ dictionaries: {
+ vibe: "Build the Shinobi library Jutsu hand sign dictionary.",
+ realWorldConnection: "Looking up hand sign requirements by Jutsu name uses dictionaries.",
+ codeExample: `jutsu_dict = {"Rasengan": "Ram → Serpent", "Chidori": "Ox → Rabbit"}
+print("Rasengan signs:", jutsu_dict["Rasengan"])`,
+ },
+ sets: {
+ vibe: "Deduplicate wild dungeon rift monster radar pings.",
+ realWorldConnection: "Sets eliminate duplicate monster radar detections automatically.",
+ codeExample: `dungeon_monsters = {"Dragon_Rift", "Goblin_King", "Dragon_Rift"}
+print("Unique beasts detected:", dungeon_monsters)`,
+ },
+ functions: {
+ vibe: "Package secret Jutsu chakra multiplier logic into reusable functions.",
+ realWorldConnection: "Functions allow any shinobi to trigger complex jutsu logic with one call.",
+ codeExample: `def cast_shadow_clone(base_chakra, count):
+ return f"Summoned {count} clones! Used {base_chakra * count} chakra."
+
+print(cast_shadow_clone(100, 5))`,
+ },
+ search: {
+ vibe: "Search the dungeon rift for S-Rank legendary monsters.",
+ realWorldConnection: "Filter algorithms scan dungeon levels for high-value targets.",
+ codeExample: `beasts = ["Rank-C Goblin", "Rank-S Dragon", "Rank-A Wolf"]
+found = "Rank-S Dragon" in beasts
+print("S-Rank Beast spotted:", found)`,
+ },
+ error: {
+ vibe: "Handle chakra depletion exceptions cleanly during high-level battles.",
+ realWorldConnection: "Catching chakra exhaustion with try/except prevents total collapse.",
+ codeExample: `try:
+ chakra = 10
+ if chakra < 50:
+ raise Exception("Chakra Depleted!")
+except Exception as e:
+ print("Wood clone substitution triggered! 🍃", e)`,
+ },
+ algorithms: {
+ vibe: "Rank the Shinobi Guild leaderboard by combat power.",
+ realWorldConnection: "Sorting algorithms order ninja ranks dynamically.",
+ codeExample: `power_levels = [9500, 12000, 8800]
+power_levels.sort(reverse=True)
+print("Shinobi Rankings:", power_levels)`,
+ },
+ files: {
+ vibe: "Save Secret Leaf Scroll scrolls to permanent archive files.",
+ realWorldConnection: "File storage saves ninja clan scroll scrolls across generations.",
+ codeExample: `with open("forbidden_scrolls.txt", "w") as f:
+ f.write("Secret Shinobi Scrolls v1")`,
+ },
+ },
+};
+
+export function getThemedChapter(chapterId, theme) {
+ const base = CHAPTERS_MAP[chapterId];
+ if (!base) return null;
+ if (!theme || theme === 'default' || !THEMED_CHAPTER_VARIANTS[theme]) {
+ return base;
+ }
+ const variant = THEMED_CHAPTER_VARIANTS[theme][chapterId];
+ if (!variant) return base;
+
+ return {
+ ...base,
+ intro: {
+ ...base.intro,
+ vibe: variant.vibe || base.intro.vibe,
+ },
+ theory: {
+ ...base.theory,
+ realWorldConnection: variant.realWorldConnection || base.theory.realWorldConnection,
+ codeExample: variant.codeExample || base.theory.codeExample,
+ },
+ };
+}
diff --git a/client/src/layouts/AppLayout.jsx b/client/src/layouts/AppLayout.jsx
new file mode 100644
index 0000000..5852159
--- /dev/null
+++ b/client/src/layouts/AppLayout.jsx
@@ -0,0 +1,117 @@
+import React, { useState, useEffect } from 'react';
+import { Outlet, useNavigate } from 'react-router-dom';
+import CurriculumSidebar from '../components/CurriculumSidebar.jsx';
+import { Menu, X } from 'lucide-react';
+import { useTheme } from '../context/ThemeContext.jsx';
+
+export default function AppLayout() {
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const { dashboardTheme } = useTheme();
+
+ useEffect(() => {
+ const themeToApply = dashboardTheme || 'default';
+ document.documentElement.setAttribute('data-dashboard-theme', themeToApply);
+ return () => {
+ document.documentElement.removeAttribute('data-dashboard-theme');
+ };
+ }, [dashboardTheme]);
+
+ return (
+
+ Each theme is a full abstraction evolution arc. A problem that starts simple, grows
+ until the current approach breaks, and forces you to rediscover the next Python construct.
+ Select a theme below to view its specific case study arcs.
+
+
+ {/* Theme Filter Tabs */}
+
+ {tabs.map(tab => (
+
+ ))}
+
+
+
+ {/* Philosophy callout */}
+
+
💡
+
+ The UADE Philosophy
+
+ Variables became difficult to manage → Lists emerged.
+ Lists could not associate names → Dictionaries emerged.
+ Repeated code became difficult to maintain → Functions emerged.
+ You should experience this evolution yourself, not just be told about it.
+
+
+
+
+ {/* Grid of case studies */}
+ {loading ? (
+
+ {[...Array(6)].map((_, i) => (
+
+ ))}
+
+ ) : (
+
+ {caseStudies.map(cs => (
+
+ ))}
+
+ )}
+
+ {/* Footer note */}
+
+
+
+ Theory anchor
+
+ These themes are grounded in Barrows' Problem-Based Learning, Vygotsky's Zone of
+ Proximal Development, and Deleuze & Guattari's Rhizomatic Learning model —
+ the same learner, infinite entry points, zero fixed sequence.
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/ChapterPage.jsx b/client/src/pages/ChapterPage.jsx
new file mode 100644
index 0000000..368d9d2
--- /dev/null
+++ b/client/src/pages/ChapterPage.jsx
@@ -0,0 +1,1217 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useParams, useNavigate, Link } from 'react-router-dom';
+import { gsap } from 'gsap';
+import { useAuth } from '../context/AuthContext.jsx';
+import { CHAPTERS_MAP, CURRICULUM, getThemedChapter } from '../data/curriculum.js';
+import {
+ ArrowLeft, ArrowRight, CheckCircle2, Lightbulb,
+ BookOpen, Sparkles, ChevronRight,
+} from 'lucide-react';
+
+import { useTheme } from '../context/ThemeContext.jsx';
+
+const API = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';
+
+async function fetchScenarios(concepts, activeTheme) {
+ const themeToUse = activeTheme || 'default';
+ const results = new Map();
+ for (const concept of concepts) {
+ try {
+ const url = `${API}/scenarios?concept=${encodeURIComponent(concept)}&theme=${encodeURIComponent(themeToUse)}`;
+ const r = await fetch(url);
+ const data = await r.json();
+ data.forEach(s => results.set(s._id, s));
+ } catch { /* ignore */ }
+ }
+ let list = [...results.values()];
+ if (list.length === 0 && themeToUse !== 'default') {
+ for (const concept of concepts) {
+ try {
+ const r = await fetch(`${API}/scenarios?concept=${encodeURIComponent(concept)}&theme=default`);
+ const data = await r.json();
+ data.forEach(s => results.set(s._id, s));
+ } catch { /* ignore */ }
+ }
+ list = [...results.values()];
+ }
+ return list.slice(0, 4);
+}
+
+async function submitSession(scenarioId, reasoning, promptText) {
+ try {
+ const r = await fetch(`${API}/sessions`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ scenario: scenarioId, learnerName: 'Guest', reasoning, promptText }),
+ });
+ return r.ok ? await r.json() : null;
+ } catch { return null; }
+}
+
+// ── Client-side reasoning quality estimator ───────────────
+function estimateScore(reasoning) {
+ const text = (reasoning || '').toLowerCase().trim();
+ if (text.length < 10) return 15;
+ let score = 20;
+ if (text.length > 60) score += 8;
+ if (text.length > 150) score += 8;
+ if (text.length > 300) score += 7;
+ const signals = [
+ 'because','since','so that','in order to','which means','this helps',
+ 'instead','rather than','problem','solution','better','easier','organize',
+ 'group','store','remember','track','manage','keep','avoid','think','believe',
+ 'would','could','should','maybe','one way','another',
+ ];
+ signals.forEach(w => { if (text.includes(w)) score += 3; });
+ return Math.min(Math.max(score, 15), 88);
+}
+
+function scoreLevel(s) {
+ if (s >= 80) return { label: 'Excellent thinking', color: '#A8FF3E', emoji: '🔥', grade: 'A' };
+ if (s >= 65) return { label: 'Strong reasoning', color: '#60A5FA', emoji: '⚡', grade: 'B' };
+ if (s >= 48) return { label: 'Good start', color: '#F59E0B', emoji: '💡', grade: 'C' };
+ if (s >= 30) return { label: 'Keep exploring', color: '#F97316', emoji: '📈', grade: 'D' };
+ return { label: 'Just beginning', color: '#F472B6', emoji: '🌱', grade: 'E' };
+}
+
+const PHASES = { INTRO: 'intro', QUESTION: 'question', SUMMARY: 'summary', THEORY: 'theory' };
+
+export default function ChapterPage() {
+ const { chapterId } = useParams();
+ const navigate = useNavigate();
+ const { markChapterComplete, getChapterStatus } = useAuth();
+ const { dashboardTheme } = useTheme();
+ const chapter = getThemedChapter(chapterId, dashboardTheme);
+ const pageRef = useRef(null);
+
+ const [phase, setPhase] = useState(PHASES.INTRO);
+ const [scenarios, setScenarios] = useState([]);
+ const [loadingScenarios, setLoadingScenarios] = useState(true);
+ const [currentIdx, setCurrentIdx] = useState(0);
+ const [answers, setAnswers] = useState([]);
+ const [form, setForm] = useState({ reasoning: '', promptText: '' });
+ const [submitting, setSubmitting] = useState(false);
+ const [alreadyDone, setAlreadyDone] = useState(false);
+
+ useEffect(() => {
+ if (!chapter) { navigate('/app'); }
+ }, [chapter]);
+
+ // ── CRITICAL: Reset all state when chapter or theme changes ────────
+ useEffect(() => {
+ setPhase(PHASES.INTRO);
+ setCurrentIdx(0);
+ setAnswers([]);
+ setForm({ reasoning: '', promptText: '' });
+ setSubmitting(false);
+ setAlreadyDone(getChapterStatus(chapterId) === 'completed');
+ setLoadingScenarios(true);
+ if (chapter) {
+ fetchScenarios(chapter.concepts, dashboardTheme)
+ .then(s => setScenarios(s))
+ .finally(() => setLoadingScenarios(false));
+ }
+ }, [chapterId, dashboardTheme]);
+
+ // ── Phase animation ───────────────────────────────────────
+ // Runs AFTER React commits the new phase DOM.
+ useEffect(() => {
+ if (!pageRef.current) return;
+
+ if (phase === PHASES.THEORY) {
+ // Page wrapper: make visible immediately
+ gsap.set(pageRef.current, { opacity: 1, y: 0 });
+
+ // Delay slightly to ensure React has fully painted the theory DOM
+ const timer = setTimeout(() => {
+ if (!pageRef.current) return;
+
+ // Query ALL theory sections in the order they appear — do NOT use
+ // both individual selectors AND a parent > * selector, as that
+ // causes elements to be animated twice (disappear-then-reappear bug).
+ const els = pageRef.current.querySelectorAll(
+ '.theory-eyebrow, .theory-title, .theory-concept, ' +
+ '.theory-divider, .bva, .theory-explanation, .theory-code, ' +
+ '.msb, .mml, .theory-callout, .theory-takeaway, .theory-complete'
+ );
+ // Set all to invisible first, then stagger-in
+ gsap.set(els, { opacity: 0, y: 16 });
+ gsap.to(els, {
+ opacity: 1, y: 0,
+ duration: 0.45, stagger: 0.07, ease: 'power3.out',
+ });
+ }, 60);
+ return () => clearTimeout(timer);
+ } else {
+ gsap.fromTo(pageRef.current,
+ { y: 20, opacity: 0 },
+ { y: 0, opacity: 1, duration: 0.4, ease: 'power3.out' }
+ );
+ }
+ }, [phase, currentIdx]);
+
+ // ── Phase helpers ─────────────────────────────────────────
+ function animateOut() {
+ return new Promise(resolve => {
+ if (!pageRef.current) { resolve(); return; }
+ gsap.to(pageRef.current, {
+ y: -14, opacity: 0, duration: 0.22, ease: 'power2.in', onComplete: resolve,
+ });
+ });
+ }
+
+ async function startLearning() {
+ await animateOut();
+ setCurrentIdx(0);
+ setAnswers([]);
+ setForm({ reasoning: '', promptText: '' });
+ setPhase(PHASES.QUESTION);
+ }
+
+ async function submitAnswer() {
+ if (!form.reasoning.trim()) return;
+ setSubmitting(true);
+ const scenario = scenarios[currentIdx];
+ const res = await submitSession(scenario._id, form.reasoning, form.promptText);
+
+ // Fall back to client-side score estimate if backend returns null or no score
+ const finalScore = res?.promptScore ?? estimateScore(form.reasoning);
+ const enrichedRes = res ? { ...res, promptScore: finalScore } : { promptScore: finalScore };
+
+ const newAnswers = [...answers, { scenario, reasoning: form.reasoning, result: enrichedRes }];
+ setAnswers(newAnswers);
+
+ if (currentIdx < scenarios.length - 1) {
+ await animateOut();
+ setCurrentIdx(i => i + 1);
+ setForm({ reasoning: '', promptText: '' });
+ setSubmitting(false);
+ } else {
+ await animateOut();
+ setSubmitting(false);
+ setPhase(PHASES.SUMMARY);
+ }
+ }
+
+ async function revealTheory() {
+ await animateOut();
+ setPhase(PHASES.THEORY);
+ // Animation is triggered by useEffect([phase]) above
+ }
+
+ function handleComplete() {
+ markChapterComplete(chapterId);
+ const allChapters = CURRICULUM.flatMap(s => s.chapters);
+ const idx = allChapters.findIndex(c => c.id === chapterId);
+ const next = allChapters[idx + 1];
+ // Navigate to next chapter — the useEffect([chapterId]) reset will
+ // ensure it starts at INTRO phase, not THEORY.
+ navigate(next ? `/app/chapter/${next.id}` : '/app');
+ }
+
+ if (!chapter) return null;
+
+ return (
+
+ {/* All elements below get targeted by the GSAP stagger in useEffect.
+ DO NOT wrap in a single .theory-body — the bug was that animating
+ .theory-body > * would re-animate elements already animated individually. */}
+
+
+ 💡 You just discovered…
+
+
+
{theory.headline}
+
+
{theory.concept}
+
+
+
+ {/* ── 1. BEFORE VS AFTER FRICTION SIMULATOR ───────────────── */}
+
+
+
+ PyBe teaches programming through real Indian stories — chai stalls, ISRO rocket missions, and food startups.
+ You reason through the problem in plain English first. Python code is the discovery, not the starting lecture.
+
+ 📍 The Problem: Ramu's Chai Stall has 8 tea varieties stored in 8 separate variables (price1=10, price2=15...).
+ His menu expands to 50 items. What happens when he wants to update all prices?
+
+
+
+
+
+
+
+ {demoChoice === 'A' && (
+
+ ⚠️ Cluttered code! 50 separate variables mean 50 lines of duplicate code. Hard to maintain and easy to bug out.
+
+ )}
+
+ {demoChoice === 'B' && (
+
+ 🎉 Boom! You just reinvented a Python List ([])!
+
+ In PyBe, you discover concepts by solving real problems first.
+
+ No ceiling.{' '}
+ Dennis Ritchie, the creator of C, rated himself 4–5/10 on his own language.
+ There is always more to discover. PyBe scores reflect your current depth, not your limits.
+
Start learning Python through discovery & first-principles
+
+ {error &&
{error}
}
+
+
+
+
+ Already have an account?{' '}
+ Sign in
+
+
+
+
+
+ );
+}
diff --git a/client/src/styles/animations.css b/client/src/styles/animations.css
new file mode 100644
index 0000000..180a3cf
--- /dev/null
+++ b/client/src/styles/animations.css
@@ -0,0 +1,106 @@
+/* ═══════════════════════════════════════════════════════════
+ PyBe v2.0 — Animations & Micro-interactions
+═══════════════════════════════════════════════════════════ */
+
+/* ── Fade in up ───────────────────────────────────────────── */
+@keyframes fadeInUp {
+ from { opacity: 0; transform: translateY(24px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* ── Fade in ──────────────────────────────────────────────── */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* ── Scale in ─────────────────────────────────────────────── */
+@keyframes scaleIn {
+ from { opacity: 0; transform: scale(0.92); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+/* ── Slide in from left ───────────────────────────────────── */
+@keyframes slideInLeft {
+ from { opacity: 0; transform: translateX(-20px); }
+ to { opacity: 1; transform: translateX(0); }
+}
+
+/* ── Glow pulse ───────────────────────────────────────────── */
+@keyframes glowPulse {
+ 0%, 100% { box-shadow: 0 0 20px rgba(168, 255, 62, 0.1); }
+ 50% { box-shadow: 0 0 40px rgba(168, 255, 62, 0.25), 0 0 80px rgba(168, 255, 62, 0.08); }
+}
+
+/* ── Float ────────────────────────────────────────────────── */
+@keyframes float {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-8px); }
+}
+
+/* ── Spin slow ────────────────────────────────────────────── */
+@keyframes spinSlow {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+/* ── Counter tick ─────────────────────────────────────────── */
+@keyframes countUp {
+ from { transform: translateY(8px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
+
+/* ── Progress bar fill ────────────────────────────────────── */
+@keyframes fillBar {
+ from { width: 0; }
+ to { width: var(--fill-width, 50%); }
+}
+
+/* ── Unlock vault ─────────────────────────────────────────── */
+@keyframes vaultUnlock {
+ 0% { transform: scale(1) rotate(0deg); filter: brightness(1); }
+ 30% { transform: scale(1.1) rotate(-3deg); filter: brightness(1.3); }
+ 60% { transform: scale(1.1) rotate(3deg); filter: brightness(1.5); }
+ 100% { transform: scale(1) rotate(0deg); filter: brightness(1.2); }
+}
+
+/* ── Shimmer (used on skeleton) ───────────────────────────── */
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+/* ── Stagger utility classes ──────────────────────────────── */
+.anim-fade-up { animation: fadeInUp 0.5s cubic-bezier(0.4,0,0.2,1) both; }
+.anim-fade-in { animation: fadeIn 0.4s ease both; }
+.anim-scale-in { animation: scaleIn 0.35s cubic-bezier(0.4,0,0.2,1) both; }
+.anim-float { animation: float 3s ease-in-out infinite; }
+.anim-glow { animation: glowPulse 2.5s ease-in-out infinite; }
+
+/* Stagger children */
+.stagger > *:nth-child(1) { animation-delay: 0ms; }
+.stagger > *:nth-child(2) { animation-delay: 60ms; }
+.stagger > *:nth-child(3) { animation-delay: 120ms; }
+.stagger > *:nth-child(4) { animation-delay: 180ms; }
+.stagger > *:nth-child(5) { animation-delay: 240ms; }
+.stagger > *:nth-child(6) { animation-delay: 300ms; }
+.stagger > *:nth-child(7) { animation-delay: 360ms; }
+.stagger > *:nth-child(8) { animation-delay: 420ms; }
+
+/* ── Hover lift ───────────────────────────────────────────── */
+.hover-lift {
+ transition: transform 0.25s ease, box-shadow 0.25s ease;
+}
+.hover-lift:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 40px rgba(0,0,0,0.5);
+}
+
+/* ── Card hover glow ──────────────────────────────────────── */
+.hover-glow {
+ transition: box-shadow 0.3s ease, border-color 0.3s ease;
+}
+.hover-glow:hover {
+ box-shadow: 0 0 0 1px var(--accent-dim), 0 8px 32px rgba(168,255,62,0.1);
+ border-color: var(--border-accent);
+}
diff --git a/client/src/styles/index.css b/client/src/styles/index.css
new file mode 100644
index 0000000..12abd0c
--- /dev/null
+++ b/client/src/styles/index.css
@@ -0,0 +1,522 @@
+/* ═══════════════════════════════════════════════════════════
+ PyBe v2.1 — Global Design System
+ Dark-first · Light mode · Space Grotesk + Inter · GSAP
+═══════════════════════════════════════════════════════════ */
+
+/* ── Dark Mode (default) ──────────────────────────────────── */
+:root {
+ --bg-base: #0D1117;
+ --bg-surface: #161B22;
+ --bg-elevated: #1C2129;
+ --bg-glass: rgba(22, 27, 34, 0.75);
+ --bg-glass-light: rgba(255, 255, 255, 0.04);
+
+ --accent: #A8FF3E;
+ --accent-dim: #7BC92E;
+ --accent-dark: #3D7A00;
+ --accent-glow: rgba(168, 255, 62, 0.15);
+ --accent-glow-md: rgba(168, 255, 62, 0.28);
+
+ --chai-color: #F59E0B;
+ --isro-color: #3B82F6;
+ --insta-color: #EC4899;
+ --food-color: #F97316;
+ --playlist-color: #8B5CF6;
+ --kota-color: #10B981;
+
+ --beginner-color: #34D399;
+ --explorer-color: #60A5FA;
+ --builder-color: #F472B6;
+
+ --text-primary: #E6EDF3;
+ --text-secondary: #8B949E;
+ --text-muted: #6E7681;
+ --text-accent: #A8FF3E;
+
+ --border: rgba(255, 255, 255, 0.08);
+ --border-hover: rgba(255, 255, 255, 0.16);
+ --border-accent: rgba(168, 255, 62, 0.3);
+
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
+ --shadow-md: 0 4px 16px rgba(0,0,0,0.5);
+ --shadow-lg: 0 8px 32px rgba(0,0,0,0.6);
+ --shadow-glow: 0 0 32px rgba(168, 255, 62, 0.14);
+
+ --sidebar-bg: #0F1318;
+ --sidebar-border: rgba(255,255,255,0.06);
+
+ --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:24px; --r-full:9999px;
+ --t-fast:150ms ease; --t-base:250ms ease; --t-slow:400ms cubic-bezier(0.4,0,0.2,1);
+
+ --font-heading: 'Space Grotesk', sans-serif;
+ --font-body: 'Inter', sans-serif;
+ --font-mono: 'JetBrains Mono', monospace;
+
+ --sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:20px;
+ --sp-6:24px;--sp-8:32px;--sp-10:40px;--sp-12:48px;--sp-16:64px;
+ --navbar-h:64px;
+
+ --aurora-1: rgba(168,255,62,0.12);
+ --aurora-2: rgba(59,130,246,0.10);
+ --aurora-3: rgba(139,92,246,0.08);
+ --dot-color: rgba(255,255,255,0.055);
+}
+
+/* ── Light Mode ───────────────────────────────────────────── */
+[data-theme="light"] {
+ --bg-base: #F8FAFC;
+ --bg-surface: #FFFFFF;
+ --bg-elevated: #F1F5F9;
+ --bg-glass: rgba(255,255,255,0.85);
+ --bg-glass-light: rgba(0,0,0,0.03);
+
+ --accent: #15803D;
+ --accent-dim: #166534;
+ --accent-dark: #14532D;
+ --accent-glow: rgba(21,128,61,0.12);
+ --accent-glow-md: rgba(21,128,61,0.22);
+
+ --text-primary: #0F172A;
+ --text-secondary: #334155;
+ --text-muted: #64748B;
+ --text-accent: #15803D;
+
+ --border: rgba(0,0,0,0.1);
+ --border-hover: rgba(0,0,0,0.2);
+ --border-accent: rgba(21,128,61,0.35);
+
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
+ --shadow-md: 0 4px 16px rgba(0,0,0,0.08);
+ --shadow-lg: 0 8px 32px rgba(0,0,0,0.12);
+ --shadow-glow: 0 0 28px rgba(21,128,61,0.12);
+
+ --sidebar-bg: #FFFFFF;
+ --sidebar-border: rgba(0,0,0,0.08);
+
+ --beginner-color: #047857;
+ --explorer-color: #1D4ED8;
+ --builder-color: #BE185D;
+
+ --aurora-1: rgba(21,128,61,0.08);
+ --aurora-2: rgba(37,99,235,0.07);
+ --aurora-3: rgba(124,58,237,0.06);
+ --dot-color: rgba(0,0,0,0.06);
+}
+
+/* ── Potterheads Theme (Magical Hogwarts) ────────────────────── */
+[data-dashboard-theme="potterheads"] {
+ --bg-base: #120D1A;
+ --bg-surface: #1C1329;
+ --bg-elevated: #261A38;
+ --bg-glass: rgba(28, 19, 41, 0.85);
+ --bg-glass-light: rgba(245, 158, 11, 0.07);
+
+ --accent: #F59E0B;
+ --accent-dim: #D97706;
+ --accent-dark: #92400E;
+ --accent-glow: rgba(245, 158, 11, 0.2);
+ --accent-glow-md: rgba(245, 158, 11, 0.35);
+
+ --text-primary: #FDF6E3;
+ --text-secondary: #E4D5B7;
+ --text-muted: #9CA3AF;
+ --text-accent: #F59E0B;
+
+ --border: rgba(245, 158, 11, 0.2);
+ --border-hover: rgba(245, 158, 11, 0.4);
+ --border-accent: rgba(245, 158, 11, 0.6);
+
+ --sidebar-bg: #0D0814;
+ --sidebar-border: rgba(245, 158, 11, 0.18);
+
+ --shadow-glow: 0 0 32px rgba(245, 158, 11, 0.25);
+ --aurora-1: rgba(245, 158, 11, 0.18);
+ --aurora-2: rgba(168, 85, 247, 0.15);
+ --aurora-3: rgba(236, 72, 153, 0.12);
+ --dot-color: rgba(245, 158, 11, 0.08);
+}
+
+[data-theme="light"][data-dashboard-theme="potterheads"] {
+ --bg-base: #FAF4E8;
+ --bg-surface: #FFFDF9;
+ --bg-elevated: #F5EBD9;
+ --bg-glass: rgba(255, 253, 249, 0.9);
+ --bg-glass-light: rgba(180, 83, 9, 0.05);
+
+ --accent: #B45309;
+ --accent-dim: #92400E;
+ --accent-dark: #78350F;
+ --accent-glow: rgba(180, 83, 9, 0.14);
+ --accent-glow-md: rgba(180, 83, 9, 0.28);
+
+ --text-primary: #291E16;
+ --text-secondary: #524033;
+ --text-muted: #786455;
+ --text-accent: #B45309;
+
+ --border: rgba(180, 83, 9, 0.18);
+ --border-hover: rgba(180, 83, 9, 0.38);
+ --border-accent: rgba(180, 83, 9, 0.55);
+
+ --sidebar-bg: #F5EBD9;
+ --sidebar-border: rgba(180, 83, 9, 0.16);
+
+ --shadow-glow: 0 0 28px rgba(180, 83, 9, 0.15);
+ --aurora-1: rgba(245, 158, 11, 0.1);
+ --aurora-2: rgba(168, 85, 247, 0.06);
+ --aurora-3: rgba(236, 72, 153, 0.06);
+ --dot-color: rgba(180, 83, 9, 0.07);
+}
+
+/* ── Marvel Theme (Hero Tech & Cosmic) ───────────────────────── */
+[data-dashboard-theme="marvel"] {
+ --bg-base: #0A0E17;
+ --bg-surface: #121824;
+ --bg-elevated: #1B2436;
+ --bg-glass: rgba(18, 24, 36, 0.85);
+ --bg-glass-light: rgba(0, 240, 255, 0.06);
+
+ --accent: #EF4444;
+ --accent-dim: #DC2626;
+ --accent-dark: #991B1B;
+ --accent-glow: rgba(239, 68, 68, 0.25);
+ --accent-glow-md: rgba(239, 68, 68, 0.4);
+
+ --text-primary: #F8FAFC;
+ --text-secondary: #CBD5E1;
+ --text-muted: #94A3B8;
+ --text-accent: #38BDF8;
+
+ --border: rgba(56, 189, 248, 0.22);
+ --border-hover: rgba(239, 68, 68, 0.45);
+ --border-accent: rgba(239, 68, 68, 0.65);
+
+ --sidebar-bg: #070A10;
+ --sidebar-border: rgba(56, 189, 248, 0.18);
+
+ --shadow-glow: 0 0 32px rgba(239, 68, 68, 0.25);
+ --aurora-1: rgba(239, 68, 68, 0.18);
+ --aurora-2: rgba(56, 189, 248, 0.18);
+ --aurora-3: rgba(234, 179, 8, 0.15);
+ --dot-color: rgba(56, 189, 248, 0.08);
+}
+
+[data-theme="light"][data-dashboard-theme="marvel"] {
+ --bg-base: #F1F5F9;
+ --bg-surface: #FFFFFF;
+ --bg-elevated: #E2E8F0;
+ --bg-glass: rgba(255, 255, 255, 0.92);
+ --bg-glass-light: rgba(220, 38, 38, 0.05);
+
+ --accent: #DC2626;
+ --accent-dim: #B91C1C;
+ --accent-dark: #991B1B;
+ --accent-glow: rgba(220, 38, 38, 0.15);
+ --accent-glow-md: rgba(220, 38, 38, 0.28);
+
+ --text-primary: #0F172A;
+ --text-secondary: #334155;
+ --text-muted: #64748B;
+ --text-accent: #0284C7;
+
+ --border: rgba(220, 38, 38, 0.18);
+ --border-hover: rgba(220, 38, 38, 0.38);
+ --border-accent: rgba(220, 38, 38, 0.55);
+
+ --sidebar-bg: #F8FAFC;
+ --sidebar-border: rgba(220, 38, 38, 0.14);
+
+ --shadow-glow: 0 0 28px rgba(220, 38, 38, 0.15);
+ --aurora-1: rgba(220, 38, 38, 0.09);
+ --aurora-2: rgba(2, 132, 199, 0.09);
+ --aurora-3: rgba(202, 138, 4, 0.09);
+ --dot-color: rgba(220, 38, 38, 0.06);
+}
+
+/* ── Anime Theme (Cyber Neon Tokyo) ─────────────────────────── */
+[data-dashboard-theme="anime"] {
+ --bg-base: #0D0914;
+ --bg-surface: #171024;
+ --bg-elevated: #221836;
+ --bg-glass: rgba(23, 16, 36, 0.85);
+ --bg-glass-light: rgba(255, 42, 133, 0.08);
+
+ --accent: #FF2A85;
+ --accent-dim: #E01A6F;
+ --accent-dark: #990D49;
+ --accent-glow: rgba(255, 42, 133, 0.25);
+ --accent-glow-md: rgba(255, 42, 133, 0.42);
+
+ --text-primary: #FDEDF6;
+ --text-secondary: #E0C3FC;
+ --text-muted: #A78BFA;
+ --text-accent: #00F5D4;
+
+ --border: rgba(255, 42, 133, 0.25);
+ --border-hover: rgba(0, 245, 212, 0.5);
+ --border-accent: rgba(255, 42, 133, 0.65);
+
+ --sidebar-bg: #08050D;
+ --sidebar-border: rgba(255, 42, 133, 0.2);
+
+ --shadow-glow: 0 0 32px rgba(255, 42, 133, 0.28);
+ --aurora-1: rgba(255, 42, 133, 0.2);
+ --aurora-2: rgba(0, 245, 212, 0.18);
+ --aurora-3: rgba(168, 85, 247, 0.18);
+ --dot-color: rgba(255, 42, 133, 0.09);
+}
+
+[data-theme="light"][data-dashboard-theme="anime"] {
+ --bg-base: #FAF5FF;
+ --bg-surface: #FFFFFF;
+ --bg-elevated: #F3E8FF;
+ --bg-glass: rgba(255, 255, 255, 0.92);
+ --bg-glass-light: rgba(217, 70, 239, 0.06);
+
+ --accent: #C026D3;
+ --accent-dim: #A21CAF;
+ --accent-dark: #86198F;
+ --accent-glow: rgba(192, 38, 211, 0.16);
+ --accent-glow-md: rgba(192, 38, 211, 0.3);
+
+ --text-primary: #1E1B4B;
+ --text-secondary: #4338CA;
+ --text-muted: #6B21A8;
+ --text-accent: #0891B2;
+
+ --border: rgba(192, 38, 211, 0.2);
+ --border-hover: rgba(192, 38, 211, 0.4);
+ --border-accent: rgba(192, 38, 211, 0.6);
+
+ --sidebar-bg: #FDF4FF;
+ --sidebar-border: rgba(192, 38, 211, 0.15);
+
+ --shadow-glow: 0 0 28px rgba(192, 38, 211, 0.16);
+ --aurora-1: rgba(192, 38, 211, 0.09);
+ --aurora-2: rgba(8, 145, 178, 0.09);
+ --aurora-3: rgba(147, 51, 234, 0.09);
+ --dot-color: rgba(192, 38, 211, 0.07);
+}
+
+/* ── Reset & Layout ───────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html {
+ scroll-behavior: smooth;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-family: var(--font-body);
+ font-size: 15px; line-height: 1.6;
+ color: var(--text-primary);
+ background-color: var(--bg-base);
+ min-height: 100vh;
+ transition: background-color 0.25s ease, color 0.25s ease;
+}
+
+/* Dot grid overlay */
+body::before {
+ content: '';
+ position: fixed; inset: 0;
+ background-image: radial-gradient(circle, var(--dot-color) 1.2px, transparent 1.2px);
+ background-size: 24px 24px;
+ pointer-events: none;
+ z-index: 0;
+}
+
+#root { position: relative; z-index: 1; }
+
+/* ── Keyframes ────────────────────────────────────────────── */
+@keyframes aurora-drift {
+ 0%,100% { transform: translate(0,0) scale(1) rotate(0deg); }
+ 33% { transform: translate(40px,-30px) scale(1.15) rotate(10deg); }
+ 66% { transform: translate(-25px,25px) scale(0.92) rotate(-8deg); }
+}
+@keyframes aurora-drift-2 {
+ 0%,100% { transform: translate(0,0) scale(1); }
+ 40% { transform: translate(-35px,20px) scale(1.1); }
+ 70% { transform: translate(30px,-25px) scale(0.95); }
+}
+@keyframes float-up {
+ 0%,100% { transform: translateY(0); }
+ 50% { transform: translateY(-8px); }
+}
+@keyframes shimmer-text {
+ 0% { background-position: -200% center; }
+ 100% { background-position: 200% center; }
+}
+@keyframes spin { to { transform: rotate(360deg); } }
+@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
+@keyframes pageIn { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
+@keyframes fade-in-up { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:translateY(0); } }
+@keyframes glow-pulse {
+ 0%,100% { box-shadow: 0 0 20px var(--accent-glow); }
+ 50% { box-shadow: 0 0 40px var(--accent-glow-md); }
+}
+@keyframes emoji-float {
+ 0%,100% { transform: translateY(0) rotate(-2deg); }
+ 50% { transform: translateY(-8px) rotate(2deg); }
+}
+
+/* ── Aurora blob util ─────────────────────────────────────── */
+.aurora-blob {
+ position: absolute; border-radius: 50%;
+ filter: blur(72px); pointer-events: none;
+}
+.aurora-blob--1 { background: var(--aurora-1); animation: aurora-drift 10s ease-in-out infinite; }
+.aurora-blob--2 { background: var(--aurora-2); animation: aurora-drift-2 8s ease-in-out infinite; }
+.aurora-blob--3 { background: var(--aurora-3); animation: aurora-drift 12s ease-in-out infinite reverse; }
+
+/* ── Shimmer text util (FIXED FOR LIGHT MODE) ─────────────── */
+.shimmer-text {
+ background-image: linear-gradient(90deg,
+ var(--accent) 0%, #5BFFB0 25%,
+ var(--text-primary) 50%, var(--accent) 75%, #5BFFB0 100%
+ );
+ background-size: 200% auto;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+ color: transparent !important;
+ display: inline-block;
+ animation: shimmer-text 5s linear infinite;
+}
+[data-theme="light"] .shimmer-text {
+ background-image: linear-gradient(90deg,
+ #15803D 0%, #0284C7 25%,
+ #0F172A 50%, #15803D 75%, #0284C7 100%
+ );
+ background-size: 200% auto;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+ color: transparent !important;
+ display: inline-block;
+ animation: shimmer-text 5s linear infinite;
+}
+
+/* ── Glow card util ───────────────────────────────────────── */
+.glow-card {
+ position: relative; border-radius: var(--r-lg);
+ background: var(--bg-glass); border: 1px solid var(--border);
+ backdrop-filter: blur(12px);
+ transition: border-color 0.3s, box-shadow 0.3s, transform 0.2s;
+}
+.glow-card:hover {
+ border-color: var(--border-accent);
+ box-shadow: 0 0 0 1px var(--accent-glow), var(--shadow-glow);
+ transform: translateY(-2px);
+}
+
+/* ── Float util ───────────────────────────────────────────── */
+.float { animation: float-up 3s ease-in-out infinite; }
+
+/* ── Typography ───────────────────────────────────────────── */
+h1,h2,h3,h4,h5,h6 {
+ font-family: var(--font-heading); font-weight: 600;
+ line-height: 1.2; color: var(--text-primary);
+ transition: color 0.25s ease;
+}
+h1 { font-size: clamp(2rem,5vw,3.5rem); font-weight: 700; }
+h2 { font-size: clamp(1.4rem,3vw,2rem); }
+h3 { font-size: 1.2rem; }
+
+p { color: var(--text-secondary); line-height: 1.7; transition: color 0.25s ease; }
+a { color: var(--accent); text-decoration: none; transition: opacity var(--t-fast); }
+a:hover { opacity: 0.8; }
+code { font-family: var(--font-mono); font-size: 0.88em; color: var(--accent); }
+pre { font-family: var(--font-mono); }
+
+/* ── Forms ────────────────────────────────────────────────── */
+button, input, textarea, select { font: inherit; }
+input, textarea, select {
+ background: var(--bg-elevated); border: 1px solid var(--border);
+ border-radius: var(--r-sm); color: var(--text-primary);
+ padding: var(--sp-3) var(--sp-4); outline: none;
+ transition: border-color var(--t-fast), box-shadow var(--t-fast), background-color 0.25s;
+}
+input:focus, textarea:focus, select:focus {
+ border-color: var(--accent-dim); box-shadow: 0 0 0 3px var(--accent-glow);
+}
+textarea { resize: vertical; min-height: 110px; width: 100%; line-height: 1.6; }
+select option { background: var(--bg-elevated); color: var(--text-primary); }
+::placeholder { color: var(--text-muted); }
+
+/* ── Buttons ──────────────────────────────────────────────── */
+.btn {
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
+ padding: 10px 20px; border-radius: var(--r-sm); border: 1px solid transparent;
+ font-weight: 600; font-size: 0.9rem; cursor: pointer;
+ transition: all var(--t-base); white-space: nowrap; text-decoration: none;
+}
+.btn:disabled { opacity: 0.5; cursor: not-allowed; }
+.btn-primary { background: var(--accent); color: #0D1117; border-color: var(--accent); }
+.btn-primary:hover:not(:disabled) { background: var(--accent-dim); box-shadow: var(--shadow-glow); transform: translateY(-1px); }
+[data-theme="light"] .btn-primary { color: #ffffff; background: #15803D; border-color: #15803D; }
+[data-theme="light"] .btn-primary:hover:not(:disabled) { background: #166534; }
+
+.btn-secondary { background: var(--bg-glass-light); color: var(--text-primary); border-color: var(--border); backdrop-filter: blur(8px); }
+.btn-secondary:hover:not(:disabled) { border-color: var(--border-hover); background: rgba(255,255,255,0.08); }
+[data-theme="light"] .btn-secondary:hover:not(:disabled) { background: rgba(0,0,0,0.05); }
+
+.btn-ghost { background: transparent; color: var(--text-secondary); border-color: transparent; }
+.btn-ghost:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-glass-light); }
+.btn-sm { padding: 6px 14px; font-size: 0.82rem; }
+.btn-lg { padding: 14px 28px; font-size: 1rem; }
+
+/* ── Glass card ───────────────────────────────────────────── */
+.card {
+ background: var(--bg-glass); border: 1px solid var(--border);
+ border-radius: var(--r-lg); backdrop-filter: blur(12px);
+ transition: border-color var(--t-base), box-shadow var(--t-base), transform var(--t-base);
+}
+.card:hover { border-color: var(--border-hover); }
+
+/* ── Badge ────────────────────────────────────────────────── */
+.badge {
+ display: inline-flex; align-items: center; gap: 4px;
+ padding: 3px 10px; border-radius: var(--r-full);
+ font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
+}
+.badge-beginner { background: rgba(52,211,153,0.15); color: var(--beginner-color); border: 1px solid rgba(52,211,153,0.3); }
+.badge-explorer { background: rgba(96,165,250,0.15); color: var(--explorer-color); border: 1px solid rgba(96,165,250,0.3); }
+.badge-builder { background: rgba(244,114,182,0.15); color: var(--builder-color); border: 1px solid rgba(244,114,182,0.3); }
+
+/* ── Layout ───────────────────────────────────────────────── */
+.container { max-width: 1280px; margin: 0 auto; padding: 0 var(--sp-6); }
+.page-content { padding-top: calc(var(--navbar-h) + var(--sp-4)); padding-bottom: var(--sp-16); min-height: 100vh; }
+.grid-2 { display: grid; grid-template-columns: repeat(2,1fr); gap: var(--sp-5); }
+.grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: var(--sp-5); }
+.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: var(--sp-5); }
+
+.section-header { display:flex; align-items:flex-end; justify-content:space-between; margin-bottom:var(--sp-6); gap:var(--sp-4); }
+.section-label { font-size:0.75rem; font-weight:600; text-transform:uppercase; letter-spacing:0.12em; color:var(--accent); margin-bottom:var(--sp-2); }
+.divider { height:1px; background:var(--border); margin:var(--sp-6) 0; }
+
+/* ── Skeleton ─────────────────────────────────────────────── */
+.skeleton {
+ background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--bg-glass-light) 50%, var(--bg-elevated) 75%);
+ background-size: 200% 100%; animation: shimmer 1.4s infinite; border-radius: var(--r-sm);
+}
+
+/* ── Scrollbar ────────────────────────────────────────────── */
+::-webkit-scrollbar { width: 6px; height: 6px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
+
+/* ── Utilities ────────────────────────────────────────────── */
+.flex{display:flex;} .flex-col{display:flex;flex-direction:column;}
+.items-center{align-items:center;} .justify-between{justify-content:space-between;}
+.gap-2{gap:var(--sp-2);} .gap-3{gap:var(--sp-3);} .gap-4{gap:var(--sp-4);}
+.text-accent{color:var(--accent);} .text-muted{color:var(--text-muted);}
+.text-sm{font-size:0.875rem;} .font-mono{font-family:var(--font-mono);}
+.font-heading{font-family:var(--font-heading);} .truncate{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
+.anim-fade-up { animation: fade-in-up 0.5s ease both; }
+.page-enter { animation: pageIn var(--t-slow) both; }
+
+/* ── Responsive ───────────────────────────────────────────── */
+@media (max-width:1024px) { .grid-4{grid-template-columns:repeat(2,1fr);} .grid-3{grid-template-columns:repeat(2,1fr);} }
+@media (max-width:640px) { .grid-4,.grid-3,.grid-2{grid-template-columns:1fr;} .container{padding:0 var(--sp-4);} .section-header{flex-direction:column;align-items:flex-start;} }
diff --git a/server/package-lock.json b/server/package-lock.json
index 3fcc765..0f5b259 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -19,6 +19,7 @@
}
},
"..": {
+ "name": "pybe-mern-app",
"version": "1.0.0",
"dependencies": {
"concurrently": "^9.1.2"
@@ -402,6 +403,21 @@
"node": ">= 0.6"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"license": "MIT",
diff --git a/server/src/data/db.json b/server/src/data/db.json
index 6547d2e..c58d7a7 100644
--- a/server/src/data/db.json
+++ b/server/src/data/db.json
@@ -1,7 +1,7 @@
{
"scenarios": [
{
- "_id": "8cf644f0-1402-4763-9542-02bd2ca0d1dc",
+ "_id": "176df397-5a4c-4d35-87d2-43e161f19269",
"title": "Bag Weight Label",
"difficulty": "Beginner",
"concepts": [
@@ -16,11 +16,13 @@
],
"sampleReasoning": "I only need to remember the bag weight, so I would store it with a clear name.",
"effectivenessScore": 96,
- "createdAt": "2026-06-19T11:54:44.732Z",
- "updatedAt": "2026-06-19T11:54:44.732Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "8f065c3d-36c5-48c5-948a-b7bc1a768bc9",
+ "_id": "6a7206fa-0224-498c-bd96-08d8b269b40e",
"title": "Rainy Day Choice",
"difficulty": "Beginner",
"concepts": [
@@ -35,11 +37,13 @@
],
"sampleReasoning": "If it is raining, carry an umbrella. Otherwise, leave it at home.",
"effectivenessScore": 95,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "c4dfb27a-c96b-4ff1-80ff-3bd5b5b12e74",
+ "_id": "a5af078c-03d5-4c87-a90f-85b66a27b272",
"title": "Two Snack Prices",
"difficulty": "Beginner",
"concepts": [
@@ -55,11 +59,13 @@
],
"sampleReasoning": "I would keep the two prices separately and add them to get the total.",
"effectivenessScore": 94,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "3657a91d-1f2b-42d7-bfec-4fbfb80dd648",
+ "_id": "82f17294-f634-497d-aeee-f3161c481acd",
"title": "Greeting by Name",
"difficulty": "Beginner",
"concepts": [
@@ -75,11 +81,13 @@
],
"sampleReasoning": "The computer should store the learner name and place it inside a greeting sentence.",
"effectivenessScore": 93,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "a29775e3-1744-4b5e-b213-ecbb9bf8ec39",
+ "_id": "1eb9a3ea-d850-4193-bfe6-a4a68ff217e5",
"title": "Pass Mark Check",
"difficulty": "Beginner",
"concepts": [
@@ -95,11 +103,13 @@
],
"sampleReasoning": "I would compare the score with the pass mark and decide pass if it is high enough.",
"effectivenessScore": 92,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "734c083f-b08c-4524-ae1a-f2924cd6851a",
+ "_id": "e1d5522d-f7a7-4cce-a550-26f5a5ad3aba",
"title": "Pocket Money Left",
"difficulty": "Beginner",
"concepts": [
@@ -115,11 +125,13 @@
],
"sampleReasoning": "Start with the original money, subtract the spent amount, and store what remains.",
"effectivenessScore": 91,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "1324fea6-96d2-408c-8f58-cd92877f9dbd",
+ "_id": "e1664096-f3bf-47c6-8711-47a5e07409c7",
"title": "Favorite Color List",
"difficulty": "Beginner",
"concepts": [
@@ -134,11 +146,13 @@
],
"sampleReasoning": "Since all values are colors, I would keep them together in one list.",
"effectivenessScore": 90,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "8bb64eb9-5710-433b-bce9-95b4a5b995a1",
+ "_id": "abdc5b68-b7cd-4351-862c-3341573cdf42",
"title": "First Item in a Bag",
"difficulty": "Beginner",
"concepts": [
@@ -154,11 +168,13 @@
],
"sampleReasoning": "The first item is based on its position in the ordered group.",
"effectivenessScore": 89,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "73803faa-165f-4aee-b60f-3e08d470ce84",
+ "_id": "ca1373c9-ab82-4958-a052-6f62549b14b6",
"title": "Attendance Count",
"difficulty": "Beginner",
"concepts": [
@@ -174,11 +190,13 @@
],
"sampleReasoning": "I would count how many names are in the present-students list.",
"effectivenessScore": 88,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "e510ab5f-1f11-46c9-b857-22a50e144e75",
+ "_id": "4f14f1af-f927-4265-829f-4cec5968092e",
"title": "Temperature Message",
"difficulty": "Beginner",
"concepts": [
@@ -194,11 +212,13 @@
],
"sampleReasoning": "If the temperature is above the threshold, show Hot; otherwise show Comfortable.",
"effectivenessScore": 87,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "2a3496ef-bb71-4dde-83f5-37844063678d",
+ "_id": "bef5a856-e527-432b-8d34-40d54074a920",
"title": "Water Bottle Reminder",
"difficulty": "Explorer",
"concepts": [
@@ -213,11 +233,13 @@
],
"sampleReasoning": "For every break, show the same water reminder.",
"effectivenessScore": 96,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "71b887ca-fd3d-4c1a-83a9-f464f7c2a06d",
+ "_id": "5dd0c943-e5b9-4e31-b9dc-c919e4542871",
"title": "Find the Longest Pencil",
"difficulty": "Explorer",
"concepts": [
@@ -233,11 +255,13 @@
],
"sampleReasoning": "Start with one pencil as the longest, then compare each next pencil against it.",
"effectivenessScore": 95,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "bf9c0f8c-308d-4e1f-9e37-9997c7e042b9",
+ "_id": "4c6db219-bd78-4f86-a032-abdb063d3c2d",
"title": "Clean Chore Checklist",
"difficulty": "Explorer",
"concepts": [
@@ -253,11 +277,13 @@
],
"sampleReasoning": "Put chores in a list and handle each chore one by one.",
"effectivenessScore": 94,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "ce225ef0-1111-4ed4-9318-7373018385e6",
+ "_id": "8b046600-f015-49a0-8721-f86698b2a804",
"title": "Movie Age Filter",
"difficulty": "Explorer",
"concepts": [
@@ -273,11 +299,13 @@
],
"sampleReasoning": "For each movie, keep it only if the learner age is at least the minimum age.",
"effectivenessScore": 93,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "cc62f477-44c8-403b-82cf-0b2638ff501b",
+ "_id": "fe279f70-7a3f-40de-917d-40c6948b184f",
"title": "Classroom Supply Lookup",
"difficulty": "Explorer",
"concepts": [
@@ -292,11 +320,13 @@
],
"sampleReasoning": "Each supply has a count, so I would store supply names as keys with counts as values.",
"effectivenessScore": 92,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "26b0c080-124d-4ff8-92ad-4f2380e3e89f",
+ "_id": "65fd8765-b698-4797-86db-5bc99d4ee86a",
"title": "Bus Stop Search",
"difficulty": "Explorer",
"concepts": [
@@ -312,11 +342,13 @@
],
"sampleReasoning": "Look at each stop and compare it with the stop I want.",
"effectivenessScore": 91,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "169fc6d5-7b9b-483d-87ee-325e2d497c90",
+ "_id": "e86d7966-9bdf-4ac7-8e2e-ceafe3b26a24",
"title": "Average Practice Score",
"difficulty": "Explorer",
"concepts": [
@@ -332,11 +364,13 @@
],
"sampleReasoning": "Find the total of all scores, count how many scores there are, then divide.",
"effectivenessScore": 90,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "e0bdac7d-2033-408f-833c-3f2734690398",
+ "_id": "6d10f76d-09bb-4868-9d5f-fc78b7b35144",
"title": "Separate Even Roll Numbers",
"difficulty": "Explorer",
"concepts": [
@@ -352,11 +386,13 @@
],
"sampleReasoning": "A roll number is even if dividing by two leaves no remainder.",
"effectivenessScore": 89,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "decf41d5-8529-4685-b354-d02090bbb639",
+ "_id": "84b1d7c3-3490-4647-b3c8-af7db17e724d",
"title": "Capitalize Name Tags",
"difficulty": "Explorer",
"concepts": [
@@ -372,11 +408,13 @@
],
"sampleReasoning": "For each name, convert it to title case before printing the tag.",
"effectivenessScore": 88,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "418bec64-f75a-40fa-b849-42edba0d3939",
+ "_id": "ede44093-2bf0-48a8-9e82-a22e114e3b42",
"title": "Find Missing Homework",
"difficulty": "Explorer",
"concepts": [
@@ -392,11 +430,13 @@
],
"sampleReasoning": "Take everyone in the class and remove the students who submitted.",
"effectivenessScore": 87,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "07295b48-1037-4654-995f-df57d6fd7a7b",
+ "_id": "fa9143e6-decd-4835-8988-0ea423f742a5",
"title": "Reusable Discount Rule",
"difficulty": "Builder",
"concepts": [
@@ -412,11 +452,13 @@
],
"sampleReasoning": "The helper needs the bill amount and should return the final price after applying the rule.",
"effectivenessScore": 96,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "88a35898-eba5-47ca-adf2-0805d633ea3d",
+ "_id": "d487265f-43ed-4d86-962d-c751214bfb49",
"title": "Mini Quiz Checker",
"difficulty": "Builder",
"concepts": [
@@ -432,11 +474,13 @@
],
"sampleReasoning": "Compare the learner answer with the correct answer and return whether they match.",
"effectivenessScore": 95,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "9a8e5318-6fad-44ed-8e60-03da14d3f4db",
+ "_id": "8a5cf1cc-ec3a-408c-9c66-3d194b11fc26",
"title": "Step Counter Function",
"difficulty": "Builder",
"concepts": [
@@ -452,11 +496,13 @@
],
"sampleReasoning": "The function should take step counts, add them, and give back the total.",
"effectivenessScore": 94,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "34c237f5-a9c8-478c-8980-8505ba0d2b94",
+ "_id": "9949950c-c9a6-46f6-945c-d91b02fd2a89",
"title": "Safe Username Maker",
"difficulty": "Builder",
"concepts": [
@@ -472,11 +518,13 @@
],
"sampleReasoning": "Make the name lowercase and remove spaces so it can be used as a username.",
"effectivenessScore": 93,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "9bcdd1e0-2f8a-416e-a0e2-60e188e33dcd",
+ "_id": "0d00443b-c2f3-4a88-b486-1513a6489634",
"title": "Retry Until Valid",
"difficulty": "Builder",
"concepts": [
@@ -492,11 +540,13 @@
],
"sampleReasoning": "Keep asking while the number is not positive, then stop once it is valid.",
"effectivenessScore": 92,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "54de8788-6a15-4377-a15f-e4cb73632c42",
+ "_id": "eaea73c8-16a6-4b11-a8c6-ec716d4e1949",
"title": "Simple Score Report",
"difficulty": "Builder",
"concepts": [
@@ -512,11 +562,13 @@
],
"sampleReasoning": "Return a dictionary with the learner name and score as labeled values.",
"effectivenessScore": 91,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "d98ea7cd-5792-41b6-9a66-03267e936bc1",
+ "_id": "194dfce1-f6e9-4e72-b1b6-031475c1d45f",
"title": "Task Status Updater",
"difficulty": "Builder",
"concepts": [
@@ -532,11 +584,13 @@
],
"sampleReasoning": "Find the task by name and change its status from pending to done.",
"effectivenessScore": 90,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "e5b52a26-a324-40eb-84f7-fb757eecdceb",
+ "_id": "18dbedce-62cc-4c40-8a9c-9f38acd4f372",
"title": "Small Receipt Builder",
"difficulty": "Builder",
"concepts": [
@@ -552,11 +606,13 @@
],
"sampleReasoning": "Combine the item name and price into one clear sentence.",
"effectivenessScore": 89,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "f57b40d9-7510-43dc-ae04-230a9d8331b3",
+ "_id": "d1567e5c-ed3c-4d94-9d9e-718de6558953",
"title": "Choose Next Scenario",
"difficulty": "Builder",
"concepts": [
@@ -572,11 +628,13 @@
],
"sampleReasoning": "If the score is high choose harder, if low choose easier, otherwise stay similar.",
"effectivenessScore": 88,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
},
{
- "_id": "5cf0ecd0-c690-45d4-a9b4-12e36403b125",
+ "_id": "5e179d35-1298-4959-92f9-311724fd7231",
"title": "Reflection Keyword Finder",
"difficulty": "Builder",
"concepts": [
@@ -592,8 +650,1508 @@
],
"sampleReasoning": "Look for words like confused or stuck and flag the reflection if they appear.",
"effectivenessScore": 87,
- "createdAt": "2026-06-19T11:54:44.733Z",
- "updatedAt": "2026-06-19T11:54:44.733Z"
+ "theme": "classic",
+ "caseStudyId": null,
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "3c9b1cd2-fedf-432e-9895-3f647333af6b",
+ "title": "Ramu's First Chai Variable",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "Ramu runs a tiny chai stall outside IIT Ropar's gate. He sells only one type of chai: ginger tea at ₹15 a cup. He wants to track how many cups he sold today.",
+ "prompt": "Ramu asks you: \"I only need to remember one thing — how many cups I sold. What should I call it?\" How would you help him store this single piece of information?",
+ "objectives": [
+ "Identify one value to track",
+ "Give it a meaningful name",
+ "Understand why naming matters"
+ ],
+ "sampleReasoning": "Ramu needs one piece of information — cups_sold. Naming it clearly means he can use it later to calculate earnings.",
+ "effectivenessScore": 95,
+ "theme": "chai-stall",
+ "caseStudyId": "cs-chai-stall",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Dicts → Functions",
+ "themeEmoji": "🍵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "b75792ee-5609-42ce-b801-541e847d5c57",
+ "title": "Ramu Adds More Chai Types",
+ "difficulty": "Beginner",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Ramu's stall became popular. Now he sells 8 types: ginger, masala, elaichi, tulsi, lemon, mint, kadak, and cutting. He has 8 separate variables: chai1, chai2... chai8. His notebook is a mess.",
+ "prompt": "Ramu shouts: \"I have 8 variables and if I add one more chai, I need a 9th! This is madness!\" What single structure could hold ALL his chai types together?",
+ "objectives": [
+ "Feel the pain of too many variables",
+ "Discover the concept of grouping",
+ "Map grouping to a Python list"
+ ],
+ "sampleReasoning": "All 8 items are chai types. They belong together. One list called chai_types can hold all of them and I can add more without creating new variables.",
+ "effectivenessScore": 97,
+ "theme": "chai-stall",
+ "caseStudyId": "cs-chai-stall",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Dicts → Functions",
+ "themeEmoji": "🍵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "07eba861-5753-4ab0-9e02-e15ae464ea29",
+ "title": "Ramu's Price Lookup Problem",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "Ramu now has two parallel lists: chai_types = [\"ginger\", \"masala\", ...] and prices = [15, 20, ...]. A customer asks \"How much is tulsi?\" Ramu has to count to position 4 in one list, then find position 4 in another. He made a mistake yesterday — he reordered the types list but forgot to reorder prices.",
+ "prompt": "Ramu lost ₹200 yesterday because his lists got out of sync. He says: \"I want to look up a chai by NAME, not by number.\" What structure lets you store the name AND price together as a pair?",
+ "objectives": [
+ "Understand why parallel lists break",
+ "Discover named lookup",
+ "Map key-value pairs to a dictionary"
+ ],
+ "sampleReasoning": "A dictionary maps each chai name directly to its price. \"tulsi\" always points to its correct price — no counting, no sync issues.",
+ "effectivenessScore": 98,
+ "theme": "chai-stall",
+ "caseStudyId": "cs-chai-stall",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Dicts → Functions",
+ "themeEmoji": "🍵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "3152fdc0-2946-473a-8a17-328467bee743",
+ "title": "Ramu's Evening Revenue Calculator",
+ "difficulty": "Builder",
+ "concepts": [
+ "functions"
+ ],
+ "context": "Every evening, Ramu calculates revenue for each chai type: cups_sold × price. He copy-pasted this calculation 8 times in his notebook — once for each chai. When he raised masala chai price, he had to update 3 different places and still missed one.",
+ "prompt": "Ramu says: \"I write the same calculation for every single chai type. There has to be a better way.\" How would you package this repeatable calculation so Ramu writes it ONCE and reuses it for every chai?",
+ "objectives": [
+ "Identify repeated logic",
+ "Package it as a reusable process",
+ "Map the process to a Python function"
+ ],
+ "sampleReasoning": "A function called calculate_revenue takes cups_sold and price_per_cup, then returns the total. Write once, call it for each chai type.",
+ "effectivenessScore": 96,
+ "theme": "chai-stall",
+ "caseStudyId": "cs-chai-stall",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Dicts → Functions",
+ "themeEmoji": "🍵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "f8f13548-1c6a-448d-833f-2c67dab71217",
+ "title": "ISRO: Three Telemetry Variables",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "You are an intern at ISRO's Chandrayaan-3 mission control. The spacecraft has three critical readings: altitude (in km), fuel level (in %), and velocity (in m/s). Right now you have three separate variables on your screen.",
+ "prompt": "The mission director asks: \"What are the three values we must track at all times?\" How would you name and store these three critical pieces of mission data?",
+ "objectives": [
+ "Name critical values meaningfully",
+ "Understand that names carry meaning in code",
+ "Store mission-critical data as variables"
+ ],
+ "sampleReasoning": "altitude_km, fuel_percent, velocity_ms — each name tells exactly what unit and what it measures. Naming matters in space.",
+ "effectivenessScore": 94,
+ "theme": "isro",
+ "caseStudyId": "cs-isro",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Dicts → Sets → Modules",
+ "themeEmoji": "🚀",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "5cd2566d-61f9-4b64-bcb8-01e8d1817e26",
+ "title": "ISRO: Twelve Subsystem Chaos",
+ "difficulty": "Explorer",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Chandrayaan-3 has 12 subsystems: propulsion, thermal, communication, power, navigation, attitude control, payload, avionics, structure, mechanisms, software, and ground systems. A second intern added three more subsystems without telling you. Now you have 15 separate variables. Someone's variable is named temp2 and nobody knows what it means.",
+ "prompt": "The mission director is furious: \"Why do we have 15 separate variables? One new intern and the whole codebase breaks!\" What structure would hold all subsystem names together and survive additions without chaos?",
+ "objectives": [
+ "Understand chaos from ungrouped variables",
+ "Group related items under one name",
+ "Discover lists as a solution to this pain"
+ ],
+ "sampleReasoning": "A list called subsystems holds all 12 names. Adding a new one is just appending to the list — no new variables, no naming chaos.",
+ "effectivenessScore": 97,
+ "theme": "isro",
+ "caseStudyId": "cs-isro",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Dicts → Sets → Modules",
+ "themeEmoji": "🚀",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "3154ed90-5c47-43c5-bc67-2d8e56e61186",
+ "title": "ISRO: FAIL at Position 7",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "It is 2:17 AM. The status board shows: [OK, OK, OK, FAIL, OK, OK, OK, OK, OK, OK, OK, OK]. Subsystem at index 3 failed. But which subsystem IS index 3? You have to mentally count: propulsion=0, thermal=1, communication=2, power=3. It's 2 AM. You miscounted twice.",
+ "prompt": "The mission director shouts: \"I don't care about position 3! I need to know which SUBSYSTEM failed by NAME!\" What structure would let you look up subsystem status by name, not by position?",
+ "objectives": [
+ "Feel the danger of position-based lookup",
+ "Discover named lookup under pressure",
+ "Map subsystem → status as dictionary"
+ ],
+ "sampleReasoning": "A dictionary maps each subsystem name to its status. status[\"power\"] = \"FAIL\" — no counting, no miscount at 2 AM.",
+ "effectivenessScore": 99,
+ "theme": "isro",
+ "caseStudyId": "cs-isro",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Dicts → Sets → Modules",
+ "themeEmoji": "🚀",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "8175533a-7ae7-45b6-9d47-065a9d2a0282",
+ "title": "ISRO: Duplicate Telemetry Signals",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "Three ground stations — Bengaluru, Mauritius, and Bhopal — all send telemetry. Due to a relay issue, the Bengaluru station sent the same altitude reading 14 times. Your data array has the same reading repeated across many entries. The average is now wrong.",
+ "prompt": "The data scientist says: \"We're getting duplicate readings from the same timestamp. We need only UNIQUE readings.\" What structure automatically guarantees that each value appears only once, no matter how many times it is submitted?",
+ "objectives": [
+ "Understand the cost of duplicates",
+ "Discover uniqueness as a data property",
+ "Map uniqueness guarantee to Python sets"
+ ],
+ "sampleReasoning": "A set only holds unique values. Adding the same altitude reading 14 times still results in one entry. Sets solve the duplicate problem automatically.",
+ "effectivenessScore": 96,
+ "theme": "isro",
+ "caseStudyId": "cs-isro",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Dicts → Sets → Modules",
+ "themeEmoji": "🚀",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "aa0629ed-f35c-4850-aa10-c63cd1152db3",
+ "title": "Filter App: The First Variable",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "You are building a photo filter app. Right now you have one filter: \"Warm Vintage.\" It has one setting: brightness (set to 1.3 by default).",
+ "prompt": "Your app has one setting for one filter. How do you store that brightness value so you can use it to process the photo?",
+ "objectives": [
+ "Store a single filter setting",
+ "Name it meaningfully",
+ "Understand how naming connects to usage"
+ ],
+ "sampleReasoning": "brightness = 1.3 — a named variable holds the setting and can be referenced whenever the filter is applied.",
+ "effectivenessScore": 91,
+ "theme": "instagram",
+ "caseStudyId": "cs-instagram",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Dicts → Functions → Classes",
+ "themeEmoji": "📸",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "c6ed37da-2a2e-4ee0-92b4-460a4678bbc4",
+ "title": "Filter App: Twenty Filters, Twenty Variables",
+ "difficulty": "Explorer",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Your filter app now has 20 filters: Warm Vintage, Neon Dreams, Faded Film, Ocean Breeze... You have 20 separate variables: filter1, filter2... filter20. Users swipe through filters. When they swipe right to filter number 7, your code tries to access filter7 — but you can't swipe to a variable name.",
+ "prompt": "A friend tries your app and says: \"Swiping is broken — I can only see the first filter.\" Your code cannot loop over 20 separate variables. What single structure would hold all 20 filter names so your swipe gesture can navigate them by position?",
+ "objectives": [
+ "Understand that variables cannot be iterated",
+ "Discover that lists enable position-based navigation",
+ "Connect swiping to list indexing"
+ ],
+ "sampleReasoning": "A list called filters holds all 20 names. swipe_right moves the index forward by 1. filters[current_index] gives the active filter.",
+ "effectivenessScore": 97,
+ "theme": "instagram",
+ "caseStudyId": "cs-instagram",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Dicts → Functions → Classes",
+ "themeEmoji": "📸",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "b59c6a56-b977-4c7f-8628-45d0953b3761",
+ "title": "Filter App: Five Settings Per Filter",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "Each filter now has 5 settings: brightness, contrast, saturation, hue_shift, and grain. You have 5 parallel lists: brightness_list[7], contrast_list[7], saturation_list[7]... You updated the brightness list for \"Neon Dreams\" but forgot to update the contrast list. The filter looks broken.",
+ "prompt": "You wasted 3 hours debugging because your 5 lists got out of sync. A mentor says: \"What if each filter was ONE complete package with all its settings together?\" What structure bundles multiple named settings into a single unit?",
+ "objectives": [
+ "Feel the pain of parallel lists diverging",
+ "Discover bundling related data together",
+ "Map a filter's settings to a dictionary"
+ ],
+ "sampleReasoning": "A dictionary for each filter: {\"brightness\": 1.3, \"contrast\": 1.1, \"saturation\": 1.4, \"hue_shift\": 10, \"grain\": 0.2}. All settings travel together — they can't fall out of sync.",
+ "effectivenessScore": 98,
+ "theme": "instagram",
+ "caseStudyId": "cs-instagram",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Dicts → Functions → Classes",
+ "themeEmoji": "📸",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "6d5d0df1-1694-4ccf-ade0-bf0d9fdfd6a1",
+ "title": "Filter App: Copy-Paste Logic Breaks",
+ "difficulty": "Builder",
+ "concepts": [
+ "functions"
+ ],
+ "context": "You apply filters by copy-pasting the same apply_filter logic for each of your 20 filters. When you want to add a \"fade\" effect that dims the edges, you have to add it in 20 places. You added it in 19 places and forgot filter number 14. Users are complaining that \"Moody Cinema\" doesn't fade.",
+ "prompt": "A senior dev looks at your code and says: \"You have the same 15 lines copied 20 times. If you change anything, you'll miss one. EVERY time.\" How would you package the apply-filter logic so you write it once and it works for every filter?",
+ "objectives": [
+ "Experience the maintenance nightmare of copy-pasted logic",
+ "Understand DRY: Don't Repeat Yourself",
+ "Package repeatable logic as a function"
+ ],
+ "sampleReasoning": "def apply_filter(photo, filter_settings): takes a photo and a filter dict, applies all settings, returns the modified photo. Called once per filter — no copy-paste.",
+ "effectivenessScore": 96,
+ "theme": "instagram",
+ "caseStudyId": "cs-instagram",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Dicts → Functions → Classes",
+ "themeEmoji": "📸",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "935d7100-2050-495b-b893-6175094f20fa",
+ "title": "Hostel Startup: One Item, Two Variables",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "It is 2 AM in a hostel room. Three friends — Arjun, Meera, and Dev — just launched \"HungerFix,\" a food delivery app for their campus. They have exactly one item: Maggi. Price: ₹30.",
+ "prompt": "The friends need the app to remember what they sell and for how much. What are the two pieces of information they absolutely need to store, and how would you name them clearly?",
+ "objectives": [
+ "Identify the minimum data needed",
+ "Name variables descriptively",
+ "Understand that variables hold the state of the app"
+ ],
+ "sampleReasoning": "item_name = \"Maggi\" and item_price = 30. Simple, named, and the whole app can reference these two values.",
+ "effectivenessScore": 93,
+ "theme": "food-delivery",
+ "caseStudyId": "cs-food-delivery",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Dicts → Sets → try/except",
+ "themeEmoji": "🍕",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "22aedc9e-d31a-4037-87f5-ee26876be744",
+ "title": "Hostel Startup: Fifteen Items, No Search",
+ "difficulty": "Explorer",
+ "concepts": [
+ "lists"
+ ],
+ "context": "By night 3, HungerFix has 15 items: Maggi, Bread-Omelette, Poha, Upma, Vada Pav... Users are texting \"Do you have Poha?\" Arjun has to scroll through 15 separate variables to check. He also cannot display a menu — you can't print 15 separate variables in a loop.",
+ "prompt": "Dev says: \"We can't even show users a menu because we have 15 variables. I can't loop over variables!\" What structure would let you store all 15 items so you can loop through them, display them, and search them?",
+ "objectives": [
+ "Understand that iteration requires a collection",
+ "Discover that loops need lists",
+ "Connect menu display to list traversal"
+ ],
+ "sampleReasoning": "menu = [\"Maggi\", \"Bread-Omelette\", \"Poha\", ...]. Now for item in menu: print(item) displays the whole menu. And \"Poha\" in menu checks availability instantly.",
+ "effectivenessScore": 96,
+ "theme": "food-delivery",
+ "caseStudyId": "cs-food-delivery",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Dicts → Sets → try/except",
+ "themeEmoji": "🍕",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "61235c93-4e02-4fa0-a0f3-84c97250185d",
+ "title": "Hostel Startup: The Duplicate Biryani Crisis",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "Night 10. Two friends — Ananya and Rohan — both order the last biryani at 11:58 PM simultaneously. Both orders go through. The friends running the stall have only ONE biryani. They cook two. Costs double. One customer still waits.",
+ "prompt": "Meera says: \"We need to track active orders, but we can't allow the SAME item to be ordered twice if only one is in stock.\" What structure would automatically prevent the same order from appearing twice — no manual checking needed?",
+ "objectives": [
+ "Understand the cost of duplicates in real systems",
+ "Discover sets as a membership uniqueness guarantee",
+ "Apply set membership to order deduplication"
+ ],
+ "sampleReasoning": "active_orders = set(). When Ananya orders biryani: if \"biryani\" not in active_orders, add it. When Rohan orders: \"biryani\" is already in the set — reject it. Sets prevent duplicates automatically.",
+ "effectivenessScore": 97,
+ "theme": "food-delivery",
+ "caseStudyId": "cs-food-delivery",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Dicts → Sets → try/except",
+ "themeEmoji": "🍕",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "9cddfd64-708d-44d7-a4fd-21d00ea4d123",
+ "title": "Hostel Startup: The Crashed Order History",
+ "difficulty": "Builder",
+ "concepts": [
+ "error handling",
+ "try/except"
+ ],
+ "context": "Night 14. A user types a quantity of -3 samosas. The app tries to calculate the bill: -3 × ₹12 = -₹36. The app charges them NEGATIVE money and then crashes trying to write to the orders file. The crash wipes the entire order history. 47 orders gone.",
+ "prompt": "Dev stares at the empty orders file at 3 AM and says: \"A user broke our app with a negative number. We had no plan for this.\" How would you build a safety net that catches bad input BEFORE it reaches the calculation and file-write — so the app never crashes and data is never lost?",
+ "objectives": [
+ "Understand that real programs receive unexpected input",
+ "Discover defensive programming",
+ "Map error catching to try/except"
+ ],
+ "sampleReasoning": "Wrap the quantity input and bill calculation in a try block. If quantity is negative, raise a ValueError. The except block shows the user an error without crashing — and the order history is never touched.",
+ "effectivenessScore": 98,
+ "theme": "food-delivery",
+ "caseStudyId": "cs-food-delivery",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Dicts → Sets → try/except",
+ "themeEmoji": "🍕",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "d674a822-dfe2-43d4-8543-5d2f3510dfa1",
+ "title": "Playlist: One Mood, One Genre",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "You are building an app that learns your music taste. Right now it knows one thing: your current mood. When you're in a \"study\" mood, it plays lo-fi. One mood, one genre.",
+ "prompt": "The app needs to remember what mood you are in right now. How would you store the current mood so the app can decide what to play?",
+ "objectives": [
+ "Store state as a variable",
+ "Understand that apps need to remember things",
+ "Connect variable to decision-making"
+ ],
+ "sampleReasoning": "current_mood = \"study\" — one variable holds the current state, and the app can check it to select a playlist.",
+ "effectivenessScore": 90,
+ "theme": "ai-playlist",
+ "caseStudyId": "cs-ai-playlist",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Loops → Sets → Dicts → Functions",
+ "themeEmoji": "🎵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "6ecc4ac0-6446-4b15-88ae-70440d5e004b",
+ "title": "Playlist: Twelve Genres, No Loop",
+ "difficulty": "Explorer",
+ "concepts": [
+ "lists",
+ "loops"
+ ],
+ "context": "Your app now knows 12 genres you love: lo-fi, jazz, indie, classical, synthwave, ambient, folk, blues, punk, metal, hip-hop, and bossa nova. You have 12 separate variables. The recommendation engine needs to check all of them. You're writing: check_genre1(), check_genre2()... twelve times.",
+ "prompt": "Your code has 12 separate function calls — one for each genre. Adding a 13th genre means adding another line. The recommendation logic cannot adapt dynamically. What structure lets you loop through all genres with a single repeated action?",
+ "objectives": [
+ "Understand that repetitive calls need loops",
+ "Discover that lists enable dynamic iteration",
+ "Connect recommendation loop to list traversal"
+ ],
+ "sampleReasoning": "genres = [\"lo-fi\", \"jazz\", ...]. for genre in genres: check_genre(genre) — one loop handles 12 or 1200 genres. Adding a new genre is one append.",
+ "effectivenessScore": 95,
+ "theme": "ai-playlist",
+ "caseStudyId": "cs-ai-playlist",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Loops → Sets → Dicts → Functions",
+ "themeEmoji": "🎵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "d4b998ae-4949-4c3d-9522-d89273fdeea9",
+ "title": "Playlist: Shape of You, Again",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "Your recommendation engine keeps surfacing \"Shape of You\" — you've rated it 4 stars and it keeps showing up. You've heard it 23 times this week. Your recently_played list has the same 5 songs repeated across 200 entries.",
+ "prompt": "You tell the app: \"I want variety. Never play the same song twice in one session.\" The recently_played list doesn't prevent repeats because lists allow duplicates. What structure would automatically guarantee that each song in a session appears exactly once, no matter how many times the engine tries to add it?",
+ "objectives": [
+ "Experience the frustration of duplicates in recommendations",
+ "Discover sets as a uniqueness enforcer",
+ "Apply sets to deduplication of a playlist"
+ ],
+ "sampleReasoning": "session_played = set(). When the engine picks a song, check: if song not in session_played, play it and add to the set. Sets reject duplicates — \"Shape of You\" gets added once and stays there.",
+ "effectivenessScore": 96,
+ "theme": "ai-playlist",
+ "caseStudyId": "cs-ai-playlist",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Loops → Sets → Dicts → Functions",
+ "themeEmoji": "🎵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "9012f550-5086-4dd7-900e-5152a8f72f2d",
+ "title": "Playlist: Storing Play Count AND Rating",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "The app needs to remember two things per song: how many times you played it, and what you rated it (1–5 stars). You have two parallel lists: play_counts[i] and ratings[i]. You sorted one list by count and forgot to sort the other. A song shows a 5-star rating next to the wrong play count.",
+ "prompt": "Your recommendation score is wrong because the two lists got out of sync again. A friend suggests: \"What if each song carried all its own information — play count AND rating — as one complete package?\" What structure bundles multiple named values for one song together?",
+ "objectives": [
+ "Experience sync failure of parallel lists",
+ "Discover that dictionaries bundle related data",
+ "Map a song's stats to a dictionary"
+ ],
+ "sampleReasoning": "songs = {\"Shape of You\": {\"play_count\": 23, \"rating\": 4}}. The song carries its own data. Sort by play_count? The rating comes along automatically.",
+ "effectivenessScore": 97,
+ "theme": "ai-playlist",
+ "caseStudyId": "cs-ai-playlist",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Loops → Sets → Dicts → Functions",
+ "themeEmoji": "🎵",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "63b51ace-245d-4b9f-b82f-8c31f564fcab",
+ "title": "Kota: Your JEE Rank Variable",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "You are a JEE aspirant at a Kota coaching center. You just got your mock test rank: 4,217. That's your current rank. Everything in your day — which batch you're in, which counselor you see — depends on this one number.",
+ "prompt": "The coaching center's system needs to store your rank so it can decide your batch placement. What would you call this piece of information, and why does the name matter?",
+ "objectives": [
+ "Store a single important value",
+ "Choose a meaningful name over a generic one",
+ "Understand how names convey intent"
+ ],
+ "sampleReasoning": "my_jee_rank = 4217 — the name tells anyone reading the code exactly what this number represents. rank = 4217 is fine, but x = 4217 tells you nothing.",
+ "effectivenessScore": 92,
+ "theme": "kota",
+ "caseStudyId": "cs-kota",
+ "themeStep": 1,
+ "themeArc": "Variables → Lists → Dicts → Sorting → Nested structures",
+ "themeEmoji": "📚",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "36db0896-784c-4602-8e48-e934e4b0ac13",
+ "title": "Kota: Thirty Batchmates, Thirty Variables",
+ "difficulty": "Explorer",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Your batch has 30 students. The director wants the batch average rank. You have 30 separate variables: rank_arjun, rank_priya, rank_dev... To calculate the average, you add all 30 variables manually. A new student joins — you add rank_newstudent and forget to include them in the average calculation.",
+ "prompt": "The director asks: \"Why does adding one student break the average?\" The answer is: you are manually managing 30 separate names. What structure would let you add a new rank in ONE place and have the average work automatically?",
+ "objectives": [
+ "Feel the fragility of ungrouped data",
+ "Discover that collections allow dynamic computation",
+ "Connect list to automatic aggregate operations"
+ ],
+ "sampleReasoning": "batch_ranks = [4217, 2891, 6043, ...]. To add a student: batch_ranks.append(new_rank). Average: sum(batch_ranks) / len(batch_ranks). Automatic — no manual updates.",
+ "effectivenessScore": 96,
+ "theme": "kota",
+ "caseStudyId": "cs-kota",
+ "themeStep": 2,
+ "themeArc": "Variables → Lists → Dicts → Sorting → Nested structures",
+ "themeEmoji": "📚",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "15782233-e51f-4c5f-be06-4d6b89a14478",
+ "title": "Kota: Find Priya's Rank by Name",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "A parent calls and asks: \"What is Priya Sharma's rank?\" You have a list of 30 ranks. You have to find which position Priya is at, then look up that position in the rank list. The director is watching. It takes 40 seconds.",
+ "prompt": "The director says: \"In a real coaching center, I need to look up any student's rank by NAME — not by finding their position first.\" What structure maps each student's name directly to their rank?",
+ "objectives": [
+ "Experience slow sequential lookup under pressure",
+ "Discover named direct lookup",
+ "Map student → rank as a dictionary"
+ ],
+ "sampleReasoning": "ranks = {\"Priya Sharma\": 2891, \"Arjun Dev\": 4217, ...}. ranks[\"Priya Sharma\"] gives her rank immediately — no searching needed.",
+ "effectivenessScore": 97,
+ "theme": "kota",
+ "caseStudyId": "cs-kota",
+ "themeStep": 3,
+ "themeArc": "Variables → Lists → Dicts → Sorting → Nested structures",
+ "themeEmoji": "📚",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "cf42cbf0-c4e0-47ec-ab85-6652231ff7b9",
+ "title": "Kota: The Merit List Algorithm",
+ "difficulty": "Builder",
+ "concepts": [
+ "sorting",
+ "dictionaries",
+ "algorithms"
+ ],
+ "context": "The director wants the top 10 students for the merit scholarship. You have ranks = {\"Priya\": 2891, \"Arjun\": 4217, ...}. Lower rank number = better performance. You need to sort by rank value, handle ties, and take the top 10.",
+ "prompt": "The director says: \"I need the merit list NOW — sorted by rank, ties broken by name alphabetically, top 10 only.\" What sequence of reasoning steps turns the dictionary into a sorted, trimmed merit list?",
+ "objectives": [
+ "Understand sorting as an algorithm, not magic",
+ "Sort a dictionary by value",
+ "Slice a result to a required size"
+ ],
+ "sampleReasoning": "Sort the dictionary items by rank value (ascending), then alphabetically for ties. sorted(ranks.items(), key=lambda x: (x[1], x[0]))[:10] gives the top 10 merit list.",
+ "effectivenessScore": 95,
+ "theme": "kota",
+ "caseStudyId": "cs-kota",
+ "themeStep": 4,
+ "themeArc": "Variables → Lists → Dicts → Sorting → Nested structures",
+ "themeEmoji": "📚",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "816db01e-31ca-4eeb-8a47-0fb08feb94cd",
+ "title": "Potion Ingredient Dosage",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "Severus Snape asks you to measure Boomslang Skin for Polyjuice Potion.",
+ "prompt": "How would you store the weight in grams so your cauldron brewing notes remember it?",
+ "objectives": [
+ "Identify single value",
+ "Assign a clear name",
+ "Understand variable storage"
+ ],
+ "sampleReasoning": "boomslang_weight = 25 — store the exact weight in a named variable to use in the recipe.",
+ "effectivenessScore": 96,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 1,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "728a59f3-d3e2-4a3b-8ca8-f937b481ec72",
+ "title": "Spellbook Spell List",
+ "difficulty": "Beginner",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Harry Potter wants to keep track of all defensive spells learned in DADA class.",
+ "prompt": "How would you group \"Expelliarmus\", \"Stupefy\", and \"Protego\" into a single structure?",
+ "objectives": [
+ "Group spells together",
+ "Recognize list ordering",
+ "Map spells to a list"
+ ],
+ "sampleReasoning": "defense_spells = [\"Expelliarmus\", \"Stupefy\", \"Protego\"] — keep them in one list for fast casting access.",
+ "effectivenessScore": 97,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 2,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "83d2ec1d-ef0c-4a32-aeb4-6595510a46a2",
+ "title": "House Point Counter",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "Professor McGonagall needs to track House Points for Gryffindor, Slytherin, Ravenclaw, and Hufflepuff.",
+ "prompt": "How would you store each house name directly paired with its score?",
+ "objectives": [
+ "Map house to score",
+ "Enable direct lookup",
+ "Use a dictionary"
+ ],
+ "sampleReasoning": "house_points = {\"Gryffindor\": 450, \"Slytherin\": 420, \"Ravenclaw\": 390, \"Hufflepuff\": 380} maps house names to scores directly.",
+ "effectivenessScore": 98,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 3,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "3c6f59cb-78dc-4259-b9c0-6a793464a442",
+ "title": "Cauldron Stirring Helper",
+ "difficulty": "Builder",
+ "concepts": [
+ "functions"
+ ],
+ "context": "Brewing Felix Felicis requires clockwise stirring every 3 minutes.",
+ "prompt": "How would you design a reusable helper function that calculates total stirs required?",
+ "objectives": [
+ "Create reusable brewing logic",
+ "Accept duration parameter",
+ "Return stir count"
+ ],
+ "sampleReasoning": "def calculate_stirs(minutes): return minutes * 4 — write once and reuse for any potion recipe.",
+ "effectivenessScore": 96,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 4,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "de268968-7f2e-48a5-ab20-c25cb5942f4d",
+ "title": "Unforgivable Curse Deduplicator",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "The Ministry of Magic receives duplicate dark magic spell reports from multiple owls.",
+ "prompt": "How would you automatically ensure each reported spell is unique with no duplicates?",
+ "objectives": [
+ "Eliminate duplicate reports",
+ "Guarantee uniqueness",
+ "Map to Python sets"
+ ],
+ "sampleReasoning": "dark_spells = set(owl_reports) automatically removes duplicates.",
+ "effectivenessScore": 95,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 1,
+ "themeEmoji": "🪄",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "6397e82a-9465-4d14-8e7e-b2946a441607",
+ "title": "Arc Reactor Output Voltage",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "Tony Stark needs J.A.R.V.I.S. to monitor the Mark 85 Arc Reactor power level.",
+ "prompt": "How would you store the current voltage percentage in code?",
+ "objectives": [
+ "Identify critical suit value",
+ "Store in named variable",
+ "Reference during combat"
+ ],
+ "sampleReasoning": "reactor_voltage = 98.5 — keep suit power level in a named variable.",
+ "effectivenessScore": 96,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 1,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "53804ab1-8755-4772-b6bb-be76ee348a95",
+ "title": "Avengers Emergency Roster",
+ "difficulty": "Beginner",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Captain America calls an emergency assembly for Iron Man, Thor, Hulk, and Spider-Man.",
+ "prompt": "How would you store all active Avengers together so J.A.R.V.I.S. can alert them in order?",
+ "objectives": [
+ "Group hero names",
+ "Maintain assembly order",
+ "Store as a list"
+ ],
+ "sampleReasoning": "active_avengers = [\"Iron Man\", \"Thor\", \"Hulk\", \"Spider-Man\"] stores heroes in a single list.",
+ "effectivenessScore": 97,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 2,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "a266cadc-5e1f-49e2-925f-b06c712d2de3",
+ "title": "J.A.R.V.I.S. Weapon Diagnostics",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "Tony Stark asks: \"J.A.R.V.I.S., check status of repulsors, unibeam, and nanotech shield!\"",
+ "prompt": "How would you map each weapon system name directly to its operational status?",
+ "objectives": [
+ "Map weapon to status",
+ "Instant direct lookup",
+ "Store as dictionary"
+ ],
+ "sampleReasoning": "weapon_status = {\"repulsors\": \"READY\", \"unibeam\": \"CHARGING\", \"shield\": \"ACTIVE\"} allows instant lookup by name.",
+ "effectivenessScore": 98,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 3,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "dd89f194-bd91-4936-af87-6cf8fe44bd85",
+ "title": "Repulsor Thrust Calculator",
+ "difficulty": "Builder",
+ "concepts": [
+ "functions"
+ ],
+ "context": "Iron Man needs to compute flight trajectory thruster force based on suit weight and speed.",
+ "prompt": "How would you package this flight calculation into a reusable function for J.A.R.V.I.S.?",
+ "objectives": [
+ "Package thruster math",
+ "Accept weight and velocity",
+ "Return force value"
+ ],
+ "sampleReasoning": "def calculate_thrust(weight, speed): return weight * speed * 1.5 — reuse across all armor marks.",
+ "effectivenessScore": 96,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 4,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "232d2772-99ef-4405-967f-98f499f1d1e7",
+ "title": "Infinity Stone Energy Signature Filter",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "Sensors in Wakanda register cosmic energy spikes. Multiple sensors detect the Space Stone signature.",
+ "prompt": "How would you remove duplicate sensor readings to get only unique stone signatures?",
+ "objectives": [
+ "Deduplicate sensor readings",
+ "Guarantee unique signatures",
+ "Map to sets"
+ ],
+ "sampleReasoning": "unique_signatures = set(sensor_spikes) guarantees unique cosmic signals.",
+ "effectivenessScore": 95,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-infinity",
+ "themeStep": 1,
+ "themeEmoji": "💎",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "0f3b9882-dbe3-465c-b0e5-9955b6dccf5d",
+ "title": "Chakra Points Tracker",
+ "difficulty": "Beginner",
+ "concepts": [
+ "variables"
+ ],
+ "context": "Naruto is practicing Nine-Tails Chakra control and needs to track his reserve points.",
+ "prompt": "How would you store Naruto's current chakra level in a single variable?",
+ "objectives": [
+ "Identify chakra value",
+ "Name variable clearly",
+ "Store for Jutsu casting"
+ ],
+ "sampleReasoning": "chakra_points = 5000 — store current chakra in a named variable.",
+ "effectivenessScore": 96,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 1,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "6cd1bc47-6d55-4d3f-af78-b9354245c8e7",
+ "title": "Hidden Leaf Squad Roster",
+ "difficulty": "Beginner",
+ "concepts": [
+ "lists"
+ ],
+ "context": "Kakashi Sensei forms Squad 7 with Naruto, Sasuke, and Sakura.",
+ "prompt": "How would you group all three shinobi into a single list for mission deployment?",
+ "objectives": [
+ "Group squad members",
+ "Maintain ninja order",
+ "Store in a list"
+ ],
+ "sampleReasoning": "squad_7 = [\"Naruto\", \"Sasuke\", \"Sakura\"] keeps the team together in one list.",
+ "effectivenessScore": 97,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 2,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "52d9e07c-df1c-46a4-aa34-74d8174549d9",
+ "title": "Jutsu Hand Sign Vault",
+ "difficulty": "Explorer",
+ "concepts": [
+ "dictionaries"
+ ],
+ "context": "The Shinobi Library stores hand sign sequences for Rasengan, Chidori, and Shadow Clone jutsu.",
+ "prompt": "How would you map each Jutsu name to its required hand sign combination?",
+ "objectives": [
+ "Map jutsu to hand signs",
+ "Look up jutsu instantly",
+ "Store as dictionary"
+ ],
+ "sampleReasoning": "jutsu_vault = {\"Rasengan\": \"Ram → Serpent\", \"Chidori\": \"Ox → Rabbit → Monkey\"} maps jutsu directly to signs.",
+ "effectivenessScore": 98,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 3,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "25c6fa07-d350-4e55-b84f-b649be2d6714",
+ "title": "Shadow Clone Multiplier",
+ "difficulty": "Builder",
+ "concepts": [
+ "functions"
+ ],
+ "context": "Naruto wants to calculate total chakra consumed when creating N shadow clones.",
+ "prompt": "How would you write a reusable function to compute total chakra cost per clone count?",
+ "objectives": [
+ "Create clone chakra formula",
+ "Accept clone count",
+ "Return total chakra required"
+ ],
+ "sampleReasoning": "def clone_chakra(clone_count): return clone_count * 50 — calculate chakra cost dynamically.",
+ "effectivenessScore": 96,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 4,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "74ff45af-a47d-43f2-86f1-15709068d2f6",
+ "title": "Dungeon Rift Monster Index",
+ "difficulty": "Explorer",
+ "concepts": [
+ "sets"
+ ],
+ "context": "An S-Rank Hunter encounters wild monsters in a dungeon rift and receives duplicate radar pings.",
+ "prompt": "How would you filter out duplicate monster pings to index only unique species?",
+ "objectives": [
+ "Filter duplicate pings",
+ "Store unique species",
+ "Map to sets"
+ ],
+ "sampleReasoning": "unique_monsters = set(radar_pings) removes duplicate dungeon pings.",
+ "effectivenessScore": 95,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-pokedex",
+ "themeStep": 1,
+ "themeEmoji": "🐉",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "90b55d9b-e37f-4a6d-867e-1319d66b877e",
+ "title": "Gryffindor House Points Addition",
+ "difficulty": "Beginner",
+ "concepts": [
+ "arithmetic",
+ "subtraction"
+ ],
+ "context": "Harry scores 50 points for catching the Snitch, but Snape deducts 15 for being late.",
+ "prompt": "How would you calculate Gryffindor's net points using arithmetic operators?",
+ "objectives": [
+ "Perform addition and subtraction",
+ "Calculate net points",
+ "Use math operators"
+ ],
+ "sampleReasoning": "net_points = 50 - 15 calculates the final house points.",
+ "effectivenessScore": 94,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 2,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "e6a3cbc1-8759-4886-9784-3d363fce3c56",
+ "title": "Spell Incantation Generator",
+ "difficulty": "Beginner",
+ "concepts": [
+ "strings"
+ ],
+ "context": "Hermione wants to construct full spell commands like \"Harry casts Expelliarmus!\".",
+ "prompt": "How would you combine the wizard's name and the spell name into a single message?",
+ "objectives": [
+ "Combine text strings",
+ "Format incantations",
+ "Map to Python strings"
+ ],
+ "sampleReasoning": "incantation = f\"{wizard} casts {spell}!\" sticks text pieces together.",
+ "effectivenessScore": 95,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 3,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "7f79bacc-ada2-41f8-914e-f5498c492ec1",
+ "title": "Sorting Hat House Evaluator",
+ "difficulty": "Beginner",
+ "concepts": [
+ "conditionals",
+ "comparisons"
+ ],
+ "context": "The Sorting Hat checks a student's bravery and wisdom scores to decide their house.",
+ "prompt": "How would you write a decision rule to assign Gryffindor or Ravenclaw based on scores?",
+ "objectives": [
+ "Compare trait scores",
+ "Branch decisions",
+ "Map to if/else"
+ ],
+ "sampleReasoning": "if bravery >= 80: house = \"Gryffindor\" else if wisdom >= 80: house = \"Ravenclaw\".",
+ "effectivenessScore": 96,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 4,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "f68192c3-b61e-4333-9081-63d751f1da5e",
+ "title": "Cauldron Clockwise Stir Routine",
+ "difficulty": "Explorer",
+ "concepts": [
+ "loops",
+ "while loops"
+ ],
+ "context": "Brewing Felix Felicis requires stirring the potion cauldron clockwise exactly 50 times.",
+ "prompt": "How would you make Python repeat the stirring action 50 times automatically?",
+ "objectives": [
+ "Identify repeated stir action",
+ "Loop stir count",
+ "Map to for/while loop"
+ ],
+ "sampleReasoning": "for stir in range(1, 51): print(f\"Stir {stir}\") repeats the action automatically.",
+ "effectivenessScore": 95,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 5,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "600add39-7b43-4c95-ab08-32dfd1d510c8",
+ "title": "Restricted Section Scroll Search",
+ "difficulty": "Explorer",
+ "concepts": [
+ "search",
+ "filtering"
+ ],
+ "context": "Harry is searching through 100 library scrolls to locate a forbidden dark magic spell.",
+ "prompt": "How would you filter the scroll list to find only scrolls containing \"Dark Magic\"?",
+ "objectives": [
+ "Scan list items",
+ "Filter by keyword",
+ "Map to search/filtering"
+ ],
+ "sampleReasoning": "dark_scrolls = [scroll for scroll in library if \"Dark\" in scroll] filters the scrolls.",
+ "effectivenessScore": 94,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 5,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "15ac1758-080b-4555-b31d-bb76039b657f",
+ "title": "Exploding Cauldron Exception Shield",
+ "difficulty": "Builder",
+ "concepts": [
+ "error handling",
+ "try except"
+ ],
+ "context": "A potion heat spikes above 300 degrees and threatens to blow up the potions dungeon.",
+ "prompt": "How would you catch this overheating error so your code casts a shield charm instead of crashing?",
+ "objectives": [
+ "Detect heat overload",
+ "Catch exception",
+ "Map to try/except"
+ ],
+ "sampleReasoning": "try: brew_potion() except OverheatError: cast_shield() prevents a crash.",
+ "effectivenessScore": 97,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-potions",
+ "themeStep": 6,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "eb504ea7-be9a-481b-83ad-b5024329d00a",
+ "title": "Triwizard Tournament Leaderboard Ranker",
+ "difficulty": "Builder",
+ "concepts": [
+ "algorithms",
+ "sorting"
+ ],
+ "context": "Dumbledore needs to sort the final Triwizard scores from highest to lowest score.",
+ "prompt": "How would you order champion scores from highest to lowest?",
+ "objectives": [
+ "Order champion scores",
+ "Sort list descending",
+ "Map to sorting algorithms"
+ ],
+ "sampleReasoning": "scores.sort(reverse=True) ranks the champions in descending order.",
+ "effectivenessScore": 96,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 6,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "f918c077-fc2b-4e7c-94ce-550911e45af4",
+ "title": "Ministry Secret Archive Scroll Log",
+ "difficulty": "Builder",
+ "concepts": [
+ "files",
+ "file io"
+ ],
+ "context": "The Ministry of Magic wants to save all recorded spell logs into a permanent parchment file.",
+ "prompt": "How would you write text into a file so it stays saved even after shutting down your laptop?",
+ "objectives": [
+ "Open file for writing",
+ "Save spell logs",
+ "Map to file I/O"
+ ],
+ "sampleReasoning": "with open(\"spells.txt\", \"w\") as f: f.write(log) saves data permanently.",
+ "effectivenessScore": 95,
+ "theme": "potterheads",
+ "caseStudyId": "cs-potter-spells",
+ "themeStep": 7,
+ "themeEmoji": "🧙♂️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "57309775-8cad-4f08-9d03-425c8509ca04",
+ "title": "Suit Power Drain Calculator",
+ "difficulty": "Beginner",
+ "concepts": [
+ "arithmetic",
+ "subtraction"
+ ],
+ "context": "Iron Man has 1000 kW power. Repulsor blasts use 250 kW and shield uses 180 kW.",
+ "prompt": "How would you compute the remaining suit energy using subtraction operators?",
+ "objectives": [
+ "Subtract power drain",
+ "Calculate remaining energy",
+ "Use arithmetic operators"
+ ],
+ "sampleReasoning": "remaining = 1000 - 250 - 180 calculates power remaining.",
+ "effectivenessScore": 94,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 5,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "60fbebe3-ffd9-44a4-9b0d-f59e31d5a334",
+ "title": "J.A.R.V.I.S. Audio Broadcast Formatter",
+ "difficulty": "Beginner",
+ "concepts": [
+ "strings"
+ ],
+ "context": "J.A.R.V.I.S. needs to construct HUD alert strings like \"Warning Mr. Stark: Thruster #2 Low\".",
+ "prompt": "How would you format variables into a clear status text string?",
+ "objectives": [
+ "Format status message",
+ "Combine variables with text",
+ "Map to strings"
+ ],
+ "sampleReasoning": "alert = f\"Warning {user}: {system} {status}\" formats HUD alerts.",
+ "effectivenessScore": 95,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 6,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "5fd26012-46ff-44cb-9198-46ec88a2b603",
+ "title": "Nanotech Threat Level Defense Protocol",
+ "difficulty": "Beginner",
+ "concepts": [
+ "conditionals",
+ "comparisons"
+ ],
+ "context": "Mark 85 sensors evaluate hostile threat score from 0 to 100.",
+ "prompt": "How would you deploy nanotech shields if threat > 80, repulsors if > 50, or scan only?",
+ "objectives": [
+ "Evaluate threat score",
+ "Branch suit defense",
+ "Map to if/elif/else"
+ ],
+ "sampleReasoning": "if threat > 80: deploy_shield() elif threat > 50: deploy_repulsors().",
+ "effectivenessScore": 96,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 7,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "95b71db1-fd02-453e-bb4c-6c790c348880",
+ "title": "Iron Man Subsystem Thruster Diagnostics",
+ "difficulty": "Explorer",
+ "concepts": [
+ "loops",
+ "while loops"
+ ],
+ "context": "J.A.R.V.I.S. needs to check all 100 thruster nodes across the armor one by one.",
+ "prompt": "How would you repeat the diagnostic check for every thruster node automatically?",
+ "objectives": [
+ "Iterate thruster nodes",
+ "Perform check per node",
+ "Map to loops"
+ ],
+ "sampleReasoning": "for thruster in armor_thrusters: check_status(thruster) repeats for all nodes.",
+ "effectivenessScore": 95,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 8,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "5fbb445b-ada9-4594-b250-c2b721199e08",
+ "title": "Vibranium Radar Signature Scan",
+ "difficulty": "Explorer",
+ "concepts": [
+ "search",
+ "filtering"
+ ],
+ "context": "Wakanda radar receives satellite pings across Earth. You need to find all \"Vibranium\" pings.",
+ "prompt": "How would you search radar readings and filter out non-vibranium signals?",
+ "objectives": [
+ "Filter radar pings",
+ "Extract matching signals",
+ "Map to search/filter"
+ ],
+ "sampleReasoning": "vibranium_pings = [p for p in radar_pings if \"Vibranium\" in p] filters radar data.",
+ "effectivenessScore": 94,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-infinity",
+ "themeStep": 2,
+ "themeEmoji": "💎",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "de02467b-26be-41a2-b7f6-416754816c62",
+ "title": "Arc Reactor Power Overload Bypass",
+ "difficulty": "Builder",
+ "concepts": [
+ "error handling",
+ "try except"
+ ],
+ "context": "An enemy blast causes Arc Reactor energy voltage to surge above 1200V.",
+ "prompt": "How would you handle this voltage surge exception so J.A.R.V.I.S. diverts power to heat sinks without shutting down?",
+ "objectives": [
+ "Detect power surge",
+ "Catch voltage error",
+ "Map to try/except"
+ ],
+ "sampleReasoning": "try: fire_unibeam() except PowerSurge: divert_to_heatsink() handles surges.",
+ "effectivenessScore": 97,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 9,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "a1fbca1f-19a1-44e7-9478-b295ae3db452",
+ "title": "Stark Satellite Threat Priority Ranker",
+ "difficulty": "Builder",
+ "concepts": [
+ "algorithms",
+ "sorting"
+ ],
+ "context": "Stark satellite defense detects 5 incoming alien ships with different danger ratings.",
+ "prompt": "How would you sort the incoming ships from highest threat score to lowest?",
+ "objectives": [
+ "Order threat levels",
+ "Sort list descending",
+ "Map to sorting algorithms"
+ ],
+ "sampleReasoning": "ships.sort(key=lambda s: s.threat, reverse=True) prioritizes targets.",
+ "effectivenessScore": 96,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-infinity",
+ "themeStep": 3,
+ "themeEmoji": "💎",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "96da7dfa-c905-45c6-b506-deaa8c84c0a6",
+ "title": "Stark Telemetry Mission Log Archiver",
+ "difficulty": "Builder",
+ "concepts": [
+ "files",
+ "file io"
+ ],
+ "context": "J.A.R.V.I.S. must save flight telemetry and battle statistics into a permanent log file.",
+ "prompt": "How would you write flight telemetry data into a JSON file for permanent storage?",
+ "objectives": [
+ "Open file for writing",
+ "Serialize telemetry data",
+ "Map to file I/O"
+ ],
+ "sampleReasoning": "with open(\"flight_log.json\", \"w\") as f: json.dump(log_data, f) saves telemetry.",
+ "effectivenessScore": 95,
+ "theme": "marvel",
+ "caseStudyId": "cs-marvel-jarvis",
+ "themeStep": 10,
+ "themeEmoji": "🦾",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "04bdc60a-af99-4f48-ad55-55e23e8bbc07",
+ "title": "Shadow Clone Chakra Cost Calculator",
+ "difficulty": "Beginner",
+ "concepts": [
+ "arithmetic",
+ "subtraction"
+ ],
+ "context": "Naruto has 9000 chakra. Creating 10 shadow clones uses 1500 chakra.",
+ "prompt": "How would you compute Naruto's remaining chakra using subtraction?",
+ "objectives": [
+ "Subtract chakra spent",
+ "Compute remaining chakra",
+ "Use math operators"
+ ],
+ "sampleReasoning": "remaining = 9000 - 1500 calculates remaining chakra.",
+ "effectivenessScore": 94,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 5,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "530d7965-772c-4131-ab41-694a8a85776d",
+ "title": "Jutsu Invocation Scroll Formatter",
+ "difficulty": "Beginner",
+ "concepts": [
+ "strings"
+ ],
+ "context": "A shinobi scroll needs to generate jutsu announcements like \"Naruto unleashes Rasengan!\".",
+ "prompt": "How would you format the ninja name and jutsu name into an incantation string?",
+ "objectives": [
+ "Combine text strings",
+ "Format jutsu incantations",
+ "Map to strings"
+ ],
+ "sampleReasoning": "announcement = f\"{ninja} unleashes {jutsu}!\" formats the scroll text.",
+ "effectivenessScore": 95,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 6,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "ca5d7d2a-8abb-43dc-802d-40bff5055b38",
+ "title": "Chunin Exam Promotion Evaluator",
+ "difficulty": "Beginner",
+ "concepts": [
+ "conditionals",
+ "comparisons"
+ ],
+ "context": "Proctors check shinobi exam marks: score >= 85 is Chunin, score >= 60 is Genin.",
+ "prompt": "How would you write a decision rule to determine the ninja rank based on score?",
+ "objectives": [
+ "Compare exam score",
+ "Branch rank results",
+ "Map to if/elif/else"
+ ],
+ "sampleReasoning": "if score >= 85: rank = \"Chunin\" elif score >= 60: rank = \"Genin\".",
+ "effectivenessScore": 96,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 7,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "8d53d978-597a-4138-8cb7-8c81e3c229fc",
+ "title": "Parallel Shadow Clone Training Reps",
+ "difficulty": "Explorer",
+ "concepts": [
+ "loops",
+ "while loops"
+ ],
+ "context": "Naruto summons 100 shadow clones to practice tree-climbing training reps simultaneously.",
+ "prompt": "How would you repeat the training step for every shadow clone in code?",
+ "objectives": [
+ "Iterate shadow clones",
+ "Perform training action",
+ "Map to loops"
+ ],
+ "sampleReasoning": "for clone in range(1, 101): train_clone(clone) repeats for all clones.",
+ "effectivenessScore": 95,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 8,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "9ede990b-c372-440c-9ae7-ec25f092041e",
+ "title": "S-Rank Dungeon Boss Finder",
+ "difficulty": "Explorer",
+ "concepts": [
+ "search",
+ "filtering"
+ ],
+ "context": "An S-Rank Hunter scans a dungeon rift containing 50 monsters to find S-Rank beasts.",
+ "prompt": "How would you search the monster list and filter out non-S-rank creatures?",
+ "objectives": [
+ "Filter dungeon monsters",
+ "Identify S-Rank beasts",
+ "Map to search/filtering"
+ ],
+ "sampleReasoning": "bosses = [m for m in dungeon_monsters if m.rank == \"S\"] filters the beasts.",
+ "effectivenessScore": 94,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-pokedex",
+ "themeStep": 2,
+ "themeEmoji": "🐉",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "2d11cbe4-b6f5-4409-b2dc-1b329ab8b806",
+ "title": "Chakra Exhaustion Substitution Guard",
+ "difficulty": "Builder",
+ "concepts": [
+ "error handling",
+ "try except"
+ ],
+ "context": "During battle, a shinobi's chakra drops below critical level while casting a jutsu.",
+ "prompt": "How would you catch this low-chakra exception to automatically trigger a wood clone substitution?",
+ "objectives": [
+ "Detect low chakra",
+ "Catch exception",
+ "Map to try/except"
+ ],
+ "sampleReasoning": "try: cast_jutsu() except ChakraExhausted: trigger_substitution() handles exhaustion.",
+ "effectivenessScore": 97,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 9,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "af0ce424-048e-48fe-b5ae-299f85605d7f",
+ "title": "Shinobi Guild Power Ranker",
+ "difficulty": "Builder",
+ "concepts": [
+ "algorithms",
+ "sorting"
+ ],
+ "context": "The Hokage needs to rank 20 shinobi by combat power level from highest to lowest.",
+ "prompt": "How would you order the list of shinobi scores from highest to lowest?",
+ "objectives": [
+ "Order combat scores",
+ "Sort list descending",
+ "Map to sorting algorithms"
+ ],
+ "sampleReasoning": "ninja_list.sort(key=lambda n: n.power, reverse=True) ranks the shinobi.",
+ "effectivenessScore": 96,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-pokedex",
+ "themeStep": 3,
+ "themeEmoji": "🐉",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
+ },
+ {
+ "_id": "90c55732-34ea-46d2-9c8f-46cb643c705c",
+ "title": "Forbidden Leaf Scroll Archive Saver",
+ "difficulty": "Builder",
+ "concepts": [
+ "files",
+ "file io"
+ ],
+ "context": "The Hokage wants to record secret ninjutsu scroll instructions into a permanent file archive.",
+ "prompt": "How would you write jutsu scroll instructions into a file for permanent archival?",
+ "objectives": [
+ "Open file for writing",
+ "Write scroll text",
+ "Map to file I/O"
+ ],
+ "sampleReasoning": "with open(\"scrolls.txt\", \"w\") as f: f.write(scroll_text) archives the jutsu permanently.",
+ "effectivenessScore": 95,
+ "theme": "anime",
+ "caseStudyId": "cs-anime-ninja",
+ "themeStep": 10,
+ "themeEmoji": "⚔️",
+ "createdAt": "2026-07-27T06:14:32.837Z",
+ "updatedAt": "2026-07-27T06:14:32.837Z"
}
],
"sessions": []
diff --git a/server/src/data/store.js b/server/src/data/store.js
index b6a4ce1..6f152fe 100644
--- a/server/src/data/store.js
+++ b/server/src/data/store.js
@@ -42,6 +42,15 @@ async function listScenarios(filters = {}) {
let scenarios = [...db.scenarios];
if (filters.difficulty) scenarios = scenarios.filter((item) => item.difficulty === filters.difficulty);
if (filters.concept) scenarios = scenarios.filter((item) => item.concepts.includes(filters.concept));
+ if (filters.theme) {
+ if (filters.theme === 'default') {
+ const defaultThemes = ['default', 'classic', 'chai-stall', 'isro', 'instagram', 'food-delivery', 'ai-playlist', 'kota'];
+ scenarios = scenarios.filter((item) => defaultThemes.includes(item.theme) || !item.theme);
+ } else {
+ scenarios = scenarios.filter((item) => item.theme === filters.theme);
+ }
+ }
+ if (filters.caseStudyId) scenarios = scenarios.filter((item) => item.caseStudyId === filters.caseStudyId);
if (filters.q) {
const query = filters.q.toLowerCase();
scenarios = scenarios.filter((item) => (
diff --git a/server/src/index.js b/server/src/index.js
index a28c468..d21d654 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -5,6 +5,7 @@ const scenarioRoutes = require('./routes/scenarios');
const sessionRoutes = require('./routes/sessions');
const analyticsRoutes = require('./routes/analytics');
const roadmapRoutes = require('./routes/roadmap');
+const caseStudiesRoutes = require('./routes/casestudies');
require('dotenv').config();
const app = express();
@@ -14,15 +15,16 @@ app.use(cors({ origin: process.env.CLIENT_ORIGIN || 'http://localhost:5173' }));
app.use(express.json());
app.use(morgan('dev'));
-app.get('/api/health', (_req, res) => res.json({ ok: true, product: 'PyBe' }));
+app.get('/api/health', (_req, res) => res.json({ ok: true, product: 'PyBe', version: '2.0.0' }));
app.use('/api/scenarios', scenarioRoutes);
app.use('/api/sessions', sessionRoutes);
app.use('/api/analytics', analyticsRoutes);
app.use('/api/roadmap', roadmapRoutes);
+app.use('/api/casestudies', caseStudiesRoutes);
app.use((error, _req, res, _next) => {
console.error(error);
res.status(error.status || 500).json({ message: error.message || 'Server error' });
});
-app.listen(port, () => console.log(`PyBe API running on http://localhost:${port}`));
+app.listen(port, () => console.log(`PyBe API v2.0 running on http://localhost:${port}`));
diff --git a/server/src/routes/casestudies.js b/server/src/routes/casestudies.js
new file mode 100644
index 0000000..5636ff8
--- /dev/null
+++ b/server/src/routes/casestudies.js
@@ -0,0 +1,200 @@
+const express = require('express');
+const router = express.Router();
+
+const CASE_STUDIES = [
+ // ── DEFAULT THEME CASE STUDIES ─────────────────────────────────────────────
+ {
+ id: 'cs-chai-stall',
+ title: 'The Underground Chai Stall',
+ emoji: '🍵',
+ character: 'Ramu',
+ tagline: 'From one cup of chai to an empire — rediscover Lists, Dicts, and Functions.',
+ description: 'Ramu runs a chai stall outside IIT Ropar\'s gate. Follow his journey from tracking a single variable to managing a full menu dictionary. Every growing pain he feels is a Python construct waiting to be discovered.',
+ arc: 'Variables → Lists → Dicts → Functions',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'functions'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#F59E0B'
+ },
+ {
+ id: 'cs-isro',
+ title: 'ISRO Mission Control',
+ emoji: '🚀',
+ character: 'Intern at ISRO',
+ tagline: 'Chandrayaan-3 is live. A FAIL at 2 AM shows why names matter more than positions.',
+ description: 'You are an intern at ISRO during Chandrayaan-3. From three telemetry variables to managing 12 subsystems, a critical FAIL message at 2 AM teaches you why dictionaries exist — because lives depend on looking up by name, not position.',
+ arc: 'Variables → Lists → Dicts → Sets → Modules',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sets', 'modules'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#3B82F6'
+ },
+ {
+ id: 'cs-instagram',
+ title: 'The Viral Instagram Filter Creator',
+ emoji: '📸',
+ character: 'Filter Developer',
+ tagline: 'Twenty filters, parallel lists out of sync, and users complaining. Sound familiar?',
+ description: 'Build a photo filter app from one variable to a complete class. When your 20 parallel lists fall out of sync and users see broken filters, you discover why bundling related data together isn\'t just elegant — it\'s necessary.',
+ arc: 'Variables → Lists → Dicts → Functions → Classes',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'functions', 'classes'],
+ difficulty: 'Explorer → Builder',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#EC4899'
+ },
+ {
+ id: 'cs-food-delivery',
+ title: 'Midnight Food Delivery Startup',
+ emoji: '🍕',
+ character: 'Arjun, Meera & Dev',
+ tagline: 'Three friends, a hostel room at 2 AM, and a -₹36 bill that crashed the whole app.',
+ description: 'Three hostel friends launch "HungerFix" at 2 AM. From Maggi and two variables to a full ordering system — the duplicate biryani crisis and a negative-quantity crash teach you why sets and try/except exist: real data is messy.',
+ arc: 'Variables → Lists → Dicts → Sets → try/except',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sets', 'error handling'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#F97316'
+ },
+ {
+ id: 'cs-ai-playlist',
+ title: 'The AI Playlist That Knows You',
+ emoji: '🎵',
+ character: 'Playlist Builder',
+ tagline: '"Shape of You" for the 23rd time. Your app needs sets — badly.',
+ description: 'Build a music recommendation app from scratch. One mood variable becomes a list of 12 genres, which becomes a set-deduplicated session, which becomes a dictionary of play counts and ratings. Each pain point is a concept waiting to be born.',
+ arc: 'Variables → Lists → Sets → Dicts → Functions',
+ pythonJourney: ['variables', 'lists', 'sets', 'dictionaries', 'functions'],
+ difficulty: 'Beginner → Explorer',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#8B5CF6'
+ },
+ {
+ id: 'cs-kota',
+ title: 'The Kota Coaching Factory',
+ emoji: '📚',
+ character: 'JEE Aspirant',
+ tagline: 'Thirty batchmates. A parent calls. Find Priya\'s rank — the director is watching.',
+ description: 'You are a JEE aspirant at a Kota coaching center tracking 30 batchmates\' ranks. From a single rank variable to a sorted merit list algorithm, every inefficiency you feel maps to a Python construct invented for exactly that pain.',
+ arc: 'Variables → Lists → Dicts → Sorting → Algorithms',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sorting', 'algorithms'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'default',
+ color: '#10B981'
+ },
+
+ // ── POTTERHEADS THEME CASE STUDIES ─────────────────────────────────────────
+ {
+ id: 'cs-potter-potions',
+ title: 'Hogwarts Potion Brewing & Marauder\'s Map',
+ emoji: '🧙♂️',
+ character: 'Potions Apprentice at Hogwarts',
+ tagline: 'Brewing Felix Felicis: Track ingredients, spellbooks, and map locations by magic.',
+ description: 'Master magical data structures at Hogwarts! From storing a single potion ingredient variable to managing a house inventory dictionary and automating brewing functions, experience Python through wizardry.',
+ arc: 'Variables → Lists → Dicts → Functions',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'functions'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'potterheads',
+ color: '#F59E0B'
+ },
+ {
+ id: 'cs-potter-spells',
+ title: 'The Triwizard Spell Vault & House Points',
+ emoji: '🪄',
+ character: 'Triwizard Champion',
+ tagline: 'Decipher dark spells, deduplicate potion runes, and calculate House Points in real-time.',
+ description: 'Face the Triwizard challenges! Manage spell rosters with lists, prevent duplicate curse registrations using sets, and sort House Points dynamically before Dumbledore awards the Cup.',
+ arc: 'Variables → Lists → Dicts → Sets → Sorting',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sets', 'sorting'],
+ difficulty: 'Explorer → Builder',
+ totalSteps: 4,
+ theme: 'potterheads',
+ color: '#D97706'
+ },
+
+ // ── MARVEL THEME CASE STUDIES ───────────────────────────────────────────────
+ {
+ id: 'cs-marvel-jarvis',
+ title: 'J.A.R.V.I.S. Mark 85 Suit AI',
+ emoji: '🦾',
+ character: 'Tony Stark\'s AI Engineer',
+ tagline: 'Suit power low! From tracking arc reactor voltage to managing 100 Iron Man armor modules.',
+ description: 'Build J.A.R.V.I.S. from scratch! Track arc reactor output, group armor thruster subsystems, look up weapon status by keyword, and package repulsor beam calculations into reusable functions.',
+ arc: 'Variables → Lists → Dicts → Functions',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'functions'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'marvel',
+ color: '#EF4444'
+ },
+ {
+ id: 'cs-marvel-infinity',
+ title: 'The Infinity Stones Containment Grid',
+ emoji: '💎',
+ character: 'Avenger Tech Lead',
+ tagline: 'Prevent cosmic resonance! Filter duplicate energy signatures and compute gauntlet stability.',
+ description: 'Secure the Infinity Stones! Store energy frequencies in lists, guarantee stone uniqueness using sets, map stone attributes with dictionaries, and handle cosmic surge exceptions gracefully.',
+ arc: 'Variables → Lists → Dicts → Sets → try/except',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sets', 'error handling'],
+ difficulty: 'Explorer → Builder',
+ totalSteps: 4,
+ theme: 'marvel',
+ color: '#38BDF8'
+ },
+
+ // ── ANIME THEME CASE STUDIES ────────────────────────────────────────────────
+ {
+ id: 'cs-anime-ninja',
+ title: 'Hidden Leaf Jutsu & Chakra Engine',
+ emoji: '⚔️',
+ character: 'Shinobi Academy Trainee',
+ tagline: 'Unlock your Nindo! From tracking chakra reserves to organizing secret forbidden scrolls.',
+ description: 'Train to become Hokage! Store chakra levels, manage squad lists, look up jutsu hand signs by name, and package secret shadow clone calculations into reusable jutsu functions.',
+ arc: 'Variables → Lists → Dicts → Functions',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'functions'],
+ difficulty: 'Beginner → Builder',
+ totalSteps: 4,
+ theme: 'anime',
+ color: '#FF2A85'
+ },
+ {
+ id: 'cs-anime-pokedex',
+ title: 'Legendary Creature Hunter & Mecha Sync',
+ emoji: '🐉',
+ character: 'S-Rank Hunter',
+ tagline: 'Dungeon rift open! Deduplicate spotted monsters and compute mecha synchronization rates.',
+ description: 'Enter the hunter dungeon! Track squad power levels, deduplicate wild monster encounters with sets, organize creature stats in dictionaries, and sort hunter guild rankings live.',
+ arc: 'Variables → Lists → Dicts → Sets → Sorting',
+ pythonJourney: ['variables', 'lists', 'dictionaries', 'sets', 'sorting'],
+ difficulty: 'Explorer → Builder',
+ totalSteps: 4,
+ theme: 'anime',
+ color: '#00F5D4'
+ }
+];
+
+router.get('/', (req, res) => {
+ const { theme } = req.query;
+ if (theme && theme !== 'all') {
+ const filtered = CASE_STUDIES.filter(c => {
+ if (theme === 'default') return c.theme === 'default' || !c.theme;
+ return c.theme === theme;
+ });
+ return res.json(filtered);
+ }
+ res.json(CASE_STUDIES);
+});
+
+router.get('/:id', (req, res) => {
+ const cs = CASE_STUDIES.find(c => c.id === req.params.id);
+ if (!cs) return res.status(404).json({ message: 'Case study not found' });
+ res.json(cs);
+});
+
+module.exports = router;
diff --git a/server/src/routes/scenarios.js b/server/src/routes/scenarios.js
index ea17344..393daba 100644
--- a/server/src/routes/scenarios.js
+++ b/server/src/routes/scenarios.js
@@ -5,8 +5,8 @@ const router = express.Router();
router.get('/', async (req, res, next) => {
try {
- const { q, concept, difficulty } = req.query;
- const scenarios = await store.listScenarios({ q, concept, difficulty });
+ const { q, concept, difficulty, theme, caseStudyId } = req.query;
+ const scenarios = await store.listScenarios({ q, concept, difficulty, theme, caseStudyId });
res.json(scenarios);
} catch (error) {
next(error);
diff --git a/server/src/seed.js b/server/src/seed.js
index 2f80d45..8e112a7 100644
--- a/server/src/seed.js
+++ b/server/src/seed.js
@@ -2,6 +2,7 @@ const { resetData } = require('./data/store');
require('dotenv').config();
const scenarios = [
+ // ─── ORIGINAL BEGINNER SCENARIOS ────────────────────────────────────────────
{
title: 'Bag Weight Label',
difficulty: 'Beginner',
@@ -10,7 +11,9 @@ const scenarios = [
prompt: 'What single piece of information would you store so the computer can remember the bag weight?',
objectives: ['Identify one value', 'Give the value a name', 'Connect naming to a variable'],
sampleReasoning: 'I only need to remember the bag weight, so I would store it with a clear name.',
- effectivenessScore: 96
+ effectivenessScore: 96,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Rainy Day Choice',
@@ -20,7 +23,9 @@ const scenarios = [
prompt: 'What small decision rule would help the learner decide whether to carry an umbrella?',
objectives: ['Notice one condition', 'Choose one action', 'Map the rule to if/else'],
sampleReasoning: 'If it is raining, carry an umbrella. Otherwise, leave it at home.',
- effectivenessScore: 95
+ effectivenessScore: 95,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Two Snack Prices',
@@ -30,7 +35,9 @@ const scenarios = [
prompt: 'How would you figure out the total cost using just the two prices?',
objectives: ['Store two values', 'Add values', 'Name the result'],
sampleReasoning: 'I would keep the two prices separately and add them to get the total.',
- effectivenessScore: 94
+ effectivenessScore: 94,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Greeting by Name',
@@ -40,7 +47,9 @@ const scenarios = [
prompt: 'What text should the computer remember, and how should it combine that text into a greeting?',
objectives: ['Store text', 'Combine text', 'Recognize strings'],
sampleReasoning: 'The computer should store the learner name and place it inside a greeting sentence.',
- effectivenessScore: 93
+ effectivenessScore: 93,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Pass Mark Check',
@@ -50,7 +59,9 @@ const scenarios = [
prompt: 'What comparison would decide if the learner passed?',
objectives: ['Compare two numbers', 'Create a yes/no result', 'Map comparison to condition'],
sampleReasoning: 'I would compare the score with the pass mark and decide pass if it is high enough.',
- effectivenessScore: 92
+ effectivenessScore: 92,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Pocket Money Left',
@@ -60,7 +71,9 @@ const scenarios = [
prompt: 'What two values matter, and how would you find the money left?',
objectives: ['Name starting amount', 'Name spent amount', 'Subtract to find remaining value'],
sampleReasoning: 'Start with the original money, subtract the spent amount, and store what remains.',
- effectivenessScore: 91
+ effectivenessScore: 91,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Favorite Color List',
@@ -70,7 +83,9 @@ const scenarios = [
prompt: 'How would you keep all three colors together instead of making separate notes?',
objectives: ['Group related values', 'Recognize a collection', 'Map grouping to a list'],
sampleReasoning: 'Since all values are colors, I would keep them together in one list.',
- effectivenessScore: 90
+ effectivenessScore: 90,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'First Item in a Bag',
@@ -80,7 +95,9 @@ const scenarios = [
prompt: 'How would you ask for only the first item?',
objectives: ['Notice order', 'Pick one position', 'Connect position to indexing'],
sampleReasoning: 'The first item is based on its position in the ordered group.',
- effectivenessScore: 89
+ effectivenessScore: 89,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Attendance Count',
@@ -90,7 +107,9 @@ const scenarios = [
prompt: 'What question would help you find how many students are present?',
objectives: ['Recognize a group', 'Ask for its size', 'Connect size to length'],
sampleReasoning: 'I would count how many names are in the present-students list.',
- effectivenessScore: 88
+ effectivenessScore: 88,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Temperature Message',
@@ -100,8 +119,12 @@ const scenarios = [
prompt: 'What simple rule would decide whether to show Hot or Comfortable?',
objectives: ['Set a threshold', 'Compare one value', 'Choose one message'],
sampleReasoning: 'If the temperature is above the threshold, show Hot; otherwise show Comfortable.',
- effectivenessScore: 87
+ effectivenessScore: 87,
+ theme: 'classic',
+ caseStudyId: null
},
+
+ // ─── ORIGINAL EXPLORER SCENARIOS ────────────────────────────────────────────
{
title: 'Water Bottle Reminder',
difficulty: 'Explorer',
@@ -110,7 +133,9 @@ const scenarios = [
prompt: 'How would you avoid writing the same reminder separately for every break?',
objectives: ['Recognize repeated action', 'Identify each break', 'Map repetition to a loop'],
sampleReasoning: 'For every break, show the same water reminder.',
- effectivenessScore: 96
+ effectivenessScore: 96,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Find the Longest Pencil',
@@ -120,7 +145,9 @@ const scenarios = [
prompt: 'What small comparison would you repeat as you look through the lengths?',
objectives: ['Keep current best', 'Compare one item at a time', 'Update when larger'],
sampleReasoning: 'Start with one pencil as the longest, then compare each next pencil against it.',
- effectivenessScore: 95
+ effectivenessScore: 95,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Clean Chore Checklist',
@@ -130,7 +157,9 @@ const scenarios = [
prompt: 'How would you go through each chore and mark it as done?',
objectives: ['Store chores in a list', 'Process one chore at a time', 'Repeat a simple action'],
sampleReasoning: 'Put chores in a list and handle each chore one by one.',
- effectivenessScore: 94
+ effectivenessScore: 94,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Movie Age Filter',
@@ -140,7 +169,9 @@ const scenarios = [
prompt: 'What rule would decide which movies a 12-year-old can see?',
objectives: ['Check one item rule', 'Keep allowed items', 'Map rule to filtering'],
sampleReasoning: 'For each movie, keep it only if the learner age is at least the minimum age.',
- effectivenessScore: 93
+ effectivenessScore: 93,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Classroom Supply Lookup',
@@ -150,7 +181,9 @@ const scenarios = [
prompt: 'How would you store each supply name with its count?',
objectives: ['Pair names with values', 'Look up by name', 'Map pairs to a dictionary'],
sampleReasoning: 'Each supply has a count, so I would store supply names as keys with counts as values.',
- effectivenessScore: 92
+ effectivenessScore: 92,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Bus Stop Search',
@@ -160,7 +193,9 @@ const scenarios = [
prompt: 'How would you check the stops one at a time until you find the target?',
objectives: ['Identify target', 'Scan a list', 'Stop when found'],
sampleReasoning: 'Look at each stop and compare it with the stop I want.',
- effectivenessScore: 91
+ effectivenessScore: 91,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Average Practice Score',
@@ -170,7 +205,9 @@ const scenarios = [
prompt: 'What two small steps are needed before dividing?',
objectives: ['Add all scores', 'Count scores', 'Divide total by count'],
sampleReasoning: 'Find the total of all scores, count how many scores there are, then divide.',
- effectivenessScore: 90
+ effectivenessScore: 90,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Separate Even Roll Numbers',
@@ -180,7 +217,9 @@ const scenarios = [
prompt: 'What small test tells you whether a roll number is even?',
objectives: ['Test divisibility by two', 'Keep matching numbers', 'Connect remainder to modulo'],
sampleReasoning: 'A roll number is even if dividing by two leaves no remainder.',
- effectivenessScore: 89
+ effectivenessScore: 89,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Capitalize Name Tags',
@@ -190,7 +229,9 @@ const scenarios = [
prompt: 'What same text-cleaning action should happen to every name?',
objectives: ['Recognize repeated string change', 'Apply to each name', 'Create cleaned names'],
sampleReasoning: 'For each name, convert it to title case before printing the tag.',
- effectivenessScore: 88
+ effectivenessScore: 88,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Find Missing Homework',
@@ -200,8 +241,12 @@ const scenarios = [
prompt: 'How would you reason about who is missing from the submitted list?',
objectives: ['Compare two groups', 'Find difference', 'Map group difference to sets'],
sampleReasoning: 'Take everyone in the class and remove the students who submitted.',
- effectivenessScore: 87
+ effectivenessScore: 87,
+ theme: 'classic',
+ caseStudyId: null
},
+
+ // ─── ORIGINAL BUILDER SCENARIOS ─────────────────────────────────────────────
{
title: 'Reusable Discount Rule',
difficulty: 'Builder',
@@ -210,7 +255,9 @@ const scenarios = [
prompt: 'What inputs should a reusable discount helper receive?',
objectives: ['Identify reusable rule', 'Choose inputs', 'Return discounted price'],
sampleReasoning: 'The helper needs the bill amount and should return the final price after applying the rule.',
- effectivenessScore: 96
+ effectivenessScore: 96,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Mini Quiz Checker',
@@ -220,7 +267,9 @@ const scenarios = [
prompt: 'How would you design a tiny reusable checker for one question?',
objectives: ['Accept learner answer', 'Accept correct answer', 'Return right or wrong'],
sampleReasoning: 'Compare the learner answer with the correct answer and return whether they match.',
- effectivenessScore: 95
+ effectivenessScore: 95,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Step Counter Function',
@@ -230,7 +279,9 @@ const scenarios = [
prompt: 'How would you make a reusable helper that returns the total steps?',
objectives: ['Accept a list', 'Add all values', 'Return total'],
sampleReasoning: 'The function should take step counts, add them, and give back the total.',
- effectivenessScore: 94
+ effectivenessScore: 94,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Safe Username Maker',
@@ -240,7 +291,9 @@ const scenarios = [
prompt: 'What small text transformations should a username function perform?',
objectives: ['Accept a name', 'Normalize text', 'Return username'],
sampleReasoning: 'Make the name lowercase and remove spaces so it can be used as a username.',
- effectivenessScore: 93
+ effectivenessScore: 93,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Retry Until Valid',
@@ -250,7 +303,9 @@ const scenarios = [
prompt: 'What condition tells the program to keep asking?',
objectives: ['Define valid input', 'Repeat while invalid', 'Stop after valid value'],
sampleReasoning: 'Keep asking while the number is not positive, then stop once it is valid.',
- effectivenessScore: 92
+ effectivenessScore: 92,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Simple Score Report',
@@ -260,7 +315,9 @@ const scenarios = [
prompt: 'How would you package two related pieces of information in one result?',
objectives: ['Create key-value structure', 'Return structured result', 'Connect structure to dictionary'],
sampleReasoning: 'Return a dictionary with the learner name and score as labeled values.',
- effectivenessScore: 91
+ effectivenessScore: 91,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Task Status Updater',
@@ -270,7 +327,9 @@ const scenarios = [
prompt: 'What exact value changes when a task is completed?',
objectives: ['Find one task', 'Change one status', 'Understand updating data'],
sampleReasoning: 'Find the task by name and change its status from pending to done.',
- effectivenessScore: 90
+ effectivenessScore: 90,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Small Receipt Builder',
@@ -280,7 +339,9 @@ const scenarios = [
prompt: 'What pieces should a receipt function combine into a readable line?',
objectives: ['Accept item and price', 'Format text', 'Return one receipt line'],
sampleReasoning: 'Combine the item name and price into one clear sentence.',
- effectivenessScore: 89
+ effectivenessScore: 89,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Choose Next Scenario',
@@ -290,7 +351,9 @@ const scenarios = [
prompt: 'What simple score rule could decide the next difficulty?',
objectives: ['Set score thresholds', 'Branch by score', 'Return next level'],
sampleReasoning: 'If the score is high choose harder, if low choose easier, otherwise stay similar.',
- effectivenessScore: 88
+ effectivenessScore: 88,
+ theme: 'classic',
+ caseStudyId: null
},
{
title: 'Reflection Keyword Finder',
@@ -300,13 +363,955 @@ const scenarios = [
prompt: 'What tiny search would detect that the reflection may need extra support?',
objectives: ['Choose keywords', 'Search text', 'Return support signal'],
sampleReasoning: 'Look for words like confused or stuck and flag the reflection if they appear.',
- effectivenessScore: 87
+ effectivenessScore: 87,
+ theme: 'classic',
+ caseStudyId: null
+ },
+
+ // ─── UADE THEME 1: THE UNDERGROUND CHAI STALL ───────────────────────────────
+ {
+ title: 'Ramu\'s First Chai Variable',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'Ramu runs a tiny chai stall outside IIT Ropar\'s gate. He sells only one type of chai: ginger tea at ₹15 a cup. He wants to track how many cups he sold today.',
+ prompt: 'Ramu asks you: "I only need to remember one thing — how many cups I sold. What should I call it?" How would you help him store this single piece of information?',
+ objectives: ['Identify one value to track', 'Give it a meaningful name', 'Understand why naming matters'],
+ sampleReasoning: 'Ramu needs one piece of information — cups_sold. Naming it clearly means he can use it later to calculate earnings.',
+ effectivenessScore: 95,
+ theme: 'chai-stall',
+ caseStudyId: 'cs-chai-stall',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Dicts → Functions',
+ themeEmoji: '🍵'
+ },
+ {
+ title: 'Ramu Adds More Chai Types',
+ difficulty: 'Beginner',
+ concepts: ['lists'],
+ context: 'Ramu\'s stall became popular. Now he sells 8 types: ginger, masala, elaichi, tulsi, lemon, mint, kadak, and cutting. He has 8 separate variables: chai1, chai2... chai8. His notebook is a mess.',
+ prompt: 'Ramu shouts: "I have 8 variables and if I add one more chai, I need a 9th! This is madness!" What single structure could hold ALL his chai types together?',
+ objectives: ['Feel the pain of too many variables', 'Discover the concept of grouping', 'Map grouping to a Python list'],
+ sampleReasoning: 'All 8 items are chai types. They belong together. One list called chai_types can hold all of them and I can add more without creating new variables.',
+ effectivenessScore: 97,
+ theme: 'chai-stall',
+ caseStudyId: 'cs-chai-stall',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Dicts → Functions',
+ themeEmoji: '🍵'
+ },
+ {
+ title: 'Ramu\'s Price Lookup Problem',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'Ramu now has two parallel lists: chai_types = ["ginger", "masala", ...] and prices = [15, 20, ...]. A customer asks "How much is tulsi?" Ramu has to count to position 4 in one list, then find position 4 in another. He made a mistake yesterday — he reordered the types list but forgot to reorder prices.',
+ prompt: 'Ramu lost ₹200 yesterday because his lists got out of sync. He says: "I want to look up a chai by NAME, not by number." What structure lets you store the name AND price together as a pair?',
+ objectives: ['Understand why parallel lists break', 'Discover named lookup', 'Map key-value pairs to a dictionary'],
+ sampleReasoning: 'A dictionary maps each chai name directly to its price. "tulsi" always points to its correct price — no counting, no sync issues.',
+ effectivenessScore: 98,
+ theme: 'chai-stall',
+ caseStudyId: 'cs-chai-stall',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Dicts → Functions',
+ themeEmoji: '🍵'
+ },
+ {
+ title: 'Ramu\'s Evening Revenue Calculator',
+ difficulty: 'Builder',
+ concepts: ['functions'],
+ context: 'Every evening, Ramu calculates revenue for each chai type: cups_sold × price. He copy-pasted this calculation 8 times in his notebook — once for each chai. When he raised masala chai price, he had to update 3 different places and still missed one.',
+ prompt: 'Ramu says: "I write the same calculation for every single chai type. There has to be a better way." How would you package this repeatable calculation so Ramu writes it ONCE and reuses it for every chai?',
+ objectives: ['Identify repeated logic', 'Package it as a reusable process', 'Map the process to a Python function'],
+ sampleReasoning: 'A function called calculate_revenue takes cups_sold and price_per_cup, then returns the total. Write once, call it for each chai type.',
+ effectivenessScore: 96,
+ theme: 'chai-stall',
+ caseStudyId: 'cs-chai-stall',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Dicts → Functions',
+ themeEmoji: '🍵'
+ },
+
+ // ─── UADE THEME 2: ISRO MISSION CONTROL ──────────────────────────────────────
+ {
+ title: 'ISRO: Three Telemetry Variables',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'You are an intern at ISRO\'s Chandrayaan-3 mission control. The spacecraft has three critical readings: altitude (in km), fuel level (in %), and velocity (in m/s). Right now you have three separate variables on your screen.',
+ prompt: 'The mission director asks: "What are the three values we must track at all times?" How would you name and store these three critical pieces of mission data?',
+ objectives: ['Name critical values meaningfully', 'Understand that names carry meaning in code', 'Store mission-critical data as variables'],
+ sampleReasoning: 'altitude_km, fuel_percent, velocity_ms — each name tells exactly what unit and what it measures. Naming matters in space.',
+ effectivenessScore: 94,
+ theme: 'isro',
+ caseStudyId: 'cs-isro',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Dicts → Sets → Modules',
+ themeEmoji: '🚀'
+ },
+ {
+ title: 'ISRO: Twelve Subsystem Chaos',
+ difficulty: 'Explorer',
+ concepts: ['lists'],
+ context: 'Chandrayaan-3 has 12 subsystems: propulsion, thermal, communication, power, navigation, attitude control, payload, avionics, structure, mechanisms, software, and ground systems. A second intern added three more subsystems without telling you. Now you have 15 separate variables. Someone\'s variable is named temp2 and nobody knows what it means.',
+ prompt: 'The mission director is furious: "Why do we have 15 separate variables? One new intern and the whole codebase breaks!" What structure would hold all subsystem names together and survive additions without chaos?',
+ objectives: ['Understand chaos from ungrouped variables', 'Group related items under one name', 'Discover lists as a solution to this pain'],
+ sampleReasoning: 'A list called subsystems holds all 12 names. Adding a new one is just appending to the list — no new variables, no naming chaos.',
+ effectivenessScore: 97,
+ theme: 'isro',
+ caseStudyId: 'cs-isro',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Dicts → Sets → Modules',
+ themeEmoji: '🚀'
+ },
+ {
+ title: 'ISRO: FAIL at Position 7',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'It is 2:17 AM. The status board shows: [OK, OK, OK, FAIL, OK, OK, OK, OK, OK, OK, OK, OK]. Subsystem at index 3 failed. But which subsystem IS index 3? You have to mentally count: propulsion=0, thermal=1, communication=2, power=3. It\'s 2 AM. You miscounted twice.',
+ prompt: 'The mission director shouts: "I don\'t care about position 3! I need to know which SUBSYSTEM failed by NAME!" What structure would let you look up subsystem status by name, not by position?',
+ objectives: ['Feel the danger of position-based lookup', 'Discover named lookup under pressure', 'Map subsystem → status as dictionary'],
+ sampleReasoning: 'A dictionary maps each subsystem name to its status. status["power"] = "FAIL" — no counting, no miscount at 2 AM.',
+ effectivenessScore: 99,
+ theme: 'isro',
+ caseStudyId: 'cs-isro',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Dicts → Sets → Modules',
+ themeEmoji: '🚀'
+ },
+ {
+ title: 'ISRO: Duplicate Telemetry Signals',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'Three ground stations — Bengaluru, Mauritius, and Bhopal — all send telemetry. Due to a relay issue, the Bengaluru station sent the same altitude reading 14 times. Your data array has the same reading repeated across many entries. The average is now wrong.',
+ prompt: 'The data scientist says: "We\'re getting duplicate readings from the same timestamp. We need only UNIQUE readings." What structure automatically guarantees that each value appears only once, no matter how many times it is submitted?',
+ objectives: ['Understand the cost of duplicates', 'Discover uniqueness as a data property', 'Map uniqueness guarantee to Python sets'],
+ sampleReasoning: 'A set only holds unique values. Adding the same altitude reading 14 times still results in one entry. Sets solve the duplicate problem automatically.',
+ effectivenessScore: 96,
+ theme: 'isro',
+ caseStudyId: 'cs-isro',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Dicts → Sets → Modules',
+ themeEmoji: '🚀'
+ },
+
+ // ─── UADE THEME 3: THE VIRAL INSTAGRAM FILTER CREATOR ────────────────────────
+ {
+ title: 'Filter App: The First Variable',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'You are building a photo filter app. Right now you have one filter: "Warm Vintage." It has one setting: brightness (set to 1.3 by default).',
+ prompt: 'Your app has one setting for one filter. How do you store that brightness value so you can use it to process the photo?',
+ objectives: ['Store a single filter setting', 'Name it meaningfully', 'Understand how naming connects to usage'],
+ sampleReasoning: 'brightness = 1.3 — a named variable holds the setting and can be referenced whenever the filter is applied.',
+ effectivenessScore: 91,
+ theme: 'instagram',
+ caseStudyId: 'cs-instagram',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Dicts → Functions → Classes',
+ themeEmoji: '📸'
+ },
+ {
+ title: 'Filter App: Twenty Filters, Twenty Variables',
+ difficulty: 'Explorer',
+ concepts: ['lists'],
+ context: 'Your filter app now has 20 filters: Warm Vintage, Neon Dreams, Faded Film, Ocean Breeze... You have 20 separate variables: filter1, filter2... filter20. Users swipe through filters. When they swipe right to filter number 7, your code tries to access filter7 — but you can\'t swipe to a variable name.',
+ prompt: 'A friend tries your app and says: "Swiping is broken — I can only see the first filter." Your code cannot loop over 20 separate variables. What single structure would hold all 20 filter names so your swipe gesture can navigate them by position?',
+ objectives: ['Understand that variables cannot be iterated', 'Discover that lists enable position-based navigation', 'Connect swiping to list indexing'],
+ sampleReasoning: 'A list called filters holds all 20 names. swipe_right moves the index forward by 1. filters[current_index] gives the active filter.',
+ effectivenessScore: 97,
+ theme: 'instagram',
+ caseStudyId: 'cs-instagram',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Dicts → Functions → Classes',
+ themeEmoji: '📸'
+ },
+ {
+ title: 'Filter App: Five Settings Per Filter',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'Each filter now has 5 settings: brightness, contrast, saturation, hue_shift, and grain. You have 5 parallel lists: brightness_list[7], contrast_list[7], saturation_list[7]... You updated the brightness list for "Neon Dreams" but forgot to update the contrast list. The filter looks broken.',
+ prompt: 'You wasted 3 hours debugging because your 5 lists got out of sync. A mentor says: "What if each filter was ONE complete package with all its settings together?" What structure bundles multiple named settings into a single unit?',
+ objectives: ['Feel the pain of parallel lists diverging', 'Discover bundling related data together', 'Map a filter\'s settings to a dictionary'],
+ sampleReasoning: 'A dictionary for each filter: {"brightness": 1.3, "contrast": 1.1, "saturation": 1.4, "hue_shift": 10, "grain": 0.2}. All settings travel together — they can\'t fall out of sync.',
+ effectivenessScore: 98,
+ theme: 'instagram',
+ caseStudyId: 'cs-instagram',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Dicts → Functions → Classes',
+ themeEmoji: '📸'
+ },
+ {
+ title: 'Filter App: Copy-Paste Logic Breaks',
+ difficulty: 'Builder',
+ concepts: ['functions'],
+ context: 'You apply filters by copy-pasting the same apply_filter logic for each of your 20 filters. When you want to add a "fade" effect that dims the edges, you have to add it in 20 places. You added it in 19 places and forgot filter number 14. Users are complaining that "Moody Cinema" doesn\'t fade.',
+ prompt: 'A senior dev looks at your code and says: "You have the same 15 lines copied 20 times. If you change anything, you\'ll miss one. EVERY time." How would you package the apply-filter logic so you write it once and it works for every filter?',
+ objectives: ['Experience the maintenance nightmare of copy-pasted logic', 'Understand DRY: Don\'t Repeat Yourself', 'Package repeatable logic as a function'],
+ sampleReasoning: 'def apply_filter(photo, filter_settings): takes a photo and a filter dict, applies all settings, returns the modified photo. Called once per filter — no copy-paste.',
+ effectivenessScore: 96,
+ theme: 'instagram',
+ caseStudyId: 'cs-instagram',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Dicts → Functions → Classes',
+ themeEmoji: '📸'
+ },
+
+ // ─── UADE THEME 4: MIDNIGHT FOOD DELIVERY STARTUP ────────────────────────────
+ {
+ title: 'Hostel Startup: One Item, Two Variables',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'It is 2 AM in a hostel room. Three friends — Arjun, Meera, and Dev — just launched "HungerFix," a food delivery app for their campus. They have exactly one item: Maggi. Price: ₹30.',
+ prompt: 'The friends need the app to remember what they sell and for how much. What are the two pieces of information they absolutely need to store, and how would you name them clearly?',
+ objectives: ['Identify the minimum data needed', 'Name variables descriptively', 'Understand that variables hold the state of the app'],
+ sampleReasoning: 'item_name = "Maggi" and item_price = 30. Simple, named, and the whole app can reference these two values.',
+ effectivenessScore: 93,
+ theme: 'food-delivery',
+ caseStudyId: 'cs-food-delivery',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Dicts → Sets → try/except',
+ themeEmoji: '🍕'
+ },
+ {
+ title: 'Hostel Startup: Fifteen Items, No Search',
+ difficulty: 'Explorer',
+ concepts: ['lists'],
+ context: 'By night 3, HungerFix has 15 items: Maggi, Bread-Omelette, Poha, Upma, Vada Pav... Users are texting "Do you have Poha?" Arjun has to scroll through 15 separate variables to check. He also cannot display a menu — you can\'t print 15 separate variables in a loop.',
+ prompt: 'Dev says: "We can\'t even show users a menu because we have 15 variables. I can\'t loop over variables!" What structure would let you store all 15 items so you can loop through them, display them, and search them?',
+ objectives: ['Understand that iteration requires a collection', 'Discover that loops need lists', 'Connect menu display to list traversal'],
+ sampleReasoning: 'menu = ["Maggi", "Bread-Omelette", "Poha", ...]. Now for item in menu: print(item) displays the whole menu. And "Poha" in menu checks availability instantly.',
+ effectivenessScore: 96,
+ theme: 'food-delivery',
+ caseStudyId: 'cs-food-delivery',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Dicts → Sets → try/except',
+ themeEmoji: '🍕'
+ },
+ {
+ title: 'Hostel Startup: The Duplicate Biryani Crisis',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'Night 10. Two friends — Ananya and Rohan — both order the last biryani at 11:58 PM simultaneously. Both orders go through. The friends running the stall have only ONE biryani. They cook two. Costs double. One customer still waits.',
+ prompt: 'Meera says: "We need to track active orders, but we can\'t allow the SAME item to be ordered twice if only one is in stock." What structure would automatically prevent the same order from appearing twice — no manual checking needed?',
+ objectives: ['Understand the cost of duplicates in real systems', 'Discover sets as a membership uniqueness guarantee', 'Apply set membership to order deduplication'],
+ sampleReasoning: 'active_orders = set(). When Ananya orders biryani: if "biryani" not in active_orders, add it. When Rohan orders: "biryani" is already in the set — reject it. Sets prevent duplicates automatically.',
+ effectivenessScore: 97,
+ theme: 'food-delivery',
+ caseStudyId: 'cs-food-delivery',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Dicts → Sets → try/except',
+ themeEmoji: '🍕'
+ },
+ {
+ title: 'Hostel Startup: The Crashed Order History',
+ difficulty: 'Builder',
+ concepts: ['error handling', 'try/except'],
+ context: 'Night 14. A user types a quantity of -3 samosas. The app tries to calculate the bill: -3 × ₹12 = -₹36. The app charges them NEGATIVE money and then crashes trying to write to the orders file. The crash wipes the entire order history. 47 orders gone.',
+ prompt: 'Dev stares at the empty orders file at 3 AM and says: "A user broke our app with a negative number. We had no plan for this." How would you build a safety net that catches bad input BEFORE it reaches the calculation and file-write — so the app never crashes and data is never lost?',
+ objectives: ['Understand that real programs receive unexpected input', 'Discover defensive programming', 'Map error catching to try/except'],
+ sampleReasoning: 'Wrap the quantity input and bill calculation in a try block. If quantity is negative, raise a ValueError. The except block shows the user an error without crashing — and the order history is never touched.',
+ effectivenessScore: 98,
+ theme: 'food-delivery',
+ caseStudyId: 'cs-food-delivery',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Dicts → Sets → try/except',
+ themeEmoji: '🍕'
+ },
+
+ // ─── UADE THEME 5: THE AI PLAYLIST ────────────────────────────────────────────
+ {
+ title: 'Playlist: One Mood, One Genre',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'You are building an app that learns your music taste. Right now it knows one thing: your current mood. When you\'re in a "study" mood, it plays lo-fi. One mood, one genre.',
+ prompt: 'The app needs to remember what mood you are in right now. How would you store the current mood so the app can decide what to play?',
+ objectives: ['Store state as a variable', 'Understand that apps need to remember things', 'Connect variable to decision-making'],
+ sampleReasoning: 'current_mood = "study" — one variable holds the current state, and the app can check it to select a playlist.',
+ effectivenessScore: 90,
+ theme: 'ai-playlist',
+ caseStudyId: 'cs-ai-playlist',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Loops → Sets → Dicts → Functions',
+ themeEmoji: '🎵'
+ },
+ {
+ title: 'Playlist: Twelve Genres, No Loop',
+ difficulty: 'Explorer',
+ concepts: ['lists', 'loops'],
+ context: 'Your app now knows 12 genres you love: lo-fi, jazz, indie, classical, synthwave, ambient, folk, blues, punk, metal, hip-hop, and bossa nova. You have 12 separate variables. The recommendation engine needs to check all of them. You\'re writing: check_genre1(), check_genre2()... twelve times.',
+ prompt: 'Your code has 12 separate function calls — one for each genre. Adding a 13th genre means adding another line. The recommendation logic cannot adapt dynamically. What structure lets you loop through all genres with a single repeated action?',
+ objectives: ['Understand that repetitive calls need loops', 'Discover that lists enable dynamic iteration', 'Connect recommendation loop to list traversal'],
+ sampleReasoning: 'genres = ["lo-fi", "jazz", ...]. for genre in genres: check_genre(genre) — one loop handles 12 or 1200 genres. Adding a new genre is one append.',
+ effectivenessScore: 95,
+ theme: 'ai-playlist',
+ caseStudyId: 'cs-ai-playlist',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Loops → Sets → Dicts → Functions',
+ themeEmoji: '🎵'
+ },
+ {
+ title: 'Playlist: Shape of You, Again',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'Your recommendation engine keeps surfacing "Shape of You" — you\'ve rated it 4 stars and it keeps showing up. You\'ve heard it 23 times this week. Your recently_played list has the same 5 songs repeated across 200 entries.',
+ prompt: 'You tell the app: "I want variety. Never play the same song twice in one session." The recently_played list doesn\'t prevent repeats because lists allow duplicates. What structure would automatically guarantee that each song in a session appears exactly once, no matter how many times the engine tries to add it?',
+ objectives: ['Experience the frustration of duplicates in recommendations', 'Discover sets as a uniqueness enforcer', 'Apply sets to deduplication of a playlist'],
+ sampleReasoning: 'session_played = set(). When the engine picks a song, check: if song not in session_played, play it and add to the set. Sets reject duplicates — "Shape of You" gets added once and stays there.',
+ effectivenessScore: 96,
+ theme: 'ai-playlist',
+ caseStudyId: 'cs-ai-playlist',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Loops → Sets → Dicts → Functions',
+ themeEmoji: '🎵'
+ },
+ {
+ title: 'Playlist: Storing Play Count AND Rating',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'The app needs to remember two things per song: how many times you played it, and what you rated it (1–5 stars). You have two parallel lists: play_counts[i] and ratings[i]. You sorted one list by count and forgot to sort the other. A song shows a 5-star rating next to the wrong play count.',
+ prompt: 'Your recommendation score is wrong because the two lists got out of sync again. A friend suggests: "What if each song carried all its own information — play count AND rating — as one complete package?" What structure bundles multiple named values for one song together?',
+ objectives: ['Experience sync failure of parallel lists', 'Discover that dictionaries bundle related data', 'Map a song\'s stats to a dictionary'],
+ sampleReasoning: 'songs = {"Shape of You": {"play_count": 23, "rating": 4}}. The song carries its own data. Sort by play_count? The rating comes along automatically.',
+ effectivenessScore: 97,
+ theme: 'ai-playlist',
+ caseStudyId: 'cs-ai-playlist',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Loops → Sets → Dicts → Functions',
+ themeEmoji: '🎵'
+ },
+
+ // ─── UADE THEME 6: THE KOTA COACHING FACTORY ──────────────────────────────────
+ {
+ title: 'Kota: Your JEE Rank Variable',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'You are a JEE aspirant at a Kota coaching center. You just got your mock test rank: 4,217. That\'s your current rank. Everything in your day — which batch you\'re in, which counselor you see — depends on this one number.',
+ prompt: 'The coaching center\'s system needs to store your rank so it can decide your batch placement. What would you call this piece of information, and why does the name matter?',
+ objectives: ['Store a single important value', 'Choose a meaningful name over a generic one', 'Understand how names convey intent'],
+ sampleReasoning: 'my_jee_rank = 4217 — the name tells anyone reading the code exactly what this number represents. rank = 4217 is fine, but x = 4217 tells you nothing.',
+ effectivenessScore: 92,
+ theme: 'kota',
+ caseStudyId: 'cs-kota',
+ themeStep: 1,
+ themeArc: 'Variables → Lists → Dicts → Sorting → Nested structures',
+ themeEmoji: '📚'
+ },
+ {
+ title: 'Kota: Thirty Batchmates, Thirty Variables',
+ difficulty: 'Explorer',
+ concepts: ['lists'],
+ context: 'Your batch has 30 students. The director wants the batch average rank. You have 30 separate variables: rank_arjun, rank_priya, rank_dev... To calculate the average, you add all 30 variables manually. A new student joins — you add rank_newstudent and forget to include them in the average calculation.',
+ prompt: 'The director asks: "Why does adding one student break the average?" The answer is: you are manually managing 30 separate names. What structure would let you add a new rank in ONE place and have the average work automatically?',
+ objectives: ['Feel the fragility of ungrouped data', 'Discover that collections allow dynamic computation', 'Connect list to automatic aggregate operations'],
+ sampleReasoning: 'batch_ranks = [4217, 2891, 6043, ...]. To add a student: batch_ranks.append(new_rank). Average: sum(batch_ranks) / len(batch_ranks). Automatic — no manual updates.',
+ effectivenessScore: 96,
+ theme: 'kota',
+ caseStudyId: 'cs-kota',
+ themeStep: 2,
+ themeArc: 'Variables → Lists → Dicts → Sorting → Nested structures',
+ themeEmoji: '📚'
+ },
+ {
+ title: 'Kota: Find Priya\'s Rank by Name',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'A parent calls and asks: "What is Priya Sharma\'s rank?" You have a list of 30 ranks. You have to find which position Priya is at, then look up that position in the rank list. The director is watching. It takes 40 seconds.',
+ prompt: 'The director says: "In a real coaching center, I need to look up any student\'s rank by NAME — not by finding their position first." What structure maps each student\'s name directly to their rank?',
+ objectives: ['Experience slow sequential lookup under pressure', 'Discover named direct lookup', 'Map student → rank as a dictionary'],
+ sampleReasoning: 'ranks = {"Priya Sharma": 2891, "Arjun Dev": 4217, ...}. ranks["Priya Sharma"] gives her rank immediately — no searching needed.',
+ effectivenessScore: 97,
+ theme: 'kota',
+ caseStudyId: 'cs-kota',
+ themeStep: 3,
+ themeArc: 'Variables → Lists → Dicts → Sorting → Nested structures',
+ themeEmoji: '📚'
+ },
+ {
+ title: 'Kota: The Merit List Algorithm',
+ difficulty: 'Builder',
+ concepts: ['sorting', 'dictionaries', 'algorithms'],
+ context: 'The director wants the top 10 students for the merit scholarship. You have ranks = {"Priya": 2891, "Arjun": 4217, ...}. Lower rank number = better performance. You need to sort by rank value, handle ties, and take the top 10.',
+ prompt: 'The director says: "I need the merit list NOW — sorted by rank, ties broken by name alphabetically, top 10 only." What sequence of reasoning steps turns the dictionary into a sorted, trimmed merit list?',
+ objectives: ['Understand sorting as an algorithm, not magic', 'Sort a dictionary by value', 'Slice a result to a required size'],
+ sampleReasoning: 'Sort the dictionary items by rank value (ascending), then alphabetically for ties. sorted(ranks.items(), key=lambda x: (x[1], x[0]))[:10] gives the top 10 merit list.',
+ effectivenessScore: 95,
+ theme: 'kota',
+ caseStudyId: 'cs-kota',
+ themeStep: 4,
+ themeArc: 'Variables → Lists → Dicts → Sorting → Nested structures',
+ themeEmoji: '📚'
+ },
+
+ // ─── POTTERHEADS THEME SCENARIOS ───────────────────────────────────────────
+ {
+ title: 'Potion Ingredient Dosage',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'Severus Snape asks you to measure Boomslang Skin for Polyjuice Potion.',
+ prompt: 'How would you store the weight in grams so your cauldron brewing notes remember it?',
+ objectives: ['Identify single value', 'Assign a clear name', 'Understand variable storage'],
+ sampleReasoning: 'boomslang_weight = 25 — store the exact weight in a named variable to use in the recipe.',
+ effectivenessScore: 96,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 1,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Spellbook Spell List',
+ difficulty: 'Beginner',
+ concepts: ['lists'],
+ context: 'Harry Potter wants to keep track of all defensive spells learned in DADA class.',
+ prompt: 'How would you group "Expelliarmus", "Stupefy", and "Protego" into a single structure?',
+ objectives: ['Group spells together', 'Recognize list ordering', 'Map spells to a list'],
+ sampleReasoning: 'defense_spells = ["Expelliarmus", "Stupefy", "Protego"] — keep them in one list for fast casting access.',
+ effectivenessScore: 97,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 2,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'House Point Counter',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'Professor McGonagall needs to track House Points for Gryffindor, Slytherin, Ravenclaw, and Hufflepuff.',
+ prompt: 'How would you store each house name directly paired with its score?',
+ objectives: ['Map house to score', 'Enable direct lookup', 'Use a dictionary'],
+ sampleReasoning: 'house_points = {"Gryffindor": 450, "Slytherin": 420, "Ravenclaw": 390, "Hufflepuff": 380} maps house names to scores directly.',
+ effectivenessScore: 98,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 3,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Cauldron Stirring Helper',
+ difficulty: 'Builder',
+ concepts: ['functions'],
+ context: 'Brewing Felix Felicis requires clockwise stirring every 3 minutes.',
+ prompt: 'How would you design a reusable helper function that calculates total stirs required?',
+ objectives: ['Create reusable brewing logic', 'Accept duration parameter', 'Return stir count'],
+ sampleReasoning: 'def calculate_stirs(minutes): return minutes * 4 — write once and reuse for any potion recipe.',
+ effectivenessScore: 96,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 4,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Unforgivable Curse Deduplicator',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'The Ministry of Magic receives duplicate dark magic spell reports from multiple owls.',
+ prompt: 'How would you automatically ensure each reported spell is unique with no duplicates?',
+ objectives: ['Eliminate duplicate reports', 'Guarantee uniqueness', 'Map to Python sets'],
+ sampleReasoning: 'dark_spells = set(owl_reports) automatically removes duplicates.',
+ effectivenessScore: 95,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 1,
+ themeEmoji: '🪄'
+ },
+
+ // ─── MARVEL THEME SCENARIOS ────────────────────────────────────────────────
+ {
+ title: 'Arc Reactor Output Voltage',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'Tony Stark needs J.A.R.V.I.S. to monitor the Mark 85 Arc Reactor power level.',
+ prompt: 'How would you store the current voltage percentage in code?',
+ objectives: ['Identify critical suit value', 'Store in named variable', 'Reference during combat'],
+ sampleReasoning: 'reactor_voltage = 98.5 — keep suit power level in a named variable.',
+ effectivenessScore: 96,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 1,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Avengers Emergency Roster',
+ difficulty: 'Beginner',
+ concepts: ['lists'],
+ context: 'Captain America calls an emergency assembly for Iron Man, Thor, Hulk, and Spider-Man.',
+ prompt: 'How would you store all active Avengers together so J.A.R.V.I.S. can alert them in order?',
+ objectives: ['Group hero names', 'Maintain assembly order', 'Store as a list'],
+ sampleReasoning: 'active_avengers = ["Iron Man", "Thor", "Hulk", "Spider-Man"] stores heroes in a single list.',
+ effectivenessScore: 97,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 2,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'J.A.R.V.I.S. Weapon Diagnostics',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'Tony Stark asks: "J.A.R.V.I.S., check status of repulsors, unibeam, and nanotech shield!"',
+ prompt: 'How would you map each weapon system name directly to its operational status?',
+ objectives: ['Map weapon to status', 'Instant direct lookup', 'Store as dictionary'],
+ sampleReasoning: 'weapon_status = {"repulsors": "READY", "unibeam": "CHARGING", "shield": "ACTIVE"} allows instant lookup by name.',
+ effectivenessScore: 98,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 3,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Repulsor Thrust Calculator',
+ difficulty: 'Builder',
+ concepts: ['functions'],
+ context: 'Iron Man needs to compute flight trajectory thruster force based on suit weight and speed.',
+ prompt: 'How would you package this flight calculation into a reusable function for J.A.R.V.I.S.?',
+ objectives: ['Package thruster math', 'Accept weight and velocity', 'Return force value'],
+ sampleReasoning: 'def calculate_thrust(weight, speed): return weight * speed * 1.5 — reuse across all armor marks.',
+ effectivenessScore: 96,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 4,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Infinity Stone Energy Signature Filter',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'Sensors in Wakanda register cosmic energy spikes. Multiple sensors detect the Space Stone signature.',
+ prompt: 'How would you remove duplicate sensor readings to get only unique stone signatures?',
+ objectives: ['Deduplicate sensor readings', 'Guarantee unique signatures', 'Map to sets'],
+ sampleReasoning: 'unique_signatures = set(sensor_spikes) guarantees unique cosmic signals.',
+ effectivenessScore: 95,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-infinity',
+ themeStep: 1,
+ themeEmoji: '💎'
+ },
+
+ // ─── ANIME THEME SCENARIOS ─────────────────────────────────────────────────
+ {
+ title: 'Chakra Points Tracker',
+ difficulty: 'Beginner',
+ concepts: ['variables'],
+ context: 'Naruto is practicing Nine-Tails Chakra control and needs to track his reserve points.',
+ prompt: 'How would you store Naruto\'s current chakra level in a single variable?',
+ objectives: ['Identify chakra value', 'Name variable clearly', 'Store for Jutsu casting'],
+ sampleReasoning: 'chakra_points = 5000 — store current chakra in a named variable.',
+ effectivenessScore: 96,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 1,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Hidden Leaf Squad Roster',
+ difficulty: 'Beginner',
+ concepts: ['lists'],
+ context: 'Kakashi Sensei forms Squad 7 with Naruto, Sasuke, and Sakura.',
+ prompt: 'How would you group all three shinobi into a single list for mission deployment?',
+ objectives: ['Group squad members', 'Maintain ninja order', 'Store in a list'],
+ sampleReasoning: 'squad_7 = ["Naruto", "Sasuke", "Sakura"] keeps the team together in one list.',
+ effectivenessScore: 97,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 2,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Jutsu Hand Sign Vault',
+ difficulty: 'Explorer',
+ concepts: ['dictionaries'],
+ context: 'The Shinobi Library stores hand sign sequences for Rasengan, Chidori, and Shadow Clone jutsu.',
+ prompt: 'How would you map each Jutsu name to its required hand sign combination?',
+ objectives: ['Map jutsu to hand signs', 'Look up jutsu instantly', 'Store as dictionary'],
+ sampleReasoning: 'jutsu_vault = {"Rasengan": "Ram → Serpent", "Chidori": "Ox → Rabbit → Monkey"} maps jutsu directly to signs.',
+ effectivenessScore: 98,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 3,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Shadow Clone Multiplier',
+ difficulty: 'Builder',
+ concepts: ['functions'],
+ context: 'Naruto wants to calculate total chakra consumed when creating N shadow clones.',
+ prompt: 'How would you write a reusable function to compute total chakra cost per clone count?',
+ objectives: ['Create clone chakra formula', 'Accept clone count', 'Return total chakra required'],
+ sampleReasoning: 'def clone_chakra(clone_count): return clone_count * 50 — calculate chakra cost dynamically.',
+ effectivenessScore: 96,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 4,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Dungeon Rift Monster Index',
+ difficulty: 'Explorer',
+ concepts: ['sets'],
+ context: 'An S-Rank Hunter encounters wild monsters in a dungeon rift and receives duplicate radar pings.',
+ prompt: 'How would you filter out duplicate monster pings to index only unique species?',
+ objectives: ['Filter duplicate pings', 'Store unique species', 'Map to sets'],
+ sampleReasoning: 'unique_monsters = set(radar_pings) removes duplicate dungeon pings.',
+ effectivenessScore: 95,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-pokedex',
+ themeStep: 1,
+ themeEmoji: '🐉'
+ },
+
+ // ─── POTTERHEADS EXTENDED CHAPTER SCENARIOS ──────────────────────────────
+ {
+ title: 'Gryffindor House Points Addition',
+ difficulty: 'Beginner',
+ concepts: ['arithmetic', 'subtraction'],
+ context: 'Harry scores 50 points for catching the Snitch, but Snape deducts 15 for being late.',
+ prompt: 'How would you calculate Gryffindor\'s net points using arithmetic operators?',
+ objectives: ['Perform addition and subtraction', 'Calculate net points', 'Use math operators'],
+ sampleReasoning: 'net_points = 50 - 15 calculates the final house points.',
+ effectivenessScore: 94,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 2,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Spell Incantation Generator',
+ difficulty: 'Beginner',
+ concepts: ['strings'],
+ context: 'Hermione wants to construct full spell commands like "Harry casts Expelliarmus!".',
+ prompt: 'How would you combine the wizard\'s name and the spell name into a single message?',
+ objectives: ['Combine text strings', 'Format incantations', 'Map to Python strings'],
+ sampleReasoning: 'incantation = f"{wizard} casts {spell}!" sticks text pieces together.',
+ effectivenessScore: 95,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 3,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Sorting Hat House Evaluator',
+ difficulty: 'Beginner',
+ concepts: ['conditionals', 'comparisons'],
+ context: 'The Sorting Hat checks a student\'s bravery and wisdom scores to decide their house.',
+ prompt: 'How would you write a decision rule to assign Gryffindor or Ravenclaw based on scores?',
+ objectives: ['Compare trait scores', 'Branch decisions', 'Map to if/else'],
+ sampleReasoning: 'if bravery >= 80: house = "Gryffindor" else if wisdom >= 80: house = "Ravenclaw".',
+ effectivenessScore: 96,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 4,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Cauldron Clockwise Stir Routine',
+ difficulty: 'Explorer',
+ concepts: ['loops', 'while loops'],
+ context: 'Brewing Felix Felicis requires stirring the potion cauldron clockwise exactly 50 times.',
+ prompt: 'How would you make Python repeat the stirring action 50 times automatically?',
+ objectives: ['Identify repeated stir action', 'Loop stir count', 'Map to for/while loop'],
+ sampleReasoning: 'for stir in range(1, 51): print(f"Stir {stir}") repeats the action automatically.',
+ effectivenessScore: 95,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 5,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Restricted Section Scroll Search',
+ difficulty: 'Explorer',
+ concepts: ['search', 'filtering'],
+ context: 'Harry is searching through 100 library scrolls to locate a forbidden dark magic spell.',
+ prompt: 'How would you filter the scroll list to find only scrolls containing "Dark Magic"?',
+ objectives: ['Scan list items', 'Filter by keyword', 'Map to search/filtering'],
+ sampleReasoning: 'dark_scrolls = [scroll for scroll in library if "Dark" in scroll] filters the scrolls.',
+ effectivenessScore: 94,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 5,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Exploding Cauldron Exception Shield',
+ difficulty: 'Builder',
+ concepts: ['error handling', 'try except'],
+ context: 'A potion heat spikes above 300 degrees and threatens to blow up the potions dungeon.',
+ prompt: 'How would you catch this overheating error so your code casts a shield charm instead of crashing?',
+ objectives: ['Detect heat overload', 'Catch exception', 'Map to try/except'],
+ sampleReasoning: 'try: brew_potion() except OverheatError: cast_shield() prevents a crash.',
+ effectivenessScore: 97,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-potions',
+ themeStep: 6,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Triwizard Tournament Leaderboard Ranker',
+ difficulty: 'Builder',
+ concepts: ['algorithms', 'sorting'],
+ context: 'Dumbledore needs to sort the final Triwizard scores from highest to lowest score.',
+ prompt: 'How would you order champion scores from highest to lowest?',
+ objectives: ['Order champion scores', 'Sort list descending', 'Map to sorting algorithms'],
+ sampleReasoning: 'scores.sort(reverse=True) ranks the champions in descending order.',
+ effectivenessScore: 96,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 6,
+ themeEmoji: '🧙♂️'
+ },
+ {
+ title: 'Ministry Secret Archive Scroll Log',
+ difficulty: 'Builder',
+ concepts: ['files', 'file io'],
+ context: 'The Ministry of Magic wants to save all recorded spell logs into a permanent parchment file.',
+ prompt: 'How would you write text into a file so it stays saved even after shutting down your laptop?',
+ objectives: ['Open file for writing', 'Save spell logs', 'Map to file I/O'],
+ sampleReasoning: 'with open("spells.txt", "w") as f: f.write(log) saves data permanently.',
+ effectivenessScore: 95,
+ theme: 'potterheads',
+ caseStudyId: 'cs-potter-spells',
+ themeStep: 7,
+ themeEmoji: '🧙♂️'
+ },
+
+ // ─── MARVEL THEME EXTENDED SCENARIOS ───────────────────────────────────────
+ {
+ title: 'Suit Power Drain Calculator',
+ difficulty: 'Beginner',
+ concepts: ['arithmetic', 'subtraction'],
+ context: 'Iron Man has 1000 kW power. Repulsor blasts use 250 kW and shield uses 180 kW.',
+ prompt: 'How would you compute the remaining suit energy using subtraction operators?',
+ objectives: ['Subtract power drain', 'Calculate remaining energy', 'Use arithmetic operators'],
+ sampleReasoning: 'remaining = 1000 - 250 - 180 calculates power remaining.',
+ effectivenessScore: 94,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 5,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'J.A.R.V.I.S. Audio Broadcast Formatter',
+ difficulty: 'Beginner',
+ concepts: ['strings'],
+ context: 'J.A.R.V.I.S. needs to construct HUD alert strings like "Warning Mr. Stark: Thruster #2 Low".',
+ prompt: 'How would you format variables into a clear status text string?',
+ objectives: ['Format status message', 'Combine variables with text', 'Map to strings'],
+ sampleReasoning: 'alert = f"Warning {user}: {system} {status}" formats HUD alerts.',
+ effectivenessScore: 95,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 6,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Nanotech Threat Level Defense Protocol',
+ difficulty: 'Beginner',
+ concepts: ['conditionals', 'comparisons'],
+ context: 'Mark 85 sensors evaluate hostile threat score from 0 to 100.',
+ prompt: 'How would you deploy nanotech shields if threat > 80, repulsors if > 50, or scan only?',
+ objectives: ['Evaluate threat score', 'Branch suit defense', 'Map to if/elif/else'],
+ sampleReasoning: 'if threat > 80: deploy_shield() elif threat > 50: deploy_repulsors().',
+ effectivenessScore: 96,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 7,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Iron Man Subsystem Thruster Diagnostics',
+ difficulty: 'Explorer',
+ concepts: ['loops', 'while loops'],
+ context: 'J.A.R.V.I.S. needs to check all 100 thruster nodes across the armor one by one.',
+ prompt: 'How would you repeat the diagnostic check for every thruster node automatically?',
+ objectives: ['Iterate thruster nodes', 'Perform check per node', 'Map to loops'],
+ sampleReasoning: 'for thruster in armor_thrusters: check_status(thruster) repeats for all nodes.',
+ effectivenessScore: 95,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 8,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Vibranium Radar Signature Scan',
+ difficulty: 'Explorer',
+ concepts: ['search', 'filtering'],
+ context: 'Wakanda radar receives satellite pings across Earth. You need to find all "Vibranium" pings.',
+ prompt: 'How would you search radar readings and filter out non-vibranium signals?',
+ objectives: ['Filter radar pings', 'Extract matching signals', 'Map to search/filter'],
+ sampleReasoning: 'vibranium_pings = [p for p in radar_pings if "Vibranium" in p] filters radar data.',
+ effectivenessScore: 94,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-infinity',
+ themeStep: 2,
+ themeEmoji: '💎'
+ },
+ {
+ title: 'Arc Reactor Power Overload Bypass',
+ difficulty: 'Builder',
+ concepts: ['error handling', 'try except'],
+ context: 'An enemy blast causes Arc Reactor energy voltage to surge above 1200V.',
+ prompt: 'How would you handle this voltage surge exception so J.A.R.V.I.S. diverts power to heat sinks without shutting down?',
+ objectives: ['Detect power surge', 'Catch voltage error', 'Map to try/except'],
+ sampleReasoning: 'try: fire_unibeam() except PowerSurge: divert_to_heatsink() handles surges.',
+ effectivenessScore: 97,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 9,
+ themeEmoji: '🦾'
+ },
+ {
+ title: 'Stark Satellite Threat Priority Ranker',
+ difficulty: 'Builder',
+ concepts: ['algorithms', 'sorting'],
+ context: 'Stark satellite defense detects 5 incoming alien ships with different danger ratings.',
+ prompt: 'How would you sort the incoming ships from highest threat score to lowest?',
+ objectives: ['Order threat levels', 'Sort list descending', 'Map to sorting algorithms'],
+ sampleReasoning: 'ships.sort(key=lambda s: s.threat, reverse=True) prioritizes targets.',
+ effectivenessScore: 96,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-infinity',
+ themeStep: 3,
+ themeEmoji: '💎'
+ },
+ {
+ title: 'Stark Telemetry Mission Log Archiver',
+ difficulty: 'Builder',
+ concepts: ['files', 'file io'],
+ context: 'J.A.R.V.I.S. must save flight telemetry and battle statistics into a permanent log file.',
+ prompt: 'How would you write flight telemetry data into a JSON file for permanent storage?',
+ objectives: ['Open file for writing', 'Serialize telemetry data', 'Map to file I/O'],
+ sampleReasoning: 'with open("flight_log.json", "w") as f: json.dump(log_data, f) saves telemetry.',
+ effectivenessScore: 95,
+ theme: 'marvel',
+ caseStudyId: 'cs-marvel-jarvis',
+ themeStep: 10,
+ themeEmoji: '🦾'
+ },
+
+ // ─── ANIME THEME EXTENDED SCENARIOS ────────────────────────────────────────
+ {
+ title: 'Shadow Clone Chakra Cost Calculator',
+ difficulty: 'Beginner',
+ concepts: ['arithmetic', 'subtraction'],
+ context: 'Naruto has 9000 chakra. Creating 10 shadow clones uses 1500 chakra.',
+ prompt: 'How would you compute Naruto\'s remaining chakra using subtraction?',
+ objectives: ['Subtract chakra spent', 'Compute remaining chakra', 'Use math operators'],
+ sampleReasoning: 'remaining = 9000 - 1500 calculates remaining chakra.',
+ effectivenessScore: 94,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 5,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Jutsu Invocation Scroll Formatter',
+ difficulty: 'Beginner',
+ concepts: ['strings'],
+ context: 'A shinobi scroll needs to generate jutsu announcements like "Naruto unleashes Rasengan!".',
+ prompt: 'How would you format the ninja name and jutsu name into an incantation string?',
+ objectives: ['Combine text strings', 'Format jutsu incantations', 'Map to strings'],
+ sampleReasoning: 'announcement = f"{ninja} unleashes {jutsu}!" formats the scroll text.',
+ effectivenessScore: 95,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 6,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Chunin Exam Promotion Evaluator',
+ difficulty: 'Beginner',
+ concepts: ['conditionals', 'comparisons'],
+ context: 'Proctors check shinobi exam marks: score >= 85 is Chunin, score >= 60 is Genin.',
+ prompt: 'How would you write a decision rule to determine the ninja rank based on score?',
+ objectives: ['Compare exam score', 'Branch rank results', 'Map to if/elif/else'],
+ sampleReasoning: 'if score >= 85: rank = "Chunin" elif score >= 60: rank = "Genin".',
+ effectivenessScore: 96,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 7,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Parallel Shadow Clone Training Reps',
+ difficulty: 'Explorer',
+ concepts: ['loops', 'while loops'],
+ context: 'Naruto summons 100 shadow clones to practice tree-climbing training reps simultaneously.',
+ prompt: 'How would you repeat the training step for every shadow clone in code?',
+ objectives: ['Iterate shadow clones', 'Perform training action', 'Map to loops'],
+ sampleReasoning: 'for clone in range(1, 101): train_clone(clone) repeats for all clones.',
+ effectivenessScore: 95,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 8,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'S-Rank Dungeon Boss Finder',
+ difficulty: 'Explorer',
+ concepts: ['search', 'filtering'],
+ context: 'An S-Rank Hunter scans a dungeon rift containing 50 monsters to find S-Rank beasts.',
+ prompt: 'How would you search the monster list and filter out non-S-rank creatures?',
+ objectives: ['Filter dungeon monsters', 'Identify S-Rank beasts', 'Map to search/filtering'],
+ sampleReasoning: 'bosses = [m for m in dungeon_monsters if m.rank == "S"] filters the beasts.',
+ effectivenessScore: 94,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-pokedex',
+ themeStep: 2,
+ themeEmoji: '🐉'
+ },
+ {
+ title: 'Chakra Exhaustion Substitution Guard',
+ difficulty: 'Builder',
+ concepts: ['error handling', 'try except'],
+ context: 'During battle, a shinobi\'s chakra drops below critical level while casting a jutsu.',
+ prompt: 'How would you catch this low-chakra exception to automatically trigger a wood clone substitution?',
+ objectives: ['Detect low chakra', 'Catch exception', 'Map to try/except'],
+ sampleReasoning: 'try: cast_jutsu() except ChakraExhausted: trigger_substitution() handles exhaustion.',
+ effectivenessScore: 97,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 9,
+ themeEmoji: '⚔️'
+ },
+ {
+ title: 'Shinobi Guild Power Ranker',
+ difficulty: 'Builder',
+ concepts: ['algorithms', 'sorting'],
+ context: 'The Hokage needs to rank 20 shinobi by combat power level from highest to lowest.',
+ prompt: 'How would you order the list of shinobi scores from highest to lowest?',
+ objectives: ['Order combat scores', 'Sort list descending', 'Map to sorting algorithms'],
+ sampleReasoning: 'ninja_list.sort(key=lambda n: n.power, reverse=True) ranks the shinobi.',
+ effectivenessScore: 96,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-pokedex',
+ themeStep: 3,
+ themeEmoji: '🐉'
+ },
+ {
+ title: 'Forbidden Leaf Scroll Archive Saver',
+ difficulty: 'Builder',
+ concepts: ['files', 'file io'],
+ context: 'The Hokage wants to record secret ninjutsu scroll instructions into a permanent file archive.',
+ prompt: 'How would you write jutsu scroll instructions into a file for permanent archival?',
+ objectives: ['Open file for writing', 'Write scroll text', 'Map to file I/O'],
+ sampleReasoning: 'with open("scrolls.txt", "w") as f: f.write(scroll_text) archives the jutsu permanently.',
+ effectivenessScore: 95,
+ theme: 'anime',
+ caseStudyId: 'cs-anime-ninja',
+ themeStep: 10,
+ themeEmoji: '⚔️'
}
];
async function run() {
await resetData(scenarios);
console.log(`Seeded ${scenarios.length} PyBe scenarios`);
+ console.log(` - Classic scenarios: ${scenarios.filter(s => s.theme === 'classic').length}`);
+ console.log(` - Chai Stall theme: ${scenarios.filter(s => s.theme === 'chai-stall').length}`);
+ console.log(` - ISRO theme: ${scenarios.filter(s => s.theme === 'isro').length}`);
+ console.log(` - Instagram theme: ${scenarios.filter(s => s.theme === 'instagram').length}`);
+ console.log(` - Food Delivery theme: ${scenarios.filter(s => s.theme === 'food-delivery').length}`);
+ console.log(` - AI Playlist theme: ${scenarios.filter(s => s.theme === 'ai-playlist').length}`);
+ console.log(` - Kota theme: ${scenarios.filter(s => s.theme === 'kota').length}`);
+ console.log(` - Potterheads theme: ${scenarios.filter(s => s.theme === 'potterheads').length}`);
+ console.log(` - Marvel theme: ${scenarios.filter(s => s.theme === 'marvel').length}`);
+ console.log(` - Anime theme: ${scenarios.filter(s => s.theme === 'anime').length}`);
}
run().catch((error) => {