nehal2006's picture
Update child level calculation and prepare for deployment
c836d54
// Global State
let token = localStorage.getItem('token');
let userId = localStorage.getItem('userId');
let selectedChildId = localStorage.getItem('selectedChildId');
let currentEmotionLogId = null;
// --- Data Templates ---
const animalTemplates = {
'Dog': `
<circle cx="100" cy="80" r="45" data-ref-fill="#964B00" stroke="#000" stroke-width="2"/>
<circle cx="65" cy="50" r="18" data-ref-fill="#964B00" stroke="#000" stroke-width="2"/>
<circle cx="135" cy="50" r="18" data-ref-fill="#964B00" stroke="#000" stroke-width="2"/>
<circle cx="85" cy="75" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<circle cx="115" cy="75" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<ellipse cx="100" cy="95" rx="12" ry="7" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
`,
'Cat': `
<circle cx="100" cy="85" r="45" data-ref-fill="#FFA500" stroke="#000" stroke-width="2"/>
<polygon points="60,60 85,55 70,30" data-ref-fill="#FFA500" stroke="#000" stroke-width="2"/>
<polygon points="115,55 140,60 130,30" data-ref-fill="#FFA500" stroke="#000" stroke-width="2"/>
<circle cx="85" cy="80" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<circle cx="115" cy="80" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<polygon points="100,95 95,90 105,90" data-ref-fill="#FFC0CB" stroke="#000" stroke-width="2"/>
`,
'Lion': `
<circle cx="100" cy="90" r="60" data-ref-fill="#FFD700" stroke="#000" stroke-width="2"/>
<circle cx="100" cy="90" r="40" data-ref-fill="#FFA500" stroke="#000" stroke-width="2"/>
<circle cx="85" cy="85" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<circle cx="115" cy="85" r="5" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<ellipse cx="100" cy="105" rx="10" ry="6" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
`,
'Bunny': `
<circle cx="100" cy="100" r="35" data-ref-fill="#bdc3c7" stroke="#000" stroke-width="2"/>
<ellipse cx="85" cy="50" rx="10" ry="30" data-ref-fill="#bdc3c7" stroke="#000" stroke-width="2"/>
<ellipse cx="115" cy="50" rx="10" ry="30" data-ref-fill="#bdc3c7" stroke="#000" stroke-width="2"/>
<circle cx="88" cy="95" r="4" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<circle cx="112" cy="95" r="4" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<circle cx="100" cy="110" r="5" data-ref-fill="#ff9ff3" stroke="#000" stroke-width="2"/>
`,
'Fish': `
<ellipse cx="100" cy="100" rx="50" ry="30" data-ref-fill="#ff9f43" stroke="#000" stroke-width="2"/>
<polygon points="150,100 175,80 175,120" data-ref-fill="#ff9f43" stroke="#000" stroke-width="2"/>
<circle cx="75" cy="95" r="6" data-ref-fill="#fff" stroke="#000" stroke-width="2"/>
<circle cx="75" cy="95" r="3" data-ref-fill="#000" stroke="#000" stroke-width="2"/>
<path d="M 110 90 Q 130 100 110 110" data-ref-fill="none" stroke="#000" stroke-width="2" fill="none"/>
`
};
const coloringTemplates = {
'House': `
<circle cx="160" cy="40" r="20" data-ref-fill="#f1c40f" stroke="#000" stroke-width="2"/>
<rect x="50" y="100" width="100" height="80" data-ref-fill="#f39c12" stroke="#000" stroke-width="2"/>
<polygon points="50,100 150,100 100,40" data-ref-fill="#e74c3c" stroke="#000" stroke-width="2"/>
<rect x="85" y="140" width="30" height="40" data-ref-fill="#34495e" stroke="#000" stroke-width="2"/>
<rect x="115" y="115" width="20" height="20" data-ref-fill="#95a5a6" stroke="#000" stroke-width="2"/>
`,
'Flower': `
<circle cx="100" cy="100" r="25" data-ref-fill="#f1c40f" stroke="#000" stroke-width="2"/>
<circle cx="100" cy="65" r="20" data-ref-fill="#e74c3c" stroke="#000" stroke-width="2"/>
<circle cx="100" cy="135" r="20" data-ref-fill="#e74c3c" stroke="#000" stroke-width="2"/>
<circle cx="65" cy="100" r="20" data-ref-fill="#e74c3c" stroke="#000" stroke-width="2"/>
<circle cx="135" cy="100" r="20" data-ref-fill="#e74c3c" stroke="#000" stroke-width="2"/>
<rect x="95" y="155" width="10" height="40" data-ref-fill="#2ecc71" stroke="#000" stroke-width="2"/>
`,
'Car': `
<rect x="40" y="100" width="120" height="40" data-ref-fill="#3498db" stroke="#000" stroke-width="2"/>
<rect x="60" y="70" width="80" height="30" data-ref-fill="#3498db" stroke="#000" stroke-width="2"/>
<circle cx="65" cy="140" r="15" data-ref-fill="#333" stroke="#000" stroke-width="2"/>
<circle cx="135" cy="140" r="15" data-ref-fill="#333" stroke="#000" stroke-width="2"/>
<rect x="100" y="75" width="30" height="20" data-ref-fill="#ecf0f1" stroke="#000" stroke-width="2"/>
`
};
const quizData = {
'Social Skills': [
{
question: "A friend is feeling sad and crying. What should you do?",
options: [
{ text: "Ask if they are okay 🫂", correct: true },
{ text: "Laugh at them 😆", correct: false },
{ text: "Run away 🏃", correct: false }
]
},
{
question: "You want to play with a toy someone else has. What do you say?",
options: [
{ text: "Can I have a turn please? 🧸", correct: true },
{ text: "Grab it quickly! 🖐️", correct: false },
{ text: "Yell at them 📢", correct: false }
]
},
{
question: "Someone says 'Hello' to you. What is a nice way to respond?",
options: [
{ text: "Smile and say 'Hi'! 👋", correct: true },
{ text: "Look at the floor ⬇️", correct: false },
{ text: "Walk away 🚶", correct: false }
]
},
{
question: "You accidentally bumped into a friend. What do you say?",
options: [
{ text: "I'm sorry! 🙊", correct: true },
{ text: "It was your fault! 😠", correct: false },
{ text: "Say nothing 🤐", correct: false }
]
},
{
question: "A friend is talking, but you have something to say too. What should you do?",
options: [
{ text: "Wait for them to finish, then speak 🙊", correct: true },
{ text: "Interrupt them immediately 📢", correct: false },
{ text: "Start talking louder 🗣️", correct: false }
]
}
],
'Daily Routine': [
{
question: "You just finished eating dinner. What comes next?",
options: [
{ text: "Brush your teeth 🪥", correct: true },
{ text: "Go to school 🏫", correct: false },
{ text: "Eat breakfast 🥣", correct: false }
]
},
{
question: "What should you do right after you wake up in the morning?",
options: [
{ text: "Wash your face and get dressed ☀️", correct: true },
{ text: "Go to sleep 😴", correct: false },
{ text: "Eat dinner 🍽️", correct: false }
]
},
{
question: "What is the first thing you do before eating your lunch?",
options: [
{ text: "Wash your hands 🧼", correct: true },
{ text: "Run outside 🌳", correct: false },
{ text: "Start singing 🎤", correct: false }
]
},
{
question: "Where do your toys go when you are finished playing?",
options: [
{ text: "In the toy box 📦", correct: true },
{ text: "On the floor 🧹", correct: false },
{ text: "In the fridge 🧊", correct: false }
]
},
{
question: "What do we do before going to bed at night?",
options: [
{ text: "Put on pajamas and read a story 📖", correct: true },
{ text: "Go for a swim 🏊", correct: false },
{ text: "Eat a big meal 🍕", correct: false }
]
}
],
'Safety & Help': [
{
question: "If you feel lost in a big store, who should you look for?",
options: [
{ text: "A store worker in a uniform 👮", correct: true },
{ text: "A stranger 👤", correct: false },
{ text: "Run outside 🏃", correct: false }
]
},
{
question: "What should you do if you see something hot on the stove?",
options: [
{ text: "Stay away and tell a grown-up 🚫", correct: true },
{ text: "Touch it 🖐️", correct: false },
{ text: "Blow on it 🌬️", correct: false }
]
},
{
question: "If you get a small scrape on your knee, what should you do?",
options: [
{ text: "Tell a teacher or parent 🩹", correct: true },
{ text: "Keep running 🏃", correct: false },
{ text: "Cry all day 😭", correct: false }
]
},
{
question: "A stranger asks you to go with them. What do you do?",
options: [
{ text: "Say 'NO' and run to a safe adult 🛑", correct: true },
{ text: "Go with them 🚶", correct: false },
{ text: "Take the candy they offer 🍬", correct: false }
]
},
{
question: "What number should you know for emergencies?",
options: [
{ text: "911 (or your local emergency number) ☎️", correct: true },
{ text: "123 🔢", correct: false },
{ text: "555 📞", correct: false }
]
}
],
'Advanced Social Skills': [
{
question: "Your friend is building a tower and it keeps falling. How might they feel?",
options: [
{ text: "Frustrated or annoyed 😠", correct: true },
{ text: "Excited 🤩", correct: false },
{ text: "Sleepy 😴", correct: false }
]
},
{
question: "A friend is talking about their favorite cat. What is a good thing to do?",
options: [
{ text: "Listen and ask 'What is your cat's name?' 🐱", correct: true },
{ text: "Start talking about your dog 🐶", correct: false },
{ text: "Walk away 🚶", correct: false }
]
},
{
question: "You want to play a game with a group. How can you join in?",
options: [
{ text: "Wait for a break and ask 'Can I play too?' 🤝", correct: true },
{ text: "Jump into the middle of the game 🏃", correct: false },
{ text: "Take the ball away ⚽", correct: false }
]
},
{
question: "You see someone sitting alone at recess. What could you do?",
options: [
{ text: "Ask if they want to play with you 🤝", correct: true },
{ text: "Ignore them 🙈", correct: false },
{ text: "Point and laugh ☝️", correct: false }
]
},
{
question: "If your friend wins a game and you lose, what should you say?",
options: [
{ text: "Good game! You did well! 👏", correct: true },
{ text: "I hate this game! 😠", correct: false },
{ text: "You cheated! 😤", correct: false }
]
}
]
};
// UI Initial Checks
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM Loaded. Path:", window.location.pathname);
updateNav();
applySensoryUI();
applyLevelUI();
if (window.location.pathname === '/login') {
if (token) {
const loginSection = document.getElementById('login-section');
const childSection = document.getElementById('child-section');
if (loginSection) loginSection.classList.add('hidden');
if (childSection) {
childSection.classList.remove('hidden');
loadChildren();
} else {
window.location.href = '/children';
}
}
}
if (window.location.pathname === '/dashboard' || window.location.pathname === '/') {
loadChildrenForDashboard();
}
if (window.location.pathname === '/diary') {
loadDiary();
}
if (window.location.pathname === '/emotion-learning') {
loadCustomEmotions();
}
});
function updateNav() {
if (token) {
const loginNav = document.getElementById('nav-login');
const logoutNav = document.getElementById('nav-logout');
if (loginNav) loginNav.classList.add('hidden');
if (logoutNav) logoutNav.classList.remove('hidden');
}
}
function logout() {
localStorage.clear();
window.location.href = '/login';
}
function toggleAuth(showRegister) {
const loginSection = document.getElementById('login-section');
const regSection = document.getElementById('register-section');
if (showRegister) {
if (loginSection) loginSection.classList.add('hidden');
if (regSection) regSection.classList.remove('hidden');
} else {
if (loginSection) loginSection.classList.remove('hidden');
if (regSection) regSection.classList.add('hidden');
}
}
// Auth API Calls
async function register() {
const username = document.getElementById('reg-username').value;
const password = document.getElementById('reg-password').value;
const res = await fetch('/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (res.ok) {
alert("Registered! Please login.");
toggleAuth(false);
} else alert(data.detail);
}
async function login() {
const usernameInput = document.getElementById('login-username');
const passwordInput = document.getElementById('login-password');
if (!usernameInput || !passwordInput) return;
const username = usernameInput.value;
const password = passwordInput.value;
const res = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (res.ok) {
localStorage.setItem('token', data.access_token);
localStorage.setItem('userId', data.user_id);
token = data.access_token;
userId = data.user_id;
window.location.href = '/children';
} else alert(data.detail);
}
async function addChild() {
const nameInput = document.getElementById('child-name');
const ageInput = document.getElementById('child-age');
const inheritanceInput = document.getElementById('child-autism-inheritance');
const sensoryInput = document.getElementById('child-sensory-level');
if (!nameInput || !ageInput) return;
const name = nameInput.value;
const age = parseInt(ageInput.value);
const parent_id = parseInt(userId || localStorage.getItem('userId'));
if (!parent_id) {
alert("Please log in again.");
window.location.href = '/login';
return;
}
const autism_inheritance = inheritanceInput ? inheritanceInput.value : "";
const sensory_level = sensoryInput ? sensoryInput.value : "standard";
console.log("Adding child with data:", { name, age, parent_id, autism_inheritance, sensory_level });
try {
const res = await fetch('/auth/add-child', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
age,
parent_id,
autism_inheritance,
sensory_level
})
});
const data = await res.json();
if (res.ok) {
loadChildren();
alert("Child added!");
nameInput.value = '';
ageInput.value = '';
} else {
console.error("Server error:", data);
alert(data.detail || "Error adding child profile.");
}
} catch (err) {
console.error("Network error:", err);
alert("Could not connect to server.");
}
}
async function loadChildren() {
const res = await fetch(`/auth/children/${userId}`);
const children = await res.json();
const list = document.getElementById('child-list');
if (!list) return;
list.innerHTML = '';
if (children.length === 0) {
list.innerHTML = '<p>No child profiles found. Add one to get started!</p>';
return;
}
children.forEach(c => {
const div = document.createElement('div');
div.className = 'card child-card';
div.innerHTML = `
<div class="child-info">
<h3>${c.name}</h3>
<p>Age: ${c.age} | Level: ${c.level || 1} | Sensory: ${c.sensory_level}</p>
</div>
<div class="child-actions">
<button class="btn-select" onclick="window.selectChild(${c.id}, '${c.name}', ${c.age}, '${c.sensory_level}', ${c.level || 1})">Select</button>
<button class="btn-delete" onclick="window.deleteChild(${c.id})">Delete</button>
</div>
`;
list.appendChild(div);
});
}
async function deleteChild(id) {
if (!confirm("Are you sure you want to delete this child profile? All progress will be lost.")) return;
const res = await fetch(`/auth/delete-child/${id}`, { method: 'DELETE' });
if (res.ok) {
alert("Child profile deleted.");
if (selectedChildId == id) {
localStorage.removeItem('selectedChildId');
localStorage.removeItem('selectedChildAge');
localStorage.removeItem('selectedChildSensory');
localStorage.removeItem('selectedChildLevel');
selectedChildId = null;
}
loadChildren();
} else {
const data = await res.json();
alert(data.detail || "Error deleting child.");
}
}
function selectChild(id, name, age, sensory, level) {
localStorage.setItem('selectedChildId', id);
localStorage.setItem('selectedChildAge', age);
localStorage.setItem('selectedChildSensory', sensory);
localStorage.setItem('selectedChildLevel', level || 1);
selectedChildId = id;
alert(`Child profile selected: ${name} (Age: ${age}, Level: ${level || 1})`);
applySensoryUI(sensory);
applyLevelUI(age); // Note: age still used for some legacy UI logic if any
window.location.href = '/dashboard';
}
function applySensoryUI(level) {
if (!level) level = localStorage.getItem('selectedChildSensory') || 'standard';
console.log("Applying UI for sensory level:", level);
if (level === 'high') {
document.body.classList.add('calm-theme');
} else {
document.body.classList.remove('calm-theme');
}
}
function applyLevelUI(age) {
// Reverted to normal UI for all levels as requested.
// No specific theme classes added here.
document.body.classList.remove('level-2-theme', 'level-3-theme');
}
// Real-time Emotion Tracker Logic
let videoStream = null;
let captureInterval = null;
let emotionHistory = []; // Buffer for smoothing results
const SMOOTHING_WINDOW = 5; // Average over last 5 frames
async function startCamera() {
const display = document.getElementById('live-result');
const video = document.getElementById('webcam');
console.log("Attempting to start camera...");
console.log("Hostname:", window.location.hostname);
console.log("Secure Context:", window.isSecureContext);
if (!selectedChildId) {
alert("Please select a child profile first on the Child page!");
return;
}
// 1. Check for Secure Context (HTTPS or localhost)
if (!window.isSecureContext && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
const errorMsg = `❌ Camera BLOCKED. Your browser requires HTTPS for camera access unless you are using 'localhost'. (Current: ${window.location.hostname})`;
console.error(errorMsg);
if (display) display.innerHTML = `<span style="color:red; font-size: 0.8rem;">${errorMsg}</span>`;
alert(errorMsg);
return;
}
// 2. Check for MediaDevices support
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
const errorMsg = "❌ Camera API not supported in this browser. Try Chrome or Edge.";
console.error(errorMsg);
if (display) display.innerHTML = `<span style="color:red;">${errorMsg}</span>`;
alert(errorMsg);
return;
}
// Stop any existing camera session
stopCamera();
emotionHistory = [];
if (!video) {
console.error("Video element 'webcam' not found in DOM.");
return;
}
if (display) display.innerText = "⌛ Requesting camera permission...";
try {
console.log("Calling getUserMedia...");
const constraints = {
video: {
facingMode: "user",
width: { ideal: 640 },
height: { ideal: 480 }
}
};
videoStream = await navigator.mediaDevices.getUserMedia(constraints);
console.log("Camera stream obtained.");
video.srcObject = videoStream;
// Ensure video plays
video.onloadedmetadata = () => {
console.log("Video metadata loaded, playing...");
video.play()
.then(() => {
console.log("Video playing successfully.");
if (display) display.innerHTML = '<span style="color:green;">✅ Camera Active!</span>';
})
.catch(e => {
console.error("Error playing video:", e);
if (display) display.innerHTML = `<span style="color:red;">Error playing video: ${e.message}</span>`;
});
};
// Start capturing frames every 1.5 seconds
captureInterval = setInterval(captureFrame, 1500);
} catch (err) {
console.error("Camera start error:", err);
let msg = "❌ Camera Error.";
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError')
msg = "❌ Permission Denied. Please allow camera access in your browser settings.";
else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError')
msg = "❌ No camera found on this device.";
else if (err.name === 'NotReadableError' || err.name === 'TrackStartError')
msg = "❌ Camera is already in use by another app.";
else
msg = `❌ Error: ${err.name} - ${err.message}`;
if (display) display.innerHTML = `<span style="color:red; font-size: 0.9rem;">${msg}</span>`;
alert(msg);
}
}
function stopCamera() {
console.log("Stopping camera...");
if (captureInterval) {
clearInterval(captureInterval);
captureInterval = null;
}
if (videoStream) {
videoStream.getTracks().forEach(track => {
track.stop();
});
videoStream = null;
}
const video = document.getElementById('webcam');
if (video) {
video.srcObject = null;
}
const display = document.getElementById('live-result');
if (display) display.innerText = "Camera Stopped";
const overlay = document.getElementById('live-overlay');
if (overlay) overlay.innerText = "Ready...";
emotionHistory = [];
}
// Automatically stop camera if user leaves the page
window.addEventListener('beforeunload', stopCamera);
window.addEventListener('popstate', stopCamera);
async function captureFrame() {
const video = document.getElementById('webcam');
if (!video || !video.srcObject || !videoStream) {
return;
}
if (video.paused || video.ended) return;
console.log("Capturing frame for analysis...");
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
if (canvas.width === 0 || canvas.height === 0) return;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const frameData = canvas.toDataURL('image/jpeg', 0.6); // Lower quality for speed
const formData = new FormData();
formData.append('child_id', selectedChildId);
formData.append('frame_data', frameData);
try {
const res = await fetch('/emotion/process-frame', {
method: 'POST',
body: formData
});
if (!res.ok) {
console.error("Frame processing failed on server:", res.status);
return;
}
const data = await res.json();
if (data.emotion) {
console.log("Detected emotion:", data.emotion);
// Add to history for smoothing
emotionHistory.push(data.emotion);
if (emotionHistory.length > SMOOTHING_WINDOW) {
emotionHistory.shift();
}
// Get the most frequent emotion in the window
const counts = {};
emotionHistory.forEach(e => counts[e] = (counts[e] || 0) + 1);
const stableEmotion = Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b);
const overlay = document.getElementById('live-overlay');
const display = document.getElementById('live-result');
const emotion = stableEmotion.toUpperCase();
if (overlay) {
overlay.innerText = emotion;
const colors = {
'HAPPY': '#4CAF50', 'SAD': '#2196F3', 'ANGRY': '#F44336',
'SURPRISE': '#FFEB3B', 'NEUTRAL': '#9E9E9E', 'FEAR': '#9C27B0', 'DISGUST': '#795548'
};
overlay.style.borderColor = colors[emotion] || '#4a90e2';
overlay.style.background = (emotion === 'SURPRISE') ? 'rgba(255,235,59,0.9)' : 'rgba(0,0,0,0.7)';
overlay.style.color = (emotion === 'SURPRISE') ? '#000' : '#fff';
}
if (display) {
display.innerHTML = `<span style="color:green; font-weight:900;">LIVE: ${emotion}</span>`;
}
}
} catch (err) {
console.error("Frame processing error:", err);
}
}
async function capturePhoto() {
console.log("Capturing photo...");
if (!selectedChildId) return alert("Select child first!");
const video = document.getElementById('webcam');
if (!video || !video.srcObject) return alert("Start camera first!");
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg'));
const formData = new FormData();
formData.append('file', blob, 'capture.jpg');
formData.append('child_id', selectedChildId);
try {
const res = await fetch('/emotion/upload', {
method: 'POST',
body: formData
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.detail || "Upload failed");
}
const data = await res.json();
currentEmotionLogId = data.id;
document.getElementById('predicted-emotion').innerText = data.predicted_emotion.toUpperCase();
document.getElementById('result-img').src = data.image_url;
document.getElementById('emotion-result').classList.remove('hidden');
} catch (err) {
console.error("Capture upload error:", err);
alert("Upload error: " + err.message);
}
}
// Emotion Module (Legacy Upload)
async function loadCustomEmotions() {
try {
const res = await fetch('/emotion/unique-emotions');
if (!res.ok) return;
const emotions = await res.json();
const select = document.getElementById('corrected-emotion');
if (!select) return;
// Save current "other" option
const otherOption = select.querySelector('option[value="other"]');
select.innerHTML = '';
emotions.forEach(emo => {
const opt = document.createElement('option');
opt.value = emo;
opt.innerText = emo.charAt(0).toUpperCase() + emo.slice(1);
select.appendChild(opt);
});
if (otherOption) select.appendChild(otherOption);
} catch (err) {
console.error("Failed to load custom emotions:", err);
}
}
async function uploadEmotion() {
console.log("Uploading emotion image...");
if (!selectedChildId) return alert("Please select a child profile first!");
const fileInput = document.getElementById('emotion-upload');
if (!fileInput || fileInput.files.length === 0) return alert("Select an image first.");
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('child_id', selectedChildId);
try {
const res = await fetch('/emotion/upload', {
method: 'POST',
body: formData
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.detail || "Upload failed");
}
const data = await res.json();
currentEmotionLogId = data.id;
document.getElementById('predicted-emotion').innerText = data.predicted_emotion.toUpperCase();
document.getElementById('result-img').src = data.image_url;
document.getElementById('emotion-result').classList.remove('hidden');
} catch (err) {
console.error("Upload error:", err);
alert("Upload error: " + err.message);
}
}
async function confirmEmotion(confirmed) {
if (confirmed) {
await fetch('/emotion/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `log_id=${currentEmotionLogId}&confirmed=true`
});
alert("Confirmed! Thank you.");
const resDiv = document.getElementById('emotion-result');
if (resDiv) resDiv.classList.add('hidden');
} else {
const corrArea = document.getElementById('correction-area');
if (corrArea) corrArea.classList.remove('hidden');
}
}
function toggleCustomEmotion() {
const select = document.getElementById('corrected-emotion');
const customInput = document.getElementById('custom-emotion-name');
if (select.value === 'other') {
customInput.classList.remove('hidden');
} else {
customInput.classList.add('hidden');
}
}
async function saveCorrection() {
const select = document.getElementById('corrected-emotion');
const customInput = document.getElementById('custom-emotion-name');
let corrected = select.value;
if (corrected === 'other') {
corrected = customInput.value.toLowerCase().trim();
if (!corrected) return alert("Please type a new emotion name.");
}
await fetch('/emotion/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `log_id=${currentEmotionLogId}&confirmed=true&corrected_emotion=${corrected}`
});
alert(`Learned! This image is now marked as: ${corrected}`);
const resDiv = document.getElementById('emotion-result');
if (resDiv) resDiv.classList.add('hidden');
// Refresh the list to include the new emotion
loadCustomEmotions();
if (customInput) customInput.value = '';
if (select) select.value = 'happy';
toggleCustomEmotion();
}
async function trainAI() {
const status = document.getElementById('train-status');
if (status) status.innerText = "Training in progress... please wait.";
try {
const res = await fetch('/emotion/train', { method: 'POST' });
const data = await res.json();
if (res.ok) {
alert(data.message);
if (status) status.innerText = "Last training: " + data.message;
} else {
alert("Training failed: " + data.error);
if (status) status.innerText = "Training failed.";
}
} catch (err) {
console.error("Training error:", err);
alert("Error connecting to server.");
}
}
// Activities and Games Logic
let activeGame = "";
let gameScore = 0;
let startTime = 0;
function startGame(name) {
if (!selectedChildId) return alert("Select child first.");
name = (name || "").trim();
console.log("Starting game:", name);
activeGame = name;
gameScore = 0;
startTime = Date.now();
const gameArea = document.getElementById('game-area');
if (gameArea) gameArea.classList.remove('hidden');
const title = document.getElementById('current-game-title');
if (title) {
title.innerText = name;
title.style.display = 'block';
}
const controls = document.getElementById('game-controls');
if (controls) controls.classList.remove('hidden');
const scoreDisplay = document.getElementById('game-score');
if (scoreDisplay) scoreDisplay.innerText = gameScore;
const closeBtn = document.getElementById('close-game-btn');
if (closeBtn) closeBtn.classList.add('hidden');
const container = document.getElementById('game-container');
if (!container) return console.error("Game container not found!");
container.innerHTML = '';
const lowerName = name.toLowerCase();
if (lowerName === 'color match' || lowerName === 'learn colors' || lowerName === 'learn about colors') startColorMatch(container);
else if (lowerName === 'memory game') startMemoryGame(container);
else if (lowerName === 'coloring book' || lowerName === 'online coloring' || lowerName === 'online coloring game') startColoringBook(container);
else if (lowerName === 'animal coloring' || lowerName === 'animal coloring game') startAnimalColoring(container);
else if (lowerName === 'pattern match' || lowerName === 'patternmatch') startPatternMatch(container);
else if (lowerName === 'mood matcher' || lowerName === 'moodmatcher') startMoodMatch(container);
else if (lowerName === 'learn emotions' || lowerName === 'learn about emotions') startLearnEmotions(container);
else if (lowerName === 'shape match' || lowerName === 'learn shapes' || lowerName === 'learn about shapes') startShapeMatch(container);
else if (lowerName === 'alphabet trace') startAlphabetTrace(container);
else if (lowerName === 'number write') startNumberWrite(container);
else if (lowerName === 'alphabet memory') startAlphabetMemory(container);
else if (lowerName === 'word match') startWordMatch(container);
else if (lowerName === 'halves match') startHalvesMatch(container);
else if (lowerName === 'alphabet sort') startAbcSort(container);
else if (lowerName === 'alphabet order') startAlphabetOrder(container);
else if (lowerName === 'missing letter') startMissingLetter(container);
else if (lowerName === 'word picture match') startWordPictureMatch(container);
else if (lowerName === 'object search') startObjectSearch(container);
else if (lowerName === 'spatial puzzle') startSpatialPuzzle(container);
else if (lowerName === 'jigsaw puzzle') startJigsawPuzzle(container);
else if (lowerName === 'advanced patterns') startAdvancedPatterns(container);
else {
console.error("Unknown game:", name);
container.innerHTML = `<h3>Oops! Game "${name}" not found.</h3><br><button onclick="location.reload()" class="btn-blue">Reload Page</button>`;
}
}
window.startGame = startGame;
// --- New Level 2 Game Functions ---
function startAlphabetTrace(container) {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
let currentIdx = 0;
window.renderTraceLetter = (idx) => {
const char = letters[idx];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Trace the Letter: <span style="font-size: 40px; color: var(--primary);">${char}</span></h3>
<div style="text-align:center; background: white; padding: 20px; border-radius: 30px; border: 4px dashed var(--secondary); max-width: 400px; margin: 0 auto;">
<canvas id="trace-canvas" width="300" height="300" style="cursor: crosshair; touch-action: none;"></canvas>
</div>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="renderTraceLetter(${currentIdx})"><span>🔄</span> Reset</button>
<button class="cute-btn btn-green" onclick="nextTraceLetter()"><span>➡️</span> Next Letter</button>
<button class="cute-btn btn-orange" onclick="finishGame()"><span>✅</span> Done</button>
</div>
`;
initTracing();
};
window.nextTraceLetter = () => {
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
currentIdx = (currentIdx + 1) % letters.length;
renderTraceLetter(currentIdx);
};
function initTracing() {
const canvas = document.getElementById('trace-canvas');
const ctx = canvas.getContext('2d');
let drawing = false;
ctx.strokeStyle = '#8d6e63';
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.font = '250px Nunito';
ctx.fillStyle = '#f0f0f0';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(letters[currentIdx], 150, 160);
const startDraw = (e) => { drawing = true; draw(e); };
const endDraw = () => { drawing = false; ctx.beginPath(); };
const draw = (e) => {
if (!drawing) return;
const rect = canvas.getBoundingClientRect();
const x = (e.clientX || e.touches[0].clientX) - rect.left;
const y = (e.clientY || e.touches[0].clientY) - rect.top;
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
};
canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDraw);
canvas.addEventListener('touchstart', startDraw);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', endDraw);
}
renderTraceLetter(currentIdx);
}
function startNumberWrite(container) {
const numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
let currentIdx = 0;
window.renderTraceNumber = (idx) => {
const num = numbers[idx];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Write the Number: <span style="font-size: 40px; color: var(--primary);">${num}</span></h3>
<div style="text-align:center; background: white; padding: 20px; border-radius: 30px; border: 4px dashed var(--secondary); max-width: 400px; margin: 0 auto;">
<canvas id="trace-canvas" width="300" height="300" style="cursor: crosshair; touch-action: none;"></canvas>
</div>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="renderTraceNumber(${currentIdx})"><span>🔄</span> Reset</button>
<button class="cute-btn btn-green" onclick="nextTraceNumber()"><span>➡️</span> Next Number</button>
<button class="cute-btn btn-orange" onclick="finishGame()"><span>✅</span> Done</button>
</div>
`;
initTracing();
};
window.nextTraceNumber = () => {
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
currentIdx = (currentIdx + 1) % numbers.length;
renderTraceNumber(currentIdx);
};
function initTracing() {
const canvas = document.getElementById('trace-canvas');
const ctx = canvas.getContext('2d');
let drawing = false;
ctx.strokeStyle = '#8d6e63';
ctx.lineWidth = 15;
ctx.lineCap = 'round';
ctx.font = '250px Nunito';
ctx.fillStyle = '#f0f0f0';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(numbers[currentIdx], 150, 160);
const startDraw = (e) => { drawing = true; draw(e); };
const endDraw = () => { drawing = false; ctx.beginPath(); };
const draw = (e) => {
if (!drawing) return;
const rect = canvas.getBoundingClientRect();
const x = (e.clientX || e.touches[0].clientX) - rect.left;
const y = (e.clientY || e.touches[0].clientY) - rect.top;
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
};
canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDraw);
canvas.addEventListener('touchstart', startDraw);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', endDraw);
}
renderTraceNumber(currentIdx);
}
function startAlphabetMemory(container) {
const letters = [
{ u: 'A', l: 'a' }, { u: 'B', l: 'b' }, { u: 'C', l: 'c' }, { u: 'D', l: 'd' },
{ u: 'E', l: 'e' }, { u: 'F', l: 'f' }
];
let cards = [];
letters.forEach(pair => {
cards.push({ val: pair.u, match: pair.l, type: 'upper' });
cards.push({ val: pair.l, match: pair.u, type: 'lower' });
});
cards.sort(() => Math.random() - 0.5);
let firstCard = null;
let secondCard = null;
let lockBoard = false;
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 25px;">Match Upper & Lower Case!</h3>
<div id="memory-grid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; max-width: 500px; margin: 0 auto;">
${cards.map((card, i) => `
<div class="card alphabet-card" id="abc-card-${i}" onclick="window.flipAbcCard(${i})"
style="aspect-ratio: 1/1; display: flex; align-items: center; justify-content: center; font-size: 40px; cursor: pointer; background: var(--secondary); color: transparent; border-radius: 15px; border: 3px solid var(--primary);">
${card.val}
</div>
`).join('')}
</div>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="startAlphabetMemory(document.getElementById('game-container'))"><span>🔄</span> Reset</button>
<button class="cute-btn btn-orange" onclick="finishGame()"><span>✅</span> Done</button>
</div>
`;
window.flipAbcCard = (i) => {
if (lockBoard) return;
const cardEl = document.getElementById(`abc-card-${i}`);
if (cardEl.classList.contains('matched') || cardEl === firstCard) return;
cardEl.style.color = 'var(--text)';
cardEl.style.background = 'white';
if (!firstCard) {
firstCard = cardEl;
return;
}
secondCard = cardEl;
lockBoard = true;
const val1 = firstCard.innerText.trim();
const val2 = secondCard.innerText.trim();
const isMatch = (val1.toUpperCase() === val2.toUpperCase() && val1 !== val2);
if (isMatch) {
firstCard.classList.add('matched');
secondCard.classList.add('matched');
firstCard.style.background = '#d7ccc8';
secondCard.style.background = '#d7ccc8';
gameScore += 20;
document.getElementById('game-score').innerText = gameScore;
resetBoard();
} else {
setTimeout(() => {
firstCard.style.color = 'transparent';
firstCard.style.background = 'var(--secondary)';
secondCard.style.color = 'transparent';
secondCard.style.background = 'var(--secondary)';
resetBoard();
}, 1000);
}
};
function resetBoard() {
firstCard = null;
secondCard = null;
lockBoard = false;
}
}
function startWordMatch(container) {
const wordPairs = [
{ word: 'Apple', icon: '🍎' },
{ word: 'Dog', icon: '🐶' },
{ word: 'Sun', icon: '☀️' },
{ word: 'Book', icon: '📚' }
];
let currentPair = wordPairs[Math.floor(Math.random() * wordPairs.length)];
window.renderWordMatch = () => {
currentPair = wordPairs[Math.floor(Math.random() * wordPairs.length)];
const options = [...wordPairs].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Which picture matches the word?</h3>
<div style="text-align:center; margin-bottom: 30px;">
<button class="cute-btn btn-blue" onclick="speakWord('${currentPair.word}')" style="font-size: 30px; padding: 20px 40px;">
🔊 ${currentPair.word.toUpperCase()}
</button>
</div>
<div style="display: flex; justify-content: center; gap: 20px; flex-wrap: wrap;">
${options.map(opt => `
<div onclick="checkWordMatch('${opt.word}', '${currentPair.word}')" class="card" style="width: 120px; height: 120px; font-size: 60px; display: flex; align-items: center; justify-content: center; cursor: pointer;">
${opt.icon}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="renderWordMatch()"><span>🔄</span> Next</button>
<button class="cute-btn btn-orange" onclick="finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.speakWord = (word) => {
const msg = new SpeechSynthesisUtterance();
msg.text = word;
window.speechSynthesis.speak(msg);
};
window.checkWordMatch = (selected, target) => {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Correct! 🌟";
feedback.style.color = "var(--success)";
gameScore += 15;
document.getElementById('game-score').innerText = gameScore;
setTimeout(renderWordMatch, 1500);
} else {
feedback.innerText = "Try again! 😊";
feedback.style.color = "#e74c3c";
}
};
renderWordMatch();
}
function startHalvesMatch(container) {
const objects = [
{ icon: '🍎', left: '🍎', right: '🍎' }, // In real app, these would be split images
{ icon: '🐶', left: '🐶', right: '🐶' },
{ icon: '🚗', left: '🚗', right: '🚗' },
{ icon: '🏠', left: '🏠', right: '🏠' }
];
window.renderHalves = () => {
const target = objects[Math.floor(Math.random() * objects.length)];
const options = [...objects].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Find the matching half!</h3>
<div style="display: flex; justify-content: center; align-items: center; gap: 0; margin-bottom: 40px;">
<div style="width: 100px; height: 200px; background: white; border: 4px solid var(--primary); border-right: none; border-radius: 20px 0 0 20px; display: flex; align-items: center; justify-content: center; font-size: 100px; overflow: hidden;">
<span style="margin-right: -100px;">${target.icon}</span>
</div>
<div style="width: 100px; height: 200px; background: #fafafa; border: 4px dashed var(--secondary); border-left: none; border-radius: 0 20px 20px 0; display: flex; align-items: center; justify-content: center; font-size: 100px; color: #eee;">
?
</div>
</div>
<div style="display: flex; justify-content: center; gap: 20px; flex-wrap: wrap;">
${options.map(opt => `
<div onclick="checkHalvesMatch('${opt.icon}', '${target.icon}')" class="card" style="width: 100px; height: 100px; font-size: 50px; display: flex; align-items: center; justify-content: center; cursor: pointer; overflow: hidden;">
<span style="margin-left: -50px;">${opt.icon}</span>
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="renderHalves()"><span>🔄</span> Next</button>
<button class="cute-btn btn-orange" onclick="finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.checkHalvesMatch = (selected, target) => {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "You completed it! 🌟";
feedback.style.color = "var(--success)";
gameScore += 15;
document.getElementById('game-score').innerText = gameScore;
setTimeout(renderHalves, 1500);
} else {
feedback.innerText = "Keep looking! 😊";
feedback.style.color = "#e74c3c";
}
};
renderHalves();
}
// --- Game Functions ---
function startAnimalColoring(container) {
console.log("Starting Animal Coloring selection...");
if (!animalTemplates || Object.keys(animalTemplates).length === 0) {
console.error("No animal templates found!");
container.innerHTML = "<h3>Sorry, no animals found! Please refresh the page.</h3>";
return;
}
let animalCards = Object.keys(animalTemplates).map(name => {
let icon = '🐾';
if (name === 'Dog') icon = '🐶';
else if (name === 'Cat') icon = '🐱';
else if (name === 'Lion') icon = '🦁';
else if (name === 'Bunny') icon = '🐰';
else if (name === 'Fish') icon = '🐠';
return `
<div onclick="window.selectAnimalTemplate('${name}')" class="card" style="cursor:pointer; width:130px; text-align:center; padding: 15px; border-radius: 20px; background: white; border: 3px solid #eee;">
<span style="font-size:50px; display: block; margin-bottom: 10px;">${icon}</span>
<p style="font-weight: bold; margin: 0;">${name}</p>
</div>`;
}).join('');
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px; color: var(--primary);">Pick an animal friend to color!</h3>
<div style="display:flex; justify-content:center; gap:20px; margin-top:20px; flex-wrap: wrap; padding: 20px;">
${animalCards}
</div>
<div style="text-align:center; margin-top: 30px;">
<button onclick="window.location.href='/activities'" class="btn-kids" style="background: #95a5a6;">Go Back</button>
</div>
`;
}
function selectAnimalTemplate(name) {
const container = document.getElementById('game-container');
const templateSvg = animalTemplates[name];
const referenceSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="$1"');
const drawingSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, (match, p1) => {
if (p1 === 'none') return 'fill="none"';
return 'fill="#fff" class="block-to-color" onclick="window.colorBlock(this)"';
});
const palette = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
'#FFFF00', '#FFD740', '#FFAB40', '#FF6E40',
'#8D6E63', '#9E9E9E', '#CFD8DC', '#000000', '#FFFFFF'
];
container.innerHTML = `
<div style="display: flex; gap: 30px; align-items: flex-start; justify-content: center; flex-wrap: wrap;">
<div style="text-align:center; border: 3px solid #eee; padding: 15px; border-radius: 20px; background: #fff; width: 150px;">
<p style="font-size:14px; font-weight: bold; margin:0 0 10px 0;">Look at this!</p>
<svg width="120" height="120" viewBox="0 0 200 200">${referenceSvg}</svg>
</div>
<div style="text-align:center; flex-grow: 1; min-width: 300px;">
<svg width="400" height="400" viewBox="0 0 200 200" style="background:#fff; border-radius:30px; border: 6px dashed #ddd; box-shadow: inset 0 0 15px rgba(0,0,0,0.05);">${drawingSvg}</svg>
</div>
</div>
<p style="text-align:center; margin-top: 25px; font-weight: bold; color: var(--text);">Step 1: Click a color ⬇️ | Step 2: Click the animal 🎨</p>
<div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(45px, 1fr)); gap:12px; margin-top:15px; background: #fff; padding: 20px; border-radius: 30px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); max-width: 600px; margin-left: auto; margin-right: auto;">
${palette.map(c => `
<div class="color-swatch"
style="background-color:${c}; width: 45px; height: 45px; border-radius: 50%; cursor: pointer; border: 4px solid white; box-shadow: 0 4px 6px rgba(0,0,0,0.1); transition: transform 0.2s;"
onclick="window.selectPaletteColor('${c}', this)">
</div>
`).join('')}
</div>
<div class="control-box" style="margin-top: 30px;">
<button class="cute-btn btn-pink" onclick="window.startAnimalColoring(document.getElementById('game-container'))"><span>🔙</span> Different Animal</button>
<button class="cute-btn btn-blue" onclick="window.selectAnimalTemplate('${name}')"><span>🔄</span> Start Over</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> I'm Finished!</button>
</div>
`;
const firstSwatch = document.querySelector('.color-swatch');
if (firstSwatch) window.selectPaletteColor(palette[0], firstSwatch);
}
function startLearnEmotions(container) {
const basicEmotions = [
{ name: 'Happy', emoji: '😊', color: '#f1c40f' },
{ name: 'Sad', emoji: '😢', color: '#3498db' },
{ name: 'Angry', emoji: '😠', color: '#e74c3c' },
{ name: 'Silly', emoji: '😜', color: '#9b59b6' }
];
const target = basicEmotions[Math.floor(Math.random() * basicEmotions.length)];
const options = [...basicEmotions].sort(() => 0.5 - Math.random());
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px; color: var(--primary); font-size: 24px;">Can you find the <span style="color:${target.color}">${target.name.toUpperCase()}</span> face?</h3>
<div style="text-align:center; margin-bottom: 20px; font-size: 80px;">
${target.emoji}
</div>
<div style="display:flex; justify-content:center; gap:20px; flex-wrap:wrap; margin-top:20px;">
${options.map(m => `
<div onclick="window.checkBasicEmotion('${m.name}', '${target.name}')"
class="card"
style="width:140px; padding: 20px; cursor:pointer; border-radius: 20px; text-align:center; background: #fff; border: 3px solid #eee;">
<h4 style="margin:0; font-size: 24px;">${m.name}</h4>
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startLearnEmotions(document.getElementById('game-container'))"><span>🔄</span> Next</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> I'm Done!</button>
</div>
`;
}
window.checkBasicEmotion = function(selected, target) {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Yay! You got it! 🌟";
feedback.style.color = "#2ecc71";
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
setTimeout(() => startLearnEmotions(document.getElementById('game-container')), 1500);
} else {
feedback.innerText = "Not quite, try again! 😊";
feedback.style.color = "#e74c3c";
}
};
function startShapeMatch(container) {
const shapes = [
{ name: 'Circle', icon: '🔴' },
{ name: 'Square', icon: '🟧' },
{ name: 'Triangle', icon: '🔺' },
{ name: 'Star', icon: '⭐' },
{ name: 'Heart', icon: '❤️' },
{ name: 'Moon', icon: '🌙' }
];
const target = shapes[Math.floor(Math.random() * shapes.length)];
const options = [...shapes].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 30px;">Can you find the <span style="color:var(--primary); font-weight:bold; font-size:28px;">${target.name.toUpperCase()}</span>?</h3>
<div style="display:flex; justify-content:center; gap:25px; flex-wrap:wrap; margin-top:20px;">
${options.map(s => `
<div onclick="window.checkShape('${s.name}', '${target.name}')"
class="game-shape">
${s.icon}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startShapeMatch(document.getElementById('game-container'))"><span>🔄</span> Shuffle</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> All Done!</button>
</div>
`;
}
window.checkShape = function(selected, target) {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Correct! You found the " + target + "! 🌟";
feedback.style.color = "#2ecc71";
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
setTimeout(() => startShapeMatch(document.getElementById('game-container')), 1200);
} else {
feedback.innerText = "Not that one. Try again! 🤔";
feedback.style.color = "#e74c3c";
}
};
function startPatternMatch(container) {
const emojiSets = [
['🍎', '🍌'], ['🐶', '🐱'], ['☀️', '🌙'], ['🚗', '🚲'], ['🎈', '🎁'], ['🍦', '🍰']
];
const set = emojiSets[Math.floor(Math.random() * emojiSets.length)];
const pattern = [set[0], set[1], set[0], set[1]];
const target = set[0];
const options = [...set].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 30px; color: var(--primary); font-size: 24px;">What comes next in the pattern?</h3>
<div style="display:flex; justify-content:center; align-items:center; gap:15px; margin-bottom:40px; background: #f9f9f9; padding: 20px; border-radius: 20px; border: 2px dashed #ddd;">
<div style="font-size: 60px;">${pattern[0]}</div>
<div style="font-size: 40px; color: #ccc;">➡️</div>
<div style="font-size: 60px;">${pattern[1]}</div>
<div style="font-size: 40px; color: #ccc;">➡️</div>
<div style="font-size: 60px;">${pattern[2]}</div>
<div style="font-size: 40px; color: #ccc;">➡️</div>
<div style="font-size: 60px;">${pattern[3]}</div>
<div style="font-size: 40px; color: #ccc;">➡️</div>
<div style="font-size: 60px; width: 80px; height: 80px; border: 4px solid var(--primary); border-radius: 15px; display: flex; align-items: center; justify-content: center; background: #fff; color: #eee;">?</div>
</div>
<div style="display:flex; justify-content:center; gap:25px; flex-wrap:wrap; margin-top:20px;">
${options.map(emoji => `
<div onclick="window.checkPattern('${emoji}', '${target}')"
class="card"
style="width:110px; height:110px; display: flex; align-items: center; justify-content: center; font-size: 50px; cursor:pointer; border-radius: 24px; transition: transform 0.2s;">
${emoji}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startPatternMatch(document.getElementById('game-container'))"><span>🔄</span> New Pattern</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> I'm Done!</button>
</div>
`;
}
window.checkPattern = function(selected, target) {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Wow! You're so smart! 🌟";
feedback.style.color = "#2ecc71";
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
const placeholders = document.querySelectorAll('#game-container div');
placeholders.forEach(div => {
if (div.innerText === '?') {
div.innerText = target;
div.style.color = '#000';
div.style.borderColor = '#2ecc71';
}
});
setTimeout(() => startPatternMatch(document.getElementById('game-container')), 1500);
} else {
feedback.innerText = "Not quite, try looking at the sequence again! 😊";
feedback.style.color = "#e74c3c";
}
};
function startColorMatch(container) {
const colors = [
{ name: 'Red', color: '#e74c3c' }, { name: 'Blue', color: '#3498db' }, { name: 'Green', color: '#2ecc71' },
{ name: 'Yellow', color: '#f1c40f' }, { name: 'Purple', color: '#9b59b6' }, { name: 'Orange', color: '#e67e22' }
];
const target = colors[Math.floor(Math.random() * colors.length)];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 30px;">Pick the <span style="color:${target.color}; font-weight:bold; font-size:28px;">${target.name.toUpperCase()}</span> block!</h3>
<div style="display:flex; justify-content:center; gap:25px; flex-wrap:wrap; margin-top:20px;">
${colors.map(c => `
<div onclick="window.checkColor('${c.name}', '${target.name}')"
style="width:110px; height:110px; background-color:${c.color}; border-radius:24px; cursor:pointer; box-shadow: 0 8px 0 rgba(0,0,0,0.1);">
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startColorMatch(document.getElementById('game-container'))"><span>🔄</span> Shuffle</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> All Done!</button>
</div>
`;
}
window.checkColor = function(selected, target) {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Correct! You're a Color Master! 🎨";
feedback.style.color = "#2ecc71";
gameScore += 10;
document.getElementById('game-score').innerText = gameScore;
setTimeout(() => startColorMatch(document.getElementById('game-container')), 1200);
} else {
feedback.innerText = "Oops! Not that one. Try again!";
feedback.style.color = "#e74c3c";
}
};
let flippedCards = [];
function startMemoryGame(container) {
const items = ['😊', '😢', '🐶', '🐱'];
const bonus = '⭐';
let deck = [...items, ...items, bonus];
deck.sort(() => Math.random() - 0.5);
const cardGradients = [
'linear-gradient(135deg, #ff7eb3, #ff758c)', 'linear-gradient(135deg, #4facfe, #00f2fe)',
'linear-gradient(135deg, #43e97b, #38f9d7)', 'linear-gradient(135deg, #fa709a, #fee140)',
'linear-gradient(135deg, #667eea, #764ba2)', 'linear-gradient(135deg, #f093fb, #f5576c)',
'linear-gradient(135deg, #5ee7df, #b490ca)', 'linear-gradient(135deg, #c3cfe2, #c3cfe2)',
'linear-gradient(135deg, #f6d365, #fda085)'
];
flippedCards = [];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 25px; color: var(--primary); font-size: 24px;">Find the 4 pairs + The Magic Star!</h3>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 420px; width: 100%; margin: 0 auto;">
${deck.map((item, index) => `
<div class="memory-card" id="card-${index}" onclick="window.flipCard(${index}, '${item}')"
style="aspect-ratio: 1/1; background: ${cardGradients[index % cardGradients.length]}; border-radius: 18px; display: flex; align-items: center; justify-content: center; font-size: 60px; cursor: pointer; transition: all 0.4s; box-shadow: 0 8px 15px rgba(0,0,0,0.15);">
<span class="content hidden">${item}</span>
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:22px; margin-top:25px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startMemoryGame(document.getElementById('game-container'))"><span>🔄</span> Shuffle</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> All Done!</button>
</div>
`;
}
window.flipCard = function(index, item) {
const card = document.getElementById(`card-${index}`);
const content = card.querySelector('.content');
const feedback = document.getElementById('feedback');
if (card.classList.contains('matched') || !content.classList.contains('hidden') || flippedCards.length >= 2) return;
if (item === '⭐') {
content.classList.remove('hidden');
card.style.background = 'linear-gradient(135deg, #fff95b, #ff930f)';
card.classList.add('matched');
feedback.innerText = "You found the Magic Star! +50 Points! 🌟";
feedback.style.color = "#f39c12";
gameScore += 50;
document.getElementById('game-score').innerText = gameScore;
return;
}
content.classList.remove('hidden');
card.style.background = '#ffffff';
flippedCards.push({ index, item });
if (flippedCards.length === 2) setTimeout(checkMatch, 800);
};
function checkMatch() {
const [c1, c2] = flippedCards;
const card1 = document.getElementById(`card-${c1.index}`);
const card2 = document.getElementById(`card-${c2.index}`);
const feedback = document.getElementById('feedback');
if (c1.item === c2.item) {
card1.classList.add('matched');
card2.classList.add('matched');
card1.style.background = 'linear-gradient(135deg, #43e97b, #38f9d7)';
card2.style.background = 'linear-gradient(135deg, #43e97b, #38f9d7)';
feedback.innerText = "Match found! 🎉";
feedback.style.color = "#2ecc71";
gameScore += 20;
} else {
card1.querySelector('.content').classList.add('hidden');
card2.querySelector('.content').classList.add('hidden');
card1.style.background = '';
card2.style.background = '';
feedback.innerText = "Not a match, try again!";
feedback.style.color = "#e74c3c";
}
document.getElementById('game-score').innerText = gameScore;
flippedCards = [];
}
let selectedBrushColor = '#f1c40f';
function startColoringBook(container) {
container.innerHTML = `
<h3 style="text-align:center;">Choose a picture to color!</h3>
<div style="display:flex; justify-content:center; gap:20px; margin-top:20px;">
${Object.keys(coloringTemplates).map(name => `
<div onclick="window.selectTemplate('${name}')" class="card" style="cursor:pointer; width:120px; text-align:center;">
<span style="font-size:40px;">${name === 'House' ? '🏠' : name === 'Flower' ? '🌸' : '🚗'}</span>
<p>${name}</p>
</div>
`).join('')}
</div>
`;
}
window.selectTemplate = function(name) {
const container = document.getElementById('game-container');
const templateSvg = coloringTemplates[name];
const referenceSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="$1"');
const drawingSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="#fff" class="block-to-color" onclick="window.colorBlock(this)"');
container.innerHTML = `
<div style="display: flex; gap: 30px; align-items: flex-start; justify-content: center;">
<div style="text-align:center; border: 2px solid #ddd; padding: 10px; border-radius: 15px; background: #fff; width: 120px;">
<p style="font-size:12px; margin:0 0 5px 0;">Reference</p>
<svg width="100" height="100" viewBox="0 0 200 200">${referenceSvg}</svg>
</div>
<div style="text-align:center; flex-grow: 1;">
<svg width="350" height="350" viewBox="0 0 200 200" style="background:#fff; border-radius:20px; border: 4px dashed #aaa;">${drawingSvg}</svg>
</div>
</div>
<div style="display:flex; justify-content:center; gap:10px; margin-top:20px; background: #fff; padding: 15px; border-radius: 50px; box-shadow: 0 5px 15px rgba(0,0,0,0.1);">
${['#f1c40f', '#f39c12', '#e74c3c', '#34495e', '#95a5a6', '#2ecc71', '#3498db', '#ecf0f1', '#333'].map(c => `
<div class="color-swatch" style="background-color:${c}; width: 45px; height: 45px;" onclick="window.selectPaletteColor('${c}', this)"></div>
`).join('')}
</div>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.startColoringBook(document.getElementById('game-container'))"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.selectTemplate('${name}')"><span>🔄</span> Reset</button>
<button class="cute-btn btn-green" onclick="window.nextImage('${name}')"><span>➡️</span> Next</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
const firstSwatch = document.querySelector('.color-swatch');
if (firstSwatch) window.selectPaletteColor('#f1c40f', firstSwatch);
};
window.selectPaletteColor = function(color, element) {
selectedBrushColor = color;
document.querySelectorAll('.color-swatch').forEach(s => s.style.border = 'none');
element.style.border = '3px solid #333';
};
window.colorBlock = function(element) {
element.setAttribute('fill', selectedBrushColor);
gameScore += 5;
document.getElementById('game-score').innerText = gameScore;
};
window.nextImage = function(currentName) {
const keys = Object.keys(coloringTemplates);
const currentIndex = keys.indexOf(currentName);
const nextIndex = (currentIndex + 1) % keys.length;
window.selectTemplate(keys[nextIndex]);
};
function closeGame() {
window.location.href = '/activities';
}
function restartGame() {
startGame(activeGame);
}
async function finishGame() {
const duration = Math.floor((Date.now() - startTime) / 1000);
const res = await fetch('/activities/log-activity', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
child_id: selectedChildId,
activity_name: activeGame,
score: gameScore,
duration_seconds: duration
})
});
if (res.ok) {
alert(`Great Job! You scored ${gameScore} points! 🎉`);
window.location.href = '/activities';
}
}
let currentQuizType = "";
let currentQuestionIndex = 0;
let quizScore = 0;
function startNewQuiz(type) {
if (!selectedChildId) return alert("Please select a child profile first.");
currentQuizType = type;
currentQuestionIndex = 0;
quizScore = 0;
document.getElementById('quiz-selection').classList.add('hidden');
document.getElementById('quiz-area').classList.remove('hidden');
document.getElementById('quiz-content').classList.remove('hidden');
document.getElementById('quiz-results').classList.add('hidden');
document.getElementById('current-quiz-name').innerText = type;
loadQuizQuestion();
}
function loadQuizQuestion() {
const questions = quizData[currentQuizType];
const q = questions[currentQuestionIndex];
document.getElementById('question-counter').innerText = `Question ${currentQuestionIndex + 1} of ${questions.length}`;
document.getElementById('quiz-question-text').innerText = q.question;
const progress = (currentQuestionIndex / questions.length) * 100;
document.getElementById('progress-fill').style.width = `${progress}%`;
// Reset feedback
const feedback = document.getElementById('quiz-feedback');
if (feedback) feedback.innerText = "";
const optionsGrid = document.getElementById('quiz-options-grid');
optionsGrid.innerHTML = '';
q.options.forEach(opt => {
const div = document.createElement('div');
div.className = 'card quiz-option';
div.innerHTML = `<span>${opt.text.split(' ').pop()}</span><p>${opt.text.split(' ').slice(0, -1).join(' ')}</p>`;
div.onclick = () => window.selectQuizOption(opt.correct, div);
optionsGrid.appendChild(div);
});
}
window.selectQuizOption = function(isCorrect, element) {
const feedback = document.getElementById('quiz-feedback');
if (isCorrect) {
quizScore++;
if (feedback) {
feedback.innerText = "Correct! Great thinking! 🌟";
feedback.style.color = "var(--success)";
}
element.style.borderColor = "var(--success)";
element.style.background = "#e8f5e9";
// Disable all options during the transition
document.querySelectorAll('.quiz-option').forEach(opt => opt.onclick = null);
setTimeout(() => {
currentQuestionIndex++;
if (currentQuestionIndex < quizData[currentQuizType].length) {
loadQuizQuestion();
} else {
finishQuiz();
}
}, 1500);
} else {
if (feedback) {
feedback.innerText = "Not quite. Let's try again! 👍";
feedback.style.color = "#e74c3c";
}
element.style.borderColor = "#e74c3c";
element.style.opacity = "0.7";
// Do NOT increment index, allowing retry
}
};
async function finishQuiz() {
const total = quizData[currentQuizType].length;
// Every question was eventually answered correctly
const finalScore = total;
await fetch('/activities/log-quiz', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
child_id: selectedChildId,
quiz_name: currentQuizType,
score: finalScore,
total_questions: total
})
});
document.getElementById('progress-fill').style.width = `100%`;
document.getElementById('quiz-content').classList.add('hidden');
document.getElementById('quiz-results').classList.remove('hidden');
document.getElementById('final-score-text').innerText = `Amazing! You completed all ${total} questions! 🎉`;
}
async function loadChildrenForDashboard() {
if (!userId) return;
const res = await fetch(`/auth/children/${userId}`);
const children = await res.json();
const select = document.getElementById('dashboard-child-select');
if (!select) return;
select.innerHTML = '<option value="">Select Child</option>';
children.forEach(c => {
const isSelected = selectedChildId == c.id ? 'selected' : '';
select.innerHTML += `<option value="${c.id}" ${isSelected}>${c.name}</option>`;
});
if (selectedChildId) loadProgress();
}
function getLevelInfo(child) {
console.log("getLevelInfo called with:", child);
let age = 0;
if (typeof child === 'number') {
age = child;
} else if (child && typeof child === 'object') {
age = parseInt(child.age) || 0;
} else {
// Try getting from localStorage as fallback
age = parseInt(localStorage.getItem('selectedChildAge')) || 0;
}
console.log("Determined age for level check:", age);
let level = 1;
if (age >= 10) level = 3;
else if (age >= 7) level = 2;
else level = 1;
console.log("Calculated level:", level);
if (level === 3) return { level: 3, name: "Level 3: Advanced (10+)", desc: "Complex emotions and mood analysis.", theme: "purple" };
if (level === 2) return { level: 2, name: "Level 2: Intermediate (7-9)", desc: "Patterns and alphabetical logic.", theme: "sand" };
return { level: 1, name: "Level 1: Basic (3-6)", desc: "Colors, shapes, and basic emotions.", theme: "orange" };
}
async function syncChildAge() {
const childId = localStorage.getItem('selectedChildId');
if (!childId || !userId) return;
try {
const res = await fetch(`/auth/children/${userId}`);
const children = await res.json();
const child = children.find(c => c.id == childId);
if (child) {
localStorage.setItem('selectedChildAge', child.age);
localStorage.setItem('selectedChildSensory', child.sensory_level);
applySensoryUI(child.sensory_level);
}
} catch (err) { console.error("Failed to sync child age:", err); }
}
async function loadProgress() {
const select = document.getElementById('dashboard-child-select');
if (!select) return;
const childId = select.value;
if (!childId) return;
localStorage.setItem('selectedChildId', childId);
const resChild = await fetch(`/auth/children/${userId}`);
const children = await resChild.json();
const child = children.find(c => c.id == childId);
if (child) {
localStorage.setItem('selectedChildAge', child.age);
localStorage.setItem('selectedChildLevel', child.level || 1);
const levelInfo = getLevelInfo(child);
const levelDisplay = document.getElementById('dashboard-level-display');
if (levelDisplay) {
levelDisplay.innerHTML = `
<div class="card theme-${levelInfo.theme}" style="margin-bottom: 30px;">
<h3>🌟 Current Level: ${levelInfo.name}</h3>
<p style="font-weight: 700; color: #4e342e;">${levelInfo.desc}</p>
</div>
`;
}
}
const resAct = await fetch(`/dashboard/progress/${childId}`);
const data = await resAct.json();
const ctxAct = document.getElementById('activitiesChart');
if (ctxAct) {
new Chart(ctxAct.getContext('2d'), {
type: 'bar',
data: {
labels: data.activities.map(a => a.name),
datasets: [{ label: 'Avg Score', data: data.activities.map(a => a.avg_score), backgroundColor: '#8d6e63' }]
}
});
}
const ctxEmo = document.getElementById('emotionChart');
if (ctxEmo) {
new Chart(ctxEmo.getContext('2d'), {
type: 'pie',
data: {
labels: data.emotions.map(e => e.emotion),
datasets: [{ data: data.emotions.map(e => e.count), backgroundColor: ['#8d6e63', '#d6c6a2', '#a1887f', '#d7ccc8', '#bcaaa4', '#efebe9'] }]
}
});
}
}
async function generateReport() {
const select = document.getElementById('dashboard-child-select');
if (!select) return;
const childId = select.value;
if (!childId) return alert("Select child.");
const res = await fetch(`/dashboard/generate-report/${childId}`);
const data = await res.json();
const reportLink = document.getElementById('report-link');
if (reportLink) reportLink.innerHTML = `<a href="${data.report_url}" target="_blank">View Report PDF</a>`;
}
async function saveDiary() {
if (!userId) return alert("Login first.");
const titleVal = document.getElementById('diary-title');
const msgVal = document.getElementById('diary-message');
if (!titleVal || !msgVal) return;
const title = titleVal.value;
const message = msgVal.value;
const fileInput = document.getElementById('diary-upload');
const file = fileInput ? fileInput.files[0] : null;
const formData = new FormData();
formData.append('parent_id', userId);
formData.append('child_name', "Child");
formData.append('title', title);
formData.append('message', message);
if (file) formData.append('file', file);
const res = await fetch('/diary/add', { method: 'POST', body: formData });
if (res.ok) {
alert("Memory Saved!");
loadDiary();
titleVal.value = '';
msgVal.value = '';
}
}
async function loadDiary() {
if (!userId) return;
const res = await fetch(`/diary/${userId}`);
const diary = await res.json();
const list = document.getElementById('diary-timeline');
if (!list) return;
list.innerHTML = '';
diary.forEach(e => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `<h3>${e.title}</h3><p>${e.message}</p><em>${e.timestamp}</em>`;
if (e.image_path) div.innerHTML += `<br><img src="/${e.image_path}" style="max-width:200px; border-radius:10px;">`;
list.appendChild(div);
});
}
function startMoodMatch(container) {
const moods = [
{ name: 'Happy', emoji: '😊', color: '#f1c40f' }, { name: 'Sad', emoji: '😢', color: '#3498db' },
{ name: 'Angry', emoji: '😠', color: '#e74c3c' }, { name: 'Surprised', emoji: '😲', color: '#9b59b6' },
{ name: 'Scared', emoji: '😨', color: '#2ecc71' }, { name: 'Sleepy', emoji: '😴', color: '#95a5a6' }
];
const shuffled = [...moods].sort(() => 0.5 - Math.random());
const options = shuffled.slice(0, 3);
const target = options[Math.floor(Math.random() * options.length)];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px; color: var(--primary); font-size: 24px;">How is the friend feeling?</h3>
<div style="text-align:center; margin-bottom: 40px;"><div style="font-size: 120px; filter: drop-shadow(0 10px 15px rgba(0,0,0,0.1));">${target.emoji}</div></div>
<div style="display:flex; justify-content:center; gap:20px; flex-wrap:wrap; margin-top:20px;">
${options.map(m => `<div onclick="window.checkMood('${m.name}', '${target.name}')" class="card" style="width:140px; padding: 20px; cursor:pointer; border-radius: 20px; text-align:center; background: #fff; border: 3px solid #eee; transition: all 0.2s;"><h4 style="margin:0; font-size: 20px; color: var(--text);">${m.name}</h4></div>`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-pink" onclick="window.closeGame()"><span>🔙</span> Back</button>
<button class="cute-btn btn-blue" onclick="window.startMoodMatch(document.getElementById('game-container'))"><span>🔄</span> Next One</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> I'm Done!</button>
</div>
`;
}
window.checkMood = function(selected, target) {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Yes! That's exactly right! 🌟";
feedback.style.color = "#2ecc71";
gameScore += 15;
document.getElementById('game-score').innerText = gameScore;
setTimeout(() => startMoodMatch(document.getElementById('game-container')), 1500);
} else {
feedback.innerText = "Not quite. Look closely at the face! 😊";
feedback.style.color = "#e74c3c";
}
};
// --- Level 3 Advanced Game Functions ---
function startAbcSort(container) {
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
const lowercase = "abcdefghijklmnopqrstuvwxyz".split("");
let currentSet = [];
window.renderAbcSort = () => {
// Pick 4 random pairs
const indices = [];
while(indices.length < 4) {
const r = Math.floor(Math.random() * 26);
if(!indices.includes(r)) indices.push(r);
}
const items = [];
indices.forEach(i => {
items.push({ val: uppercase[i], type: 'upper' });
items.push({ val: lowercase[i], type: 'lower' });
});
items.sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Sort into Upper and Lower Case</h3>
<div style="display: flex; justify-content: space-around; width: 100%; margin-bottom: 30px;">
<div id="upper-bin" ondrop="window.drop(event, 'upper')" ondragover="window.allowDrop(event)"
style="width: 200px; min-height: 250px; background: #e3f2fd; border: 4px dashed #2196f3; border-radius: 20px; padding: 15px; text-align: center;">
<h4 style="color: #1976d2;">UPPER CASE</h4>
<div class="bin-content" style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;"></div>
</div>
<div id="lower-bin" ondrop="window.drop(event, 'lower')" ondragover="window.allowDrop(event)"
style="width: 200px; min-height: 250px; background: #f1f8e9; border: 4px dashed #4caf50; border-radius: 20px; padding: 15px; text-align: center;">
<h4 style="color: #388e3c;">lower case</h4>
<div class="bin-content" style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;"></div>
</div>
</div>
<div id="source-bin" style="display: flex; flex-wrap: wrap; gap: 15px; justify-content: center; background: #fff; padding: 20px; border-radius: 20px; border: 2px solid #eee; min-height: 100px; width: 80%;">
${items.map((item, i) => `
<div id="drag-${i}" draggable="true" ondragstart="window.drag(event)" data-type="${item.type}"
style="width: 50px; height: 50px; background: white; border: 2px solid #ccc; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: move; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
${item.val}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderAbcSort()"><span>🔄</span> New Letters</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.allowDrop = (ev) => ev.preventDefault();
window.drag = (ev) => {
ev.dataTransfer.setData("text", ev.target.id);
ev.dataTransfer.setData("type", ev.target.getAttribute("data-type"));
};
window.drop = (ev, binType) => {
ev.preventDefault();
const data = ev.dataTransfer.getData("text");
const itemType = ev.dataTransfer.getData("type");
const draggedEl = document.getElementById(data);
const feedback = document.getElementById('feedback');
if (itemType === binType) {
let bin = ev.target;
if (!bin.classList.contains('bin-content')) {
bin = bin.querySelector('.bin-content') || bin.closest('div').querySelector('.bin-content');
}
bin.appendChild(draggedEl);
draggedEl.setAttribute("draggable", "false");
draggedEl.style.cursor = "default";
gameScore += 10;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
const sourceBin = document.getElementById('source-bin');
if (sourceBin && sourceBin.children.length === 0) {
feedback.innerText = "Great sorting! 🌟";
feedback.style.color = "var(--success)";
setTimeout(window.renderAbcSort, 1500);
}
} else {
feedback.innerText = "Try the other bin! 😊";
feedback.style.color = "var(--danger)";
setTimeout(() => feedback.innerText = "", 1500);
}
};
window.renderAbcSort();
}
function startMissingLetter(container) {
const words = [
{ word: 'APPLE', missing: 0, icon: '🍎' },
{ word: 'BREAD', missing: 2, icon: '🍞' },
{ word: 'CANDY', missing: 1, icon: '🍬' },
{ word: 'DRESS', missing: 3, icon: '👗' },
{ word: 'EARTH', missing: 4, icon: '🌍' },
{ word: 'FROGS', missing: 2, icon: '🐸' }
];
window.renderMissingLetter = () => {
const target = words[Math.floor(Math.random() * words.length)];
const wordArr = target.word.split("");
const correctLetter = wordArr[target.missing];
wordArr[target.missing] = "_";
// Generate options including correct one
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
const options = [correctLetter];
while(options.length < 4) {
const r = alphabet[Math.floor(Math.random() * 26)];
if(!options.includes(r)) options.push(r);
}
options.sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Fill in the missing letter!</h3>
<div style="text-align:center; margin-bottom: 30px;">
<span style="font-size: 80px; display: block; margin-bottom: 10px;">${target.icon}</span>
<div style="font-size: 60px; letter-spacing: 15px; font-weight: 800; color: var(--primary);">
${wordArr.join("")}
</div>
</div>
<div style="display: flex; justify-content: center; gap: 20px; flex-wrap: wrap;">
${options.map(letter => `
<button onclick="window.checkMissingLetter('${letter}', '${correctLetter}')"
class="cute-btn btn-blue" style="font-size: 30px; width: 70px; height: 70px; padding:0;">
${letter}
</button>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderMissingLetter()"><span>🔄</span> Next Word</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.checkMissingLetter = (selected, target) => {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Excellent spelling! 🌟";
feedback.style.color = "var(--success)";
gameScore += 20;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
setTimeout(window.renderMissingLetter, 1500);
} else {
feedback.innerText = "Try another letter! 😊";
feedback.style.color = "var(--danger)";
}
};
window.renderMissingLetter();
}
function startWordPictureMatch(container) {
const items = [
{ word: 'Elephant', icon: '🐘' }, { word: 'Rocket', icon: '🚀' },
{ word: 'Guitar', icon: '🎸' }, { word: 'Pizza', icon: '🍕' },
{ word: 'Rainbow', icon: '🌈' }, { word: 'Bicycle', icon: '🚲' }
];
window.renderWordPicture = () => {
const target = items[Math.floor(Math.random() * items.length)];
const options = [...items].sort(() => Math.random() - 0.5).slice(0, 4);
if (!options.find(o => o.word === target.word)) {
options[Math.floor(Math.random() * 4)] = target;
}
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Match the picture to the word!</h3>
<div style="text-align:center; margin-bottom: 30px; background: #fff; padding: 20px; border-radius: 20px; border: 3px solid var(--secondary); display: inline-block;">
<h2 style="font-size: 48px; margin:0; color: var(--primary);">${target.word.toUpperCase()}</h2>
</div>
<div style="display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; margin-top: 20px;">
${options.map(opt => `
<div onclick="window.checkWordPicture('${opt.word}', '${target.word}')" class="card"
style="width: 140px; height: 140px; font-size: 70px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.2s;">
${opt.icon}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderWordPicture()"><span>🔄</span> Next</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.checkWordPicture = (selected, target) => {
const feedback = document.getElementById('feedback');
if (selected === target) {
feedback.innerText = "Perfect Match! 🌟";
feedback.style.color = "var(--success)";
gameScore += 15;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
setTimeout(window.renderWordPicture, 1500);
} else {
feedback.innerText = "That's a different one! 😊";
feedback.style.color = "var(--danger)";
}
};
window.renderWordPicture();
}
function startObjectSearch(container) {
const objects = [
{ name: 'Red Ball', icon: '🔴', color: 'red' }, { name: 'Blue Square', icon: '🟦', color: 'blue' },
{ name: 'Yellow Star', icon: '⭐', color: 'yellow' }, { name: 'Green Apple', icon: '🍏', color: 'green' },
{ name: 'Purple Heart', icon: '💜', color: 'purple' }, { name: 'Orange Orange', icon: '🍊', color: 'orange' }
];
window.renderObjectSearch = () => {
const target = objects[Math.floor(Math.random() * objects.length)];
// Create a grid of 12 items
const gridItems = [];
for(let i=0; i<11; i++) {
gridItems.push(objects[Math.floor(Math.random() * objects.length)]);
}
gridItems.push(target);
gridItems.sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Find the <span style="color:${target.color}; text-decoration: underline;">${target.name}</span>!</h3>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; background: #fff; padding: 25px; border-radius: 30px; box-shadow: 0 10px 20px rgba(0,0,0,0.05);">
${gridItems.map((item, i) => `
<div onclick="window.checkObjectSearch('${item.name}', '${target.name}', this)"
style="font-size: 50px; cursor: pointer; text-align: center; padding: 10px; border-radius: 15px; transition: background 0.2s;">
${item.icon}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderObjectSearch()"><span>🔄</span> New Grid</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.checkObjectSearch = (selected, target, el) => {
const feedback = document.getElementById('feedback');
if (selected === target) {
el.style.background = "#e8f5e9";
el.style.transform = "scale(1.1)";
feedback.innerText = "Found it! Well done! 🌟";
feedback.style.color = "var(--success)";
gameScore += 10;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
setTimeout(window.renderObjectSearch, 1500);
} else {
el.style.opacity = "0.3";
feedback.innerText = "Keep looking... 🔍";
feedback.style.color = "var(--text)";
}
};
window.renderObjectSearch();
}
function startSpatialPuzzle(container) {
const puzzles = [
{ full: '🧩', parts: ['🟦', '🟩', '🟨'] },
{ full: '🏠', parts: ['📐', '🧱', '🚪'] },
{ full: '🌸', parts: ['🍃', '📍', '💗'] }
];
window.renderSpatialPuzzle = () => {
const target = puzzles[Math.floor(Math.random() * puzzles.length)];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Spatial Reasoning: Build the Object</h3>
<div style="display: flex; flex-direction: column; align-items: center; gap: 30px;">
<div id="puzzle-target" style="width: 150px; height: 150px; background: #eee; border: 4px dashed #ccc; border-radius: 20px; display: flex; align-items: center; justify-content: center; font-size: 80px; color: #bbb;">
?
</div>
<div style="display: flex; gap: 20px;">
${target.parts.map(part => `
<div class="card" style="width: 80px; height: 80px; font-size: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer;"
onclick="window.addSpatialPiece(this, '${target.full}', ${target.parts.length})">
${part}
</div>
`).join('')}
</div>
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderSpatialPuzzle()"><span>🔄</span> New Puzzle</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
let piecesPlaced = 0;
window.addSpatialPiece = (el, fullIcon, totalPieces) => {
if (el.style.opacity === "0.5") return;
el.style.opacity = "0.5";
piecesPlaced++;
gameScore += 5;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
if (piecesPlaced >= totalPieces) {
const feedback = document.getElementById('feedback');
const puzzleTarget = document.getElementById('puzzle-target');
if (feedback) {
feedback.innerText = "Puzzle Complete! 🌟";
feedback.style.color = "var(--success)";
}
if (puzzleTarget) {
puzzleTarget.innerText = fullIcon;
puzzleTarget.style.background = "#fff";
puzzleTarget.style.borderColor = "var(--success)";
}
setTimeout(window.renderSpatialPuzzle, 2000);
}
};
};
window.renderSpatialPuzzle();
}
function startAlphabetOrder(container) {
const letters = "ABCDEFG".split("");
window.renderAlphabetOrder = () => {
const items = [...letters].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Put the letters in order! (A to G)</h3>
<div id="target-row" style="display: flex; justify-content: center; gap: 10px; margin-bottom: 40px; min-height: 70px;">
${letters.map((_, i) => `
<div class="drop-target" data-index="${i}" ondrop="window.dropOrder(event, ${i})" ondragover="window.allowDrop(event)"
style="width: 60px; height: 60px; border: 3px dashed #ccc; border-radius: 10px; background: #fafafa; display: flex; align-items: center; justify-content: center; font-size: 24px; color: #ddd;">
${i+1}
</div>
`).join('')}
</div>
<div id="source-row" style="display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; background: #fff; padding: 20px; border-radius: 20px; border: 2px solid #eee;">
${items.map((item, i) => `
<div id="drag-order-${i}" draggable="true" ondragstart="window.drag(event)" data-val="${item}"
style="width: 60px; height: 60px; background: white; border: 2px solid var(--primary); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 28px; cursor: move; box-shadow: 0 4px 6px rgba(0,0,0,0.05); color: var(--primary); font-weight: bold;">
${item}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderAlphabetOrder()"><span>🔄</span> Reset</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.dropOrder = (ev, targetIdx) => {
ev.preventDefault();
const data = ev.dataTransfer.getData("text");
const draggedEl = document.getElementById(data);
if (!draggedEl) return;
const val = draggedEl.getAttribute("data-val");
const feedback = document.getElementById('feedback');
if (val === letters[targetIdx]) {
ev.target.innerText = val;
ev.target.style.background = "#e8f5e9";
ev.target.style.borderColor = "#4caf50";
ev.target.style.color = "#2e7d32";
ev.target.style.borderStyle = "solid";
draggedEl.remove();
gameScore += 15;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
const sourceRow = document.getElementById('source-row');
if (sourceRow && sourceRow.children.length === 0) {
feedback.innerText = "Alphabet Master! 🌟";
feedback.style.color = "var(--success)";
setTimeout(window.renderAlphabetOrder, 2000);
}
} else {
feedback.innerText = "That's not where " + val + " goes! 😊";
feedback.style.color = "var(--danger)";
setTimeout(() => feedback.innerText = "", 1500);
}
};
window.renderAlphabetOrder();
}
function startJigsawPuzzle(container) {
const images = [
{ icon: '🐶', name: 'Puppy' }, { icon: '🐱', name: 'Kitty' },
{ icon: '🦁', name: 'Lion' }, { icon: '🐘', name: 'Elephant' }
];
window.renderJigsaw = () => {
const target = images[Math.floor(Math.random() * images.length)];
const parts = [1, 2, 3, 4];
const shuffledParts = [...parts].sort(() => Math.random() - 0.5);
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 20px;">Complete the Jigsaw!</h3>
<div id="jigsaw-board" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; width: 200px; height: 200px; margin: 0 auto 30px; background: #eee; padding: 5px; border-radius: 10px;">
${parts.map(p => `
<div class="jigsaw-slot" data-part="${p}" ondrop="window.dropJigsaw(event, ${p})" ondragover="window.allowDrop(event)"
style="width: 90px; height: 90px; background: #fff; border: 2px dashed #ccc; display: flex; align-items: center; justify-content: center; font-size: 40px; color: #eee;">
${p}
</div>
`).join('')}
</div>
<div id="jigsaw-pieces" style="display: flex; justify-content: center; gap: 15px; flex-wrap: wrap;">
${shuffledParts.map(p => `
<div id="piece-${p}" draggable="true" ondragstart="window.drag(event)" data-part="${p}"
style="width: 80px; height: 80px; background: var(--secondary); border: 2px solid var(--primary); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 50px; cursor: move; color: white;">
${target.icon}
</div>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderJigsaw()"><span>🔄</span> New Puzzle</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.dropJigsaw = (ev, targetPart) => {
ev.preventDefault();
const data = ev.dataTransfer.getData("text");
const draggedEl = document.getElementById(data);
if (!draggedEl) return;
const part = draggedEl.getAttribute("data-part");
const feedback = document.getElementById('feedback');
if (parseInt(part) === targetPart) {
ev.target.innerHTML = draggedEl.innerHTML;
ev.target.style.background = "var(--secondary)";
ev.target.style.borderColor = "var(--primary)";
ev.target.style.borderStyle = "solid";
draggedEl.remove();
gameScore += 20;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
const pieces = document.getElementById('jigsaw-pieces');
if (pieces && pieces.children.length === 0) {
feedback.innerText = "Puzzle Solved! 🌟";
feedback.style.color = "var(--success)";
setTimeout(window.renderJigsaw, 2000);
}
} else {
feedback.innerText = "Wrong spot! 😊";
feedback.style.color = "var(--danger)";
setTimeout(() => feedback.innerText = "", 1500);
}
};
window.renderJigsaw();
}
function startAdvancedPatterns(container) {
const sequences = [
{ pattern: ['🔴', '🔵', '🟢', '🔴', '🔵'], next: '🟢', options: ['🟢', '🟡', '🟠'] },
{ pattern: ['1', '2', '4', '8'], next: '16', options: ['10', '16', '12'] },
{ pattern: ['⬆️', '➡️', '⬇️', '⬅️', '⬆️'], next: '➡️', options: ['⬇️', '➡️', '⬆️'] }
];
window.renderAdvancedPattern = () => {
const target = sequences[Math.floor(Math.random() * sequences.length)];
container.innerHTML = `
<h3 style="text-align:center; margin-bottom: 30px;">Complete the Logic Pattern</h3>
<div id="pattern-display" style="display: flex; justify-content: center; align-items: center; gap: 15px; margin-bottom: 40px; background: #f0f4f8; padding: 25px; border-radius: 30px; border: 2px solid #d1d9e6;">
${target.pattern.map(item => `<div style="font-size: 45px; font-weight: bold;">${item}</div>`).join('<div style="color:#999;">•</div>')}
<div style="color:#999;">•</div>
<div id="pattern-target" style="width: 70px; height: 70px; border: 4px solid var(--primary); border-radius: 15px; display: flex; align-items: center; justify-content: center; background: #fff; font-size: 35px; color: #ddd;">?</div>
</div>
<div style="display: flex; justify-content: center; gap: 20px;">
${target.options.map(opt => `
<button onclick="window.checkAdvancedPattern('${opt}', '${target.next}')"
class="cute-btn btn-blue" style="font-size: 24px; min-width: 80px;">
${opt}
</button>
`).join('')}
</div>
<p id="feedback" style="text-align:center; font-size:24px; margin-top:30px; font-weight:bold; height:50px;"></p>
<div class="control-box">
<button class="cute-btn btn-blue" onclick="window.renderAdvancedPattern()"><span>🔄</span> New Challenge</button>
<button class="cute-btn btn-orange" onclick="window.finishGame()"><span>✅</span> Done</button>
</div>
`;
};
window.checkAdvancedPattern = (selected, target) => {
const feedback = document.getElementById('feedback');
const patternTarget = document.getElementById('pattern-target');
if (selected === target) {
if (feedback) {
feedback.innerText = "Brilliant Logic! 🌟";
feedback.style.color = "var(--success)";
}
gameScore += 25;
const scoreEl = document.getElementById('game-score');
if (scoreEl) scoreEl.innerText = gameScore;
if (patternTarget) {
patternTarget.innerText = target;
patternTarget.style.color = "#000";
patternTarget.style.borderColor = "var(--success)";
}
setTimeout(window.renderAdvancedPattern, 2000);
} else {
if (feedback) {
feedback.innerText = "Look closer at the sequence! 😊";
feedback.style.color = "var(--danger)";
}
}
};
window.renderAdvancedPattern();
}
// Final Window Exports
window.startCamera = startCamera;
window.stopCamera = stopCamera;
window.capturePhoto = capturePhoto;
window.uploadEmotion = uploadEmotion;
window.confirmEmotion = confirmEmotion;
window.toggleCustomEmotion = toggleCustomEmotion;
window.saveCorrection = saveCorrection;
window.trainAI = trainAI;
window.startGame = startGame;
window.startNewQuiz = startNewQuiz;
window.saveDiary = saveDiary;
window.logout = logout;
window.register = register;
window.login = login;
window.addChild = addChild;
window.selectChild = selectChild;
window.deleteChild = deleteChild;
window.toggleAuth = toggleAuth;
window.syncChildAge = syncChildAge;
window.loadProgress = loadProgress;
window.generateReport = generateReport;
window.startAnimalColoring = startAnimalColoring;
window.selectAnimalTemplate = selectAnimalTemplate;
window.startLearnEmotions = startLearnEmotions;
window.startShapeMatch = startShapeMatch;
window.startPatternMatch = startPatternMatch;
window.startColorMatch = startColorMatch;
window.startMemoryGame = startMemoryGame;
window.startColoringBook = startColoringBook;
window.selectTemplate = selectTemplate;
window.selectPaletteColor = selectPaletteColor;
window.colorBlock = colorBlock;
window.nextImage = nextImage;
window.closeGame = closeGame;
window.restartGame = restartGame;
window.finishGame = finishGame;
window.startMoodMatch = startMoodMatch;
window.startAbcSort = startAbcSort;
window.startAlphabetOrder = startAlphabetOrder;
window.startMissingLetter = startMissingLetter;
window.startWordPictureMatch = startWordPictureMatch;
window.startObjectSearch = startObjectSearch;
window.startSpatialPuzzle = startSpatialPuzzle;
window.startJigsawPuzzle = startJigsawPuzzle;
window.startAdvancedPatterns = startAdvancedPatterns;
window.getLevelInfo = getLevelInfo;