diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ce8bd52
--- /dev/null
+++ b/README.md
@@ -0,0 +1,186 @@
+
+
+
+
+# MichiganReads AI
+
+### AI-powered reading tutor for Michigan Kโ8 students
+
+**Built in 48 hours at [HackMichigan 2026](https://www.hackmichigan.dev) ยท TechTown Detroit**
+
+[](https://github.com/AliAlj/FourG)
+[](LICENSE)
+[](#)
+
+[๐ Live Demo](https://fourg-1.onrender.com) ยท [๐ Docs](#setup) ยท [๐ฅ Demo Video](#)
+
+
+
+---
+
+## The Problem
+
+Nearly **two-thirds of Michigan third graders** are not reading at grade level. Teachers can't give every student individualized feedback. Private tutors are out of reach for most families. Existing edtech tools present content โ but never listen.
+
+**MichiganReads AI listens.**
+
+---
+
+## What It Does
+
+Students open the app, pick a book, and read aloud. The app scores every word in real time and connects them to **Bookworm** โ an AI tutor that knows exactly where they struggled and coaches them personally.
+
+
+
+
+
+### ๐ For Students
+- Read passages & illustrated books aloud
+- Word-level pronunciation scoring
+- Highlighted problem words (< 75% accuracy)
+- Comprehension questions after each session
+- Progress charts & reading streak tracker
+- **Bookworm AI tutor** โ voice or text
+- Bilingual: English & Spanish
+
+
+
+
+### ๐ฉโ๐ซ For Teachers
+- Create classes with join codes
+- Assign books with due dates
+- Live activity feed โ see who's reading now
+- Analytics: score trends, difficult words
+- Student drill-downs with full history
+- AI-generated weekly progress summaries
+
+
+
+
+
+---
+
+## Tech Stack
+
+| Layer | Technology |
+|---|---|
+| ๐ค Speech Recognition | Microsoft Azure Speech Services |
+| ๐ค AI Tutor | IBM watsonx Granite 4 |
+| ๐ Text-to-Speech | Google Cloud TTS |
+| ๐ Summaries | Groq โ Llama 3.1 |
+| ๐๏ธ Database & Auth | Supabase (PostgreSQL) |
+| โ๏ธ Backend | Python 3.10+ / Flask |
+| ๐ Frontend | HTML5 ยท CSS3 ยท Vanilla JS |
+| ๐ Charts | Chart.js |
+
+---
+
+## Michigan Library
+
+The built-in passage library is written for Michigan students โ not generic content.
+
+| Grade | Title | Topic |
+|---|---|---|
+| 3 | Eastern Market: The Heart of Detroit | Detroit History |
+| 3 | The Sleeping Bear Dunes | Michigan Nature |
+| 3 | The Gray Wolf Returns to Michigan | Animals |
+| 4 | Michigan's Greatest Treasure | Great Lakes |
+| 4 | How Detroit Built the American Dream | Michigan Industry |
+| 5 | Detroit and the Road to Freedom | Civil Rights |
+| 6 | Dearborn: Michigan's Arab American Capital | Community |
+| 7 | Motown: The Sound That Changed America | Michigan History |
+
+Plus **9 illustrated books** for grades 1โ4 (Peter Rabbit, Goldilocks, Three Little Pigs, and more).
+
+---
+
+## Project Structure
+
+```
+FourG/
+โโโ readingai/
+โ โโโ frontend/ # Single-page web app
+โ โ โโโ index.html
+โ โ โโโ css/styles.css
+โ โ โโโ js/ # app, auth, teacher, bookworm, library...
+โ โโโ bookworm/ # Python Flask backend (TTS proxy)
+โ โโโ app.py
+โ โโโ requirements.txt
+โโโ supabase/ # Edge Functions + local dev config
+โ โโโ functions/
+โ โโโ get-ibm-token/
+โ โโโ ibm-chat/
+โ โโโ fetch-gutenberg/
+โโโ docs/ # Marketing landing page
+```
+
+---
+
+## Setup
+
+### Prerequisites
+- Python 3.10+
+- A [Supabase](https://supabase.com) project
+- API keys: Azure Speech ยท Google Cloud TTS ยท IBM watsonx ยท Groq
+
+### 1. Backend
+
+```bash
+cd readingai/bookworm
+python3 -m venv venv && source venv/bin/activate
+pip install -r requirements.txt
+cp .env.example .env
+python app.py # โ http://localhost:5000
+```
+
+### 2. Frontend
+
+```bash
+cd readingai/frontend
+python3 -m http.server 8000 # โ http://localhost:8000
+```
+
+> Add your API keys to `readingai/frontend/config.js` โ **never commit this file.**
+
+### 3. Supabase (optional local dev)
+
+```bash
+supabase start # Postgres + Studio on local ports
+supabase stop
+```
+
+---
+
+## API Reference
+
+| Method | Endpoint | Description |
+|---|---|---|
+| `GET` | `/health` | Health check |
+| `POST` | `/api/tts/synthesize` | Synthesize speech |
+| `POST` | `/api/tts/synthesize-slow` | 0.5ร speed for word practice |
+| `GET` | `/api/tts/voices` | List available voices |
+
+---
+
+## Privacy & Compliance
+
+Designed to comply with **COPPA**, **FERPA**, and the **Michigan Student Data Privacy Act**. No student data is sold or shared with third parties. Bilingual (English/Spanish) to serve all Michigan families.
+
+---
+
+## Contributing
+
+```bash
+git checkout -b feature/your-feature
+# make changes
+git push origin feature/your-feature
+# open a pull request โ main
+```
+
+---
+
+
+
+Built with โค๏ธ in Detroit ยท HackMichigan 2026 ยท [COMPASS Detroit](https://compass-detroit.com)
+
+
diff --git a/readingai/frontend/js/bookworm.js b/readingai/frontend/js/bookworm.js
index ebcba0a..fb59d01 100644
--- a/readingai/frontend/js/bookworm.js
+++ b/readingai/frontend/js/bookworm.js
@@ -8,6 +8,23 @@ let bookwormConfidenceAsked = false;
let bookwormConfidenceLevel = null;
let bookwormRecurringWords = [];
+function openBookwormIntro() {
+ const bubble = document.getElementById('wormSpeechBubble');
+ const text = document.getElementById('wormSpeechText');
+ if (!bubble || !text) return;
+
+ text.textContent = "Hey! I'm Bookworm, your AI reading buddy powered by IBM Granite ๐ I help students read out loud, understand stories, and learn new words โ and help teachers track progress. Tap me to chat!";
+ bubble.style.display = 'block';
+
+ bookwormSpeak(text.textContent);
+}
+
+function dismissWormBubble() {
+ const bubble = document.getElementById('wormSpeechBubble');
+ if (bubble) bubble.style.display = 'none';
+ openBookwormHelp();
+}
+
function openBookworm(context) {
const resumingSameBook = bookwormContext?.title === context.title && bookwormHistory.length > 0;
@@ -58,13 +75,26 @@ function openBookwormFromLibrary(idx) {
}
function openBookwormHelp() {
- bookwormContext = { title: null, passage: null, score: null, difficultWords: [], studentName: currentStudent?.name || 'there', grade: currentStudent?.grade || '' };
+ if (currentBook && currentScreenId === 'readingScreen') {
+ openBookworm({
+ title: currentBook.title,
+ passage: currentBook.text || '',
+ score: null,
+ difficultWords: [],
+ studentName: currentStudent?.name || 'there',
+ grade: currentStudent?.grade || ''
+ });
+ return;
+ }
+
+ bookwormContext = null;
bookwormHistory = [];
bookwormChallengeWords = [];
bookwormConfidenceAsked = false;
bookwormConfidenceLevel = null;
bookwormRecurringWords = [];
document.getElementById('bookwormMessages').innerHTML = '';
+ document.getElementById('bookwormQuickBtns').style.display = 'flex';
document.getElementById('bookwormPanel').classList.add('open');
const greeting = "Hi! I'm BookWorm, your reading helper. How can I help you today?";
addBookwormBubble(greeting, 'worm');
@@ -90,11 +120,90 @@ function addBookwormBubble(text, role) {
return el;
}
+async function bookwormQuickAsk(question) {
+ document.getElementById('bookwormQuickBtns').style.display = 'none';
+
+ if (question.includes('scores')) {
+ await handleStudentScoreQuestion();
+ return;
+ }
+
+ await handleStudentMessage(question);
+}
+
+async function handleStudentScoreQuestion() {
+ if (!currentStudent?.userId) {
+ await handleStudentMessage('What are my reading scores so far?');
+ return;
+ }
+
+ addBookwormBubble('What are my reading scores so far?', 'student');
+ const typingEl = addBookwormBubble('Bookworm is thinking...', 'typing');
+ typingEl.id = 'bookwormTyping';
+
+ try {
+ const { data: sessions } = await sb.from('reading_sessions')
+ .select('book_title, overall_score, accuracy_score, fluency_score, created_at')
+ .eq('student_id', currentStudent.userId)
+ .order('created_at', { ascending: false })
+ .limit(10);
+
+ document.getElementById('bookwormTyping')?.remove();
+
+ if (!sessions || sessions.length === 0) {
+ const reply = `You haven't recorded any reading sessions yet, ${currentStudent.name}! Tap a book in your library and start reading to get your first score. ๐`;
+ addBookwormBubble(reply, 'worm');
+ bookwormHistory.push({ role: 'assistant', content: reply });
+ bookwormSpeak(reply);
+ return;
+ }
+
+ const avg = Math.round(sessions.reduce((s, r) => s + (r.overall_score || 0), 0) / sessions.length);
+ const best = Math.max(...sessions.map(r => r.overall_score || 0));
+ const sessionLines = sessions.map(r => {
+ const date = new Date(r.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
+ return `${date}: "${r.book_title}" โ ${r.overall_score}%`;
+ }).join('\n');
+
+ const factPrompt = `VERIFIED DATA FROM DATABASE โ use ONLY these exact numbers, do not make up any other figures:
+Student: ${currentStudent.name}, Grade ${currentStudent.grade}
+Total sessions: ${sessions.length}
+Average score: ${avg}%
+Best score: ${best}%
+Recent sessions (newest first):
+${sessionLines}
+
+The student just asked: "What are my reading scores so far?"
+Respond warmly in 3-4 sentences using only the exact numbers above. Highlight their best score and average. Encourage them. Do not mention any book, score, or streak not listed above.`;
+
+ const res = await fetch('https://fourg-44vh.onrender.com/api/bookworm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ system: factPrompt,
+ messages: [],
+ max_tokens: 150
+ })
+ });
+ const data = await res.json();
+ const reply = data.reply || `You've read ${sessions.length} session${sessions.length > 1 ? 's' : ''} with an average of ${avg}% and a best score of ${best}%, ${currentStudent.name}! Keep it up! ๐`;
+
+ addBookwormBubble(reply, 'worm');
+ bookwormHistory.push({ role: 'user', content: 'What are my reading scores so far?' });
+ bookwormHistory.push({ role: 'assistant', content: reply });
+ bookwormSpeak(reply);
+ } catch {
+ document.getElementById('bookwormTyping')?.remove();
+ await handleStudentMessage('What are my reading scores so far?');
+ }
+}
+
async function bookwormSend() {
const input = document.getElementById('bookwormInput');
const text = input.value.trim();
if (!text) return;
input.value = '';
+ document.getElementById('bookwormQuickBtns').style.display = 'none';
await handleStudentMessage(text);
}
@@ -114,6 +223,35 @@ async function handleStudentMessage(text) {
document.getElementById('bookwormTyping')?.remove();
}
+function showConfidenceButtons() {
+ bookwormConfidenceAsked = true;
+ const messages = document.getElementById('bookwormMessages');
+ const el = document.createElement('div');
+ el.className = 'bookworm-bubble worm';
+ el.id = 'confidenceBubble';
+ el.innerHTML = `How did reading feel today?
+
+
+
+
+
`;
+ messages.appendChild(el);
+ messages.scrollTop = messages.scrollHeight;
+}
+
+function handleConfidenceChoice(level) {
+ bookwormConfidenceLevel = level;
+ document.getElementById('confidenceBubble')?.remove();
+ const label = level === 'easy' ? '๐ Easy' : level === 'medium' ? '๐ Medium' : '๐ Hard';
+ addBookwormBubble(label, 'student');
+ bookwormHistory.push({ role: 'user', content: `Reading felt ${level} today.` });
+
+ const replies = {
+ easy: `That's awesome โ feeling confident means you're growing as a reader! ๐ What part of the passage did you like best?`,
+ medium: `Medium is totally fine โ that means you're being challenged in just the right way! What was the trickiest part?`,
+ hard: `Thank you for being honest โ hard readings are how we get stronger! What made it feel difficult?`
+ };
+ const reply = replies[level];
addBookwormBubble(reply, 'worm');
bookwormHistory.push({ role: 'assistant', content: reply });
bookwormSpeak(reply);
@@ -123,6 +261,9 @@ async function handleStudentMessage(text) {
if (!bookwormConfidenceAsked && bookwormContext?.score !== null && studentTurns === 1) {
setTimeout(showConfidenceButtons, 600);
}
+ if (studentTurns === 1 && bookwormRecurringWords.length > 0) {
+ setTimeout(() => showStruggleWordDrill(bookwormRecurringWords), 1200);
+ }
}
function showStruggleWordDrill(words) {
@@ -207,35 +348,31 @@ async function callIBM() {
? `\nRECURRING STRUGGLE WORDS โ this student has mispronounced these across multiple sessions: ${bookwormRecurringWords.join(', ')}. Bring one up early and naturally: "Hey, last time you had trouble with '____' โ want to try it again today?"`
: '';
+ const assignedBookList = (libraryBooks || []).map(b => `"${b.title}"`).join(', ') || 'none assigned yet';
+
const system = `You are Bookworm, a warm and encouraging reading tutor for elementary school students.${currentLanguage === 'es' ? ' Respond only in Spanish. Use simple, friendly Spanish for an elementary student.' : ''}
Student name: ${bookwormContext.studentName}, Grade ${bookwormContext.grade}
-Book/passage they read: "${bookwormContext.title}"
-Opening of the passage: "${(bookwormContext.passage || '').substring(0, 250)}"
${scoreInfo}
-Words they struggled to pronounce today: ${difficultList}${memoryBlock}${challengeBlock}${recurringBlock}
+Words they struggled to pronounce today: ${difficultList}
+The student's assigned books (ONLY suggest these if recommending another book): ${assignedBookList}${memoryBlock}${challengeBlock}${recurringBlock}
-Your rules:
-- Keep every reply to 2-3 short sentences max โ kids lose focus quickly
+Rules:
+- Keep every reply to 2-3 short sentences max
- Use simple, friendly, grade-appropriate language
- Ask only one question at a time
-- Focus on comprehension: what happened, why, how it connects to their life
-- Reference the student's score naturally when relevant (e.g. "You scored 84% โ your fluency is really improving!")
-- If they ask about a word's meaning, explain it simply with an example
-- If they mention a word they found hard to say, give a one-sentence tip (e.g. "Say it in parts: com-mu-ni-ty")
-- Be enthusiastic and positive โ never say anything discouraging
-- Use their name occasionally to keep it personal
-- Sound like a fun friend who loves books, not a teacher giving a test
-- Never say you cannot see or access the student's score โ you always have it`;
-
- const { data, error } = await sb.functions.invoke('ibm-chat', {
- body: {
- messages: [{ role: 'system', content: system }, ...bookwormHistory],
- max_tokens: 120,
- project_id: IBM_PROJECT_ID
- }
+- Be enthusiastic and positive โ never discouraging
+- Use their name occasionally
+- Never say you cannot see the student's score โ you always have it
+- Never suggest a book that is not in the assigned books list above`;
+
+ const res = await fetch('https://fourg-44vh.onrender.com/api/bookworm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ system, messages: bookwormHistory, max_tokens: 120 })
});
- if (error || !data?.content) throw new Error(error || 'No content');
- return data.content;
+ const data = await res.json();
+ if (!data.reply) throw new Error('No reply');
+ return data.reply;
}
// โโ Cross-session memory โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@@ -275,9 +412,7 @@ async function loadBookwormMemory() {
.slice(0, 3)
.map(([word]) => word);
- if (bookwormRecurringWords.length > 0) {
- showStruggleWordDrill(bookwormRecurringWords);
- }
+ // Struggle word drill is shown after first student message, not on open
const readingLines = readings.filter(r => r.overall_score != null && !isNaN(r.overall_score)).map(r => {
const daysAgo = Math.round((Date.now() - new Date(r.created_at)) / 86400000);
@@ -427,3 +562,6 @@ function stopBookwormListening() {
if (btn) btn.classList.remove('listening');
if (input) input.placeholder = 'Type or tap ๐๏ธ to speak...';
}
+
+
+}
diff --git a/readingai/frontend/js/passages.js b/readingai/frontend/js/passages.js
index 8903e05..886651a 100644
--- a/readingai/frontend/js/passages.js
+++ b/readingai/frontend/js/passages.js
@@ -46,11 +46,29 @@ const passages = [
title: "How Detroit Built the American Dream",
text: "Detroit is known around the world as the Motor City, and for good reason. In the early twentieth century, Henry Ford revolutionized manufacturing by creating the moving assembly line at his Highland Park plant in 1913. This innovation allowed cars to be built faster and more affordably than ever before, putting automobile ownership within reach of ordinary American families for the first time. At its peak, Detroit's automotive industry employed hundreds of thousands of workers, drawing people from across the United States and around the world to Michigan in search of good paying jobs and a better life. The auto industry helped build the American middle class and made Michigan one of the most prosperous states in the nation."
},
+ {
+ grade: 3,
+ topic: "Michigan Nature",
+ title: "The Sleeping Bear Dunes",
+ text: "Sleeping Bear Dunes National Lakeshore sits along the northwestern shore of Michigan's Lower Peninsula, on the coast of Lake Michigan. The park is famous for its enormous sand dunes, some rising more than 400 feet above the lake. According to a Native American legend, the dunes were formed when a mother bear and her two cubs swam across Lake Michigan to escape a forest fire in Wisconsin. The mother bear made it to shore and climbed a bluff to wait for her cubs, but they disappeared beneath the waves. The Great Spirit Manitou created the North and South Manitou Islands to mark where the cubs had sunk, and the lone dune on the mainland became the sleeping mother bear. Today, millions of visitors come to Sleeping Bear Dunes every year to hike, climb the dunes, and enjoy the stunning view of Lake Michigan from the top."
+ },
+ {
+ grade: 3,
+ topic: "Animals",
+ title: "The Gray Wolf Returns to Michigan",
+ text: "For many decades, gray wolves had disappeared from most of Michigan. Hunters and farmers had driven them out because wolves sometimes attacked farm animals. But in the 1990s, wolves from Canada began crossing into the Upper Peninsula of Michigan on their own. Today, Michigan is home to over 600 gray wolves, one of the largest wolf populations in the lower 48 states. Wolves play an important role in the ecosystem. They hunt deer and keep the deer population from growing too large, which helps forests stay healthy. Scientists call this a predator and prey relationship. When one animal is removed from an ecosystem, everything else is affected. The return of wolves to Michigan has taught people an important lesson about how every living thing is connected in nature."
+ },
{
grade: 6,
topic: "Dearborn Community",
title: "Dearborn: Michigan's Arab American Capital",
text: "Dearborn, Michigan is home to one of the largest Arab American communities outside of the Middle East. People from Lebanon, Yemen, Iraq, and many other countries have made Dearborn their home over the past century, drawn by the jobs available at Ford's River Rouge Complex and other Michigan industries. Today, Dearborn's Arab American community has built a rich cultural life in Michigan, with restaurants, mosques, community centers, and businesses that reflect the traditions and values of their homelands while contributing to the fabric of Michigan life. The Arab American National Museum, located in downtown Dearborn, is the only museum in the United States dedicated to documenting and sharing the Arab American experience."
+ },
+ {
+ grade: 7,
+ topic: "Michigan History",
+ title: "Motown: The Sound That Changed America",
+ text: "In 1959, a young songwriter named Berry Gordy borrowed eight hundred dollars from his family and started a small record label in a house on West Grand Boulevard in Detroit. He called it Motown Records, short for Motor Town, and it would go on to become one of the most successful music companies in American history. Gordy had a vision: create music that would appeal to everyone, regardless of race. During the 1960s, a time of great racial division in America, Motown's artists โ including Stevie Wonder, Diana Ross and the Supremes, Marvin Gaye, and the Jackson Five โ played on radio stations and in living rooms across the country, reaching both Black and white audiences. Motown music became the soundtrack of the civil rights era, spreading messages of love, hope, and dignity at a time when America desperately needed them. The music born in that little Detroit house still echoes around the world today."
}
];
@@ -154,5 +172,109 @@ const illustratedBooks = [
image: "https://upload.wikimedia.org/wikipedia/commons/d/d8/Jack_escaping_from_the_Giant.jpg"
}
]
+ },
+ {
+ grade: 1,
+ topic: "Classic Story",
+ title: "The Three Little Pigs",
+ author: "L. Leslie Brooke",
+ cover_url: "https://upload.wikimedia.org/wikipedia/commons/f/f5/Three_little_pigs%2C_Img008.jpg",
+ type: "illustrated",
+ pages: [
+ {
+ text: "Once upon a time, three little pigs left home to build their own houses. The first pig built a house of straw. The second pig built a house of sticks. The third pig worked very hard and built a house of bricks.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/f/f5/Three_little_pigs%2C_Img008.jpg"
+ },
+ {
+ text: "A big bad wolf came along. He went to the straw house and said, Little pig, little pig, let me come in! The pig said, No! So the wolf huffed and puffed and blew the house down.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/c/cf/Three_little_pigs%2C_Img010.jpg"
+ },
+ {
+ text: "The wolf blew down the stick house too. Both pigs ran to their brother's brick house as fast as they could. They slammed the door shut just in time.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/9/92/Three_little_pigs%2C_Img012.jpg"
+ },
+ {
+ text: "The wolf huffed and puffed with all his might, but he could not blow down the brick house. At last he gave up and ran away into the forest. The three little pigs were safe at last!",
+ image: "https://upload.wikimedia.org/wikipedia/commons/c/c9/Three_little_pigs%2C_Img014.jpg"
+ }
+ ]
+ },
+ {
+ grade: 2,
+ topic: "Classic Story",
+ title: "The Ugly Duckling",
+ author: "Hans Christian Andersen",
+ cover_url: "https://upload.wikimedia.org/wikipedia/commons/2/2a/Hoytema-Het-leelijke-jonge-eendje.jpg",
+ type: "illustrated",
+ pages: [
+ {
+ text: "On a farm near a pond, a mother duck sat on her eggs. One by one, the eggs cracked open and little yellow ducklings came out. But one egg was much bigger than the rest.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/2/2a/Hoytema-Het-leelijke-jonge-eendje.jpg"
+ },
+ {
+ text: "When the big egg hatched, out came a gray, clumsy bird that looked nothing like the others. All the animals on the farm made fun of him and called him the Ugly Duckling.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/40/Le-vilain-petit-canard-10-525b9f59.jpg"
+ },
+ {
+ text: "The ugly duckling ran away and spent a cold, lonely winter hiding by himself. He felt sad because no one wanted to be his friend. He wished more than anything that he could belong somewhere.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/8/8f/Le-vilain-petit-canard-13-525bb09e.jpg"
+ },
+ {
+ text: "When spring came, the duckling looked into the water and saw his reflection. He was no longer gray and clumsy. He had grown into a beautiful white swan! The other swans welcomed him, and he was never lonely again.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/2/2a/Hoytema-Het-leelijke-jonge-eendje.jpg"
+ }
+ ]
+ },
+ {
+ grade: 3,
+ topic: "Classic Story",
+ title: "Hansel and Gretel",
+ author: "Brothers Grimm",
+ cover_url: "https://upload.wikimedia.org/wikipedia/commons/9/93/Hansel_and_Grethal-Rackham-008.jpg",
+ type: "illustrated",
+ pages: [
+ {
+ text: "Hansel and Gretel lived with their father and stepmother near a dark forest. One night, Hansel heard his stepmother say they had no food left. He crept outside and filled his pockets with white pebbles.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/9/93/Hansel_and_Grethal-Rackham-008.jpg"
+ },
+ {
+ text: "The next morning, their stepmother led them deep into the forest. Hansel secretly dropped white pebbles along the path. When night fell, the moonlight made the pebbles shine, and the children followed them safely home.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/b/b1/Hansel_and_Grethal-Rackham-022.jpg"
+ },
+ {
+ text: "A few days later, they were led into the forest again. This time Hansel dropped breadcrumbs, but birds ate them all up. The children were lost. Deep in the woods they found a house made of gingerbread and candy.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/3/33/Hansel_and_Grethal-Rackham-023.jpg"
+ },
+ {
+ text: "Inside the house lived a wicked witch. Gretel was brave and pushed the witch into the oven. The children found chests of jewels in the house and carried them home to their father. They all lived happily ever after.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/3/30/Hansel_and_Grethal-Rackham-030.jpg"
+ }
+ ]
+ },
+ {
+ grade: 4,
+ topic: "Classic Story",
+ title: "Aladdin and the Magic Lamp",
+ author: "Traditional",
+ cover_url: "https://upload.wikimedia.org/wikipedia/commons/4/4a/FOCDarley-Aladdin-and-the-genie.JPG",
+ type: "illustrated",
+ pages: [
+ {
+ text: "Long ago in a faraway land, there lived a young boy named Aladdin. He was poor but kind-hearted. One day, a mysterious stranger appeared and promised Aladdin great riches if he would enter a magic cave.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/4a/FOCDarley-Aladdin-and-the-genie.JPG"
+ },
+ {
+ text: "Inside the cave, Aladdin found a dusty old oil lamp. When he rubbed it, a huge genie appeared in a cloud of smoke. I am the Genie of the Lamp, the giant figure boomed. Your wish is my command!",
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/4a/FOCDarley-Aladdin-and-the-genie.JPG"
+ },
+ {
+ text: "Aladdin used the genie's power wisely, helping his poor mother and winning the heart of a kind princess. But the mysterious stranger tricked the princess into giving away the lamp.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/4a/FOCDarley-Aladdin-and-the-genie.JPG"
+ },
+ {
+ text: "Aladdin was brave and clever. He outsmarted the stranger and got the lamp back. He used his final wish to set the genie free. From that day on, Aladdin ruled with kindness and lived a long, happy life.",
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/4a/FOCDarley-Aladdin-and-the-genie.JPG"
+ }
+ ]
}
];
diff --git a/readingai/frontend/js/teacher-analytics.js b/readingai/frontend/js/teacher-analytics.js
index 672a78c..5d8801e 100644
--- a/readingai/frontend/js/teacher-analytics.js
+++ b/readingai/frontend/js/teacher-analytics.js
@@ -205,7 +205,7 @@ function renderStudentOverviewCards(profiles, byStudent, confidenceByStudent = {