Maths-Olymps / src /streamlit_app.py
DataMine's picture
Update src/streamlit_app.py
c7ccd4c verified
import streamlit as st
import os
from groq import Groq
import json
import re
from typing import List, Dict, Optional
import time
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta
import streamlit.components.v1 as components
import hashlib
# Configure page
st.set_page_config(
page_title="🧮 MathGenius Academy",
page_icon="🧮",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for enhanced UI
def load_custom_css():
st.markdown("""
<style>
/* Import Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Nunito:wght@300;400;500;600;700&display=swap');
/* Global Styles */
.main {
font-family: 'Poppins', sans-serif;
}
/* Header Styling */
.hero-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 2rem;
border-radius: 20px;
color: white;
text-align: center;
margin-bottom: 2rem;
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.hero-header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(20px, -20px) rotate(180deg); }
}
.hero-title {
font-size: 3.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
position: relative;
z-index: 1;
}
.hero-subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 1rem;
position: relative;
z-index: 1;
}
/* Timer styling */
.timer-display {
background: linear-gradient(135deg, #FF6B6B, #FF8E8E);
color: white;
padding: 1rem 2rem;
border-radius: 20px;
text-align: center;
font-size: 2rem;
font-weight: 700;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3); }
50% { transform: scale(1.02); box-shadow: 0 15px 40px rgba(255, 107, 107, 0.5); }
100% { transform: scale(1); box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3); }
}
.timer-warning {
background: linear-gradient(135deg, #FF4757, #FF6B6B) !important;
animation: fastPulse 1s infinite !important;
}
@keyframes fastPulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Card Styling */
.custom-card {
background: white;
padding: 2rem;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
border: 1px solid #e1e8ed;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
color: #333;
}
.custom-card:hover {
transform: translateY(-10px);
box-shadow: 0 25px 50px rgba(0,0,0,0.15);
}
.custom-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.custom-card h4 {
color: #667eea !important;
margin-bottom: 1rem;
font-weight: 600;
}
.custom-card p, .custom-card li {
color: #444 !important;
line-height: 1.6;
}
.custom-card ul {
padding-left: 1.5rem;
}
.custom-card ol {
padding-left: 1.5rem;
}
/* Question Card */
.question-card {
background: linear-gradient(145deg, #f8fafc 0%, #e2e8f0 100%);
border-radius: 20px;
padding: 2rem;
margin: 1rem 0;
border-left: 6px solid #667eea;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.question-card:hover {
transform: translateX(10px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
}
/* Stats Cards */
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
border-radius: 15px;
text-align: center;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: scale(1.05);
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
/* Buttons */
.custom-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 50px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.custom-button:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
/* Progress Bar */
.progress-container {
background: #e2e8f0;
border-radius: 50px;
padding: 0.5rem;
margin: 1rem 0;
}
.progress-bar {
background: linear-gradient(90deg, #667eea, #764ba2);
height: 20px;
border-radius: 50px;
transition: width 1s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 0.8rem;
}
/* Animations */
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in-up {
animation: slideInUp 0.6s ease-out;
}
.pulse-animation {
animation: pulse 2s infinite;
}
/* Sidebar Styling */
.sidebar .sidebar-content {
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
color: white;
}
/* Toast Notifications */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 2rem;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 1000;
animation: slideInRight 0.5s ease;
}
@keyframes slideInRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
/* Mobile Responsive */
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.custom-card {
padding: 1rem;
}
.question-card {
padding: 1rem;
}
.timer-display {
font-size: 1.5rem;
padding: 0.75rem 1.5rem;
}
}
body, .main, .block-container {
background: #f7f9fb !important;
}
.custom-card {
margin-bottom: 2rem;
padding: 2.5rem;
border-radius: 24px;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.08);
}
.question-card {
background: #fff;
border-radius: 20px;
box-shadow: 0 4px 24px rgba(102, 126, 234, 0.10);
margin-bottom: 2rem;
padding: 2rem 2.5rem;
}
.question-card:hover {
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.15);
}
.question-card .stRadio > div {
gap: 1.5rem;
}
.stRadio label {
background: #f0f4fa;
border-radius: 16px;
padding: 0.75rem 1.5rem;
margin-bottom: 0.5rem;
font-size: 1.1rem;
transition: background 0.2s, color 0.2s;
cursor: pointer;
}
.stRadio label:hover {
background: #e3e9f7;
color: #667eea;
}
.stRadio input:checked + label {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.stButton > button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border-radius: 30px;
font-weight: 600;
font-size: 1.1rem;
padding: 0.75rem 2.5rem;
margin: 0.5rem 0;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.10);
transition: background 0.2s, box-shadow 0.2s;
}
.stButton > button:hover {
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.15);
}
.stMarkdown h2, .stMarkdown h3, .stMarkdown h4 {
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
color: #667eea;
}
.stMarkdown hr {
border: none;
border-top: 2px solid #e1e8ed;
margin: 2rem 0;
}
.progress-container {
margin-bottom: 1.5rem;
}
</style>
""", unsafe_allow_html=True)
class MathOlympiadExam:
def __init__(self):
self.client = self._initialize_groq_client()
self.grade_levels = {
1: {"name": "Grade 1 (Ages 6-7)", "emoji": "🌱", "color": "#FF6B6B"},
2: {"name": "Grade 2 (Ages 7-8)", "emoji": "🌿", "color": "#4ECDC4"},
3: {"name": "Grade 3 (Ages 8-9)", "emoji": "🍃", "color": "#45B7D1"},
4: {"name": "Grade 4 (Ages 9-10)", "emoji": "🌳", "color": "#96CEB4"},
5: {"name": "Grade 5 (Ages 10-11)", "emoji": "🎯", "color": "#FECA57"},
6: {"name": "Grade 6 (Ages 11-12)", "emoji": "🚀", "color": "#FF9FF3"},
7: {"name": "Grade 7 (Ages 12-13)", "emoji": "⭐", "color": "#54A0FF"},
8: {"name": "Grade 8 (Ages 13-14)", "emoji": "💎", "color": "#5F27CD"},
9: {"name": "Grade 9 (Ages 14-15)", "emoji": "🏆", "color": "#00D2D3"},
10: {"name": "Grade 10 (Ages 15-16)", "emoji": "👑", "color": "#FF6348"}
}
def _initialize_groq_client(self) -> Optional[Groq]:
"""Initialize Groq client with API key"""
try:
api_key = "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"
if not api_key:
st.error("⚠️ GROQ_API_KEY not found!")
return None
return Groq(api_key=api_key)
except Exception as e:
st.error(f"Failed to initialize Groq client: {e}")
return None
def generate_questions(self, grade: int, num_questions: int, difficulty: str = "medium") -> List[Dict]:
"""Generate unique math olympiad questions with STRICT rules against visual questions - FIXED"""
if not self.client:
return []
try:
difficulty_map = {
1: "Basic counting 1-20, simple addition/subtraction within 20, comparing numbers, basic money problems",
2: "Addition/subtraction within 100, word problems about time/money, simple multiplication by 2,5,10",
3: "Multiplication tables to 12, basic division, simple fractions (1/2, 1/4), word problems with multiple steps",
4: "Multi-digit multiplication/division, equivalent fractions, decimals to tenths, perimeter calculations",
5: "Advanced arithmetic, basic algebra (find the missing number), percentages, area calculations, factors/multiples",
6: "Pre-algebra equations, ratios/proportions, basic statistics (mean/median), coordinate problems",
7: "Linear equations, integers, basic probability, algebraic expressions, geometric formulas",
8: "Systems of equations, exponents/roots, advanced probability, quadratic expressions, geometric theorems",
9: "Advanced algebra, trigonometry basics, sequences/series, complex word problems, mathematical proofs",
10: "Pre-calculus topics, advanced trigonometry, logarithms, complex problem solving, advanced geometry"
}
difficulty_modifiers = {
"easy": "Focus on fundamental concepts with straightforward calculations",
"medium": "Standard olympiad difficulty with moderate complexity requiring 2-3 steps",
"hard": "Advanced multi-step problems requiring deep mathematical reasoning"
}
topics = difficulty_map.get(grade, "general math concepts")
modifier = difficulty_modifiers.get(difficulty, "")
# STRICT forbidden keywords - expanded list
forbidden_keywords = [
"figure", "diagram", "shape", "block", "square", "triangle", "circle", "rectangle", "polygon",
"pattern below", "picture", "image", "graph", "chart", "visual", "draw", "sketch", "plot",
"look at", "observe", "see the", "shown", "displayed", "illustrated", "appears",
"geometric shape", "arrange", "tessellation", "symmetry", "reflection", "rotation",
"hexagon", "pentagon", "octagon", "parallelogram", "rhombus", "trapezoid",
"coordinate plane", "grid", "lattice", "dot", "point on", "line segment",
"angle", "vertex", "side", "face", "edge", "cube", "cylinder", "sphere",
"shaded", "colored", "pattern", "sequence of shapes", "visual pattern"
]
seen_hashes = getattr(st.session_state, 'question_hashes', set())
# ENHANCED prompt with VERY strict formatting requirements
base_prompt = f"""
Generate exactly {num_questions} UNIQUE Math Olympiad questions for Grade {grade}.
🚨 ABSOLUTE RESTRICTIONS - NEVER INCLUDE:
❌ Any visual elements (shapes, figures, diagrams, patterns, images)
❌ Questions requiring visual interpretation
❌ Geometric shape identification or counting
❌ Pattern completion with visual elements
❌ Any reference to "figure", "diagram", "shape", "pattern below"
❌ Coordinate plotting or graph reading
❌ Any visual arrangement problems
✅ ONLY ALLOWED - TEXT-BASED QUESTIONS:
✅ Pure arithmetic calculations and word problems
✅ Number sequences (like: 2, 4, 6, 8, ?)
✅ Logic puzzles with numbers/text only
✅ Real-world scenarios (money, time, travel, shopping)
✅ Algebraic word problems
✅ Statistical problems with given data
✅ Mathematical reasoning without visuals
TOPICS: {topics}
DIFFICULTY: {modifier}
🎯 CRITICAL: Answer must be EXACTLY one letter: A, B, C, or D (no explanations in answer line)
FORMAT (EXACT - NO DEVIATIONS):
**Question 1:** [Engaging word problem - NO VISUALS]
A) [Option A with number/calculation]
B) [Option B with number/calculation]
C) [Option C with number/calculation]
D) [Option D with number/calculation]
**Answer:** A
**Explanation:** [Step-by-step solution]
**Hint:** [Helpful tip for solving]
**Question 2:** [Next question...]
A) [Option A]
B) [Option B]
C) [Option C]
D) [Option D]
**Answer:** C
**Explanation:** [Step-by-step solution]
**Hint:** [Helpful tip for solving]
[Continue for all {num_questions} questions]
🔥 IMPORTANT FORMATTING RULES:
- Answer line must contain ONLY the letter (A, B, C, or D)
- No parentheses, explanations, or additional text in the answer
- Each option must start with the letter followed by closing parenthesis
- Keep questions practical and engaging with real scenarios
MAKE QUESTIONS PRACTICAL AND ENGAGING - Use real scenarios students can relate to!
"""
max_attempts = 3
all_questions = []
for attempt in range(max_attempts):
try:
response = self.client.chat.completions.create(
model="openai/gpt-oss-120b", # More reliable model
messages=[
{"role": "system", "content": "You are an expert Math Olympiad question creator. You MUST create only text-based word problems without any visual elements. The answer must be EXACTLY one letter: A, B, C, or D with no additional text or explanations."},
{"role": "user", "content": base_prompt}
],
temperature=0.7, # Balanced creativity
max_tokens=4000
)
new_questions = self._parse_questions_enhanced(response.choices[0].message.content)
# STRICT filtering
valid_questions = []
for q in new_questions:
q_text = q['question_text'].lower()
# Check for forbidden keywords
if any(forbidden in q_text for forbidden in forbidden_keywords):
continue
# Additional checks for visual elements
if any(word in q_text for word in ['draw', 'sketch', 'plot', 'shown', 'displayed', 'appears']):
continue
# Check uniqueness
q_hash = self._get_question_hash(q['question_text'] + str(q['options']))
if q_hash not in seen_hashes:
q['hash'] = q_hash
valid_questions.append(q)
seen_hashes.add(q_hash)
all_questions.extend(valid_questions)
if len(all_questions) >= num_questions:
break
# Add retry hint with more specific guidance
base_prompt += f"\n\nNEED MORE QUESTIONS: Generated {len(all_questions)} so far. Create MORE PRACTICAL WORD PROBLEMS about real-life scenarios (shopping, travel, sports, cooking, etc.). REMEMBER: Answer must be EXACTLY one letter (A, B, C, or D)."
except Exception as e:
st.error(f"Error in attempt {attempt + 1}: {e}")
continue
st.session_state.question_hashes = seen_hashes
final_questions = all_questions[:num_questions]
if len(final_questions) < num_questions:
st.warning(f"⚠️ Generated {len(final_questions)} unique valid questions (requested {num_questions}). Some were filtered out for containing visual elements.")
return final_questions
except Exception as e:
st.error(f"Failed to generate questions: {e}")
return []
def _get_question_hash(self, question_text: str) -> str:
"""Generate hash for question to track uniqueness"""
# Remove numbers and normalize text for better uniqueness detection
normalized = re.sub(r'\d+', 'X', question_text.lower().strip())
normalized = re.sub(r'[^\w\s]', '', normalized) # Remove punctuation
return hashlib.md5(normalized.encode()).hexdigest()
def _parse_questions_enhanced(self, content: str) -> List[Dict]:
"""Enhanced question parsing with better error handling and validation"""
questions = []
# Split by question markers
question_blocks = re.split(r'\*\*Question \d+:\*\*', content)
if len(question_blocks) < 2:
# Try alternative splitting
question_blocks = re.split(r'Question \d+:', content)
for i, block in enumerate(question_blocks[1:], 1):
try:
lines = [line.strip() for line in block.strip().split('\n') if line.strip()]
if len(lines) < 6: # Need at least question + 4 options + answer
continue
question_text = lines[0].strip()
if not question_text:
continue
# Parse options - FIXED: Create both dictionary and list properly
options_dict = {}
options_list = []
correct_answer = None
explanation = ""
hint = ""
current_section = "options"
for line in lines[1:]:
line = line.strip()
# Detect sections
if line.startswith('**Answer:**') or line.startswith('**Correct Answer:**'):
current_section = "answer"
answer_part = line.replace('**Answer:**', '').replace('**Correct Answer:**', '').strip()
if answer_part:
# Extract only the letter from the answer (A, B, C, or D)
answer_match = re.search(r'[ABCD]', answer_part.upper())
if answer_match:
correct_answer = answer_match.group()
continue
elif line.startswith('**Explanation:**'):
current_section = "explanation"
exp_part = line.replace('**Explanation:**', '').strip()
if exp_part:
explanation = exp_part
continue
elif line.startswith('**Hint:**'):
current_section = "hint"
hint_part = line.replace('**Hint:**', '').strip()
if hint_part:
hint = hint_part
continue
# Process based on current section
if current_section == "options" and line.startswith(('A)', 'B)', 'C)', 'D)')):
option_key = line[0] # A, B, C, or D
option_text = line[2:].strip() # Remove "A) "
options_dict[option_key] = option_text
options_list.append(line) # Keep full format "A) text"
elif current_section == "answer":
# Try to extract single letter answer
if line.strip() in ['A', 'B', 'C', 'D']:
correct_answer = line.strip()
elif not correct_answer:
# Fallback: extract first letter found
answer_match = re.search(r'[ABCD]', line.upper())
if answer_match:
correct_answer = answer_match.group()
elif current_section == "explanation" and line:
if explanation:
explanation += " " + line
else:
explanation = line
elif current_section == "hint" and line:
if hint:
hint += " " + line
else:
hint = line
# Validate question - ensure we have exactly 4 options and a valid answer
if len(options_dict) == 4 and correct_answer and correct_answer in options_dict:
questions.append({
'question_number': i,
'question_text': question_text,
'options': options_list, # List format for compatibility: ['A) text', 'B) text', ...]
'options_dict': options_dict, # Dictionary format: {'A': 'text', 'B': 'text', ...}
'correct_answer': correct_answer,
'explanation': explanation or "Work through the problem step by step.",
'hint': hint or "Read the question carefully and identify what you need to find.",
'user_answer': None,
'time_taken': 0,
'difficulty': self._estimate_difficulty(question_text)
})
else:
st.warning(f"Question {i} validation failed: options={len(options_dict)}, answer='{correct_answer}', keys={list(options_dict.keys())}")
except Exception as e:
st.warning(f"Error parsing question {i}: {e}")
continue
return questions
def _estimate_difficulty(self, question_text: str) -> str:
"""Estimate question difficulty based on content"""
hard_keywords = ['system', 'equation', 'quadratic', 'prove', 'theorem', 'complex', 'advanced']
easy_keywords = ['add', 'subtract', 'simple', 'basic', 'find the', 'what is', 'how many']
text_lower = question_text.lower()
if any(keyword in text_lower for keyword in hard_keywords):
return "Hard"
elif any(keyword in text_lower for keyword in easy_keywords):
return "Easy"
else:
return "Medium"
# Initialize session state
def initialize_session_state():
if 'questions' not in st.session_state:
st.session_state.questions = []
if 'exam_completed' not in st.session_state:
st.session_state.exam_completed = False
if 'grade_selected' not in st.session_state:
st.session_state.grade_selected = None
if 'exam_history' not in st.session_state:
st.session_state.exam_history = []
if 'current_question' not in st.session_state:
st.session_state.current_question = 0
if 'exam_mode' not in st.session_state:
st.session_state.exam_mode = 'practice'
if 'start_time' not in st.session_state:
st.session_state.start_time = None
if 'user_profile' not in st.session_state:
st.session_state.user_profile = {
'name': 'Math Explorer',
'total_exams': 0,
'total_questions': 0,
'correct_answers': 0,
'favorite_grade': 5,
'badges': [],
'streak': 0
}
if 'question_hashes' not in st.session_state:
st.session_state.question_hashes = set()
@st.cache_resource
def get_exam_system():
return MathOlympiadExam()
def render_hero_section():
"""Render the hero section with animations"""
st.markdown("""
<div class="hero-header slide-in-up">
<div class="hero-title">🧮 MathGenius Academy</div>
<div class="hero-subtitle">Master Mathematics Through Interactive Olympiad Training</div>
<div style="margin-top: 1rem;">
<span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">🎯 Practical Problems</span>
<span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">📊 Progress Tracking</span>
<span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">🏆 Achievement System</span>
</div>
</div>
""", unsafe_allow_html = True)
def render_timer_display():
"""Render enhanced timer display with better positioning"""
if st.session_state.exam_mode == 'timed' and st.session_state.start_time:
total_time = len(st.session_state.questions) * 60 # 1 minute per question
elapsed = time.time() - st.session_state.start_time
remaining = max(0, total_time - elapsed)
mins, secs = divmod(int(remaining), 60)
# Add warning class if time is running low
warning_class = "timer-warning" if remaining <= 60 else ""
timer_html = f"""
<div class="timer-display {warning_class}">
<div style="display: flex; align-items: center; justify-content: center; gap: 1rem;">
<div style="font-size: 3rem;">⏰</div>
<div>
<div style="font-size: 2.5rem; font-weight: 800;">{mins:02d}:{secs:02d}</div>
<div style="font-size: 1rem; opacity: 0.9;">Time Remaining</div>
</div>
</div>
</div>
"""
st.markdown(timer_html, unsafe_allow_html=True)
# Auto-submit when time is up
if remaining <= 0:
st.error("⏰ Time's up! Auto-submitting your exam...")
st.session_state.exam_completed = True
time.sleep(1)
st.rerun()
# Show warning when time is low
elif remaining <= 60:
st.warning(f"⚠️ Only {int(remaining)} seconds left!")
def render_dashboard():
"""Render user dashboard with statistics"""
profile = st.session_state.user_profile
st.markdown("### 📊 Your Learning Dashboard")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown(f"""
<div class="stat-card">
<div class="stat-number">{profile['total_exams']}</div>
<div class="stat-label">Exams Taken</div>
</div>
""", unsafe_allow_html=True)
with col2:
percentage = (profile['correct_answers'] / max(profile['total_questions'], 1)) * 100
st.markdown(f"""
<div class="stat-card">
<div class="stat-number">{percentage:.1f}%</div>
<div class="stat-label">percentage</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown(f"""
<div class="stat-card">
<div class="stat-number">{profile['streak']}</div>
<div class="stat-label">Day Streak</div>
</div>
""", unsafe_allow_html=True)
with col4:
st.markdown(f"""
<div class="stat-card">
<div class="stat-number">{len(profile['badges'])}</div>
<div class="stat-label">Badges Earned</div>
</div>
""", unsafe_allow_html=True)
# Progress Chart
if st.session_state.exam_history:
df = pd.DataFrame(st.session_state.exam_history)
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 📈 Performance Trend")
fig = px.line(df, x='date', y='percentage',
title='Your Progress Over Time',
color_discrete_sequence=['#667eea'])
fig.update_layout(
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
)
st.plotly_chart(fig, use_container_width=True)
def render_question_card(question, question_index, total_questions):
"""Render an individual question with enhanced UI - FIXED"""
progress = ((question_index + 1) / total_questions) * 100
st.markdown(f"""
<div class="progress-container">
<div class="progress-bar" style="width: {progress}%;">
Question {question_index + 1} of {total_questions}
</div>
</div>
""", unsafe_allow_html=True)
st.markdown(f"""
<div class="question-card slide-in-up">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="color: #667eea; margin: 0;">Question {question['question_number']}</h3>
<span style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 0.25rem 0.75rem; border-radius: 15px; font-size: 0.8rem;">
{question.get('difficulty', 'Medium')} Level
</span>
</div>
<div style="font-size: 1.1rem; font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6; color: #222;">
{question['question_text']}
</div>
</div>
""", unsafe_allow_html=True)
# FIXED: Handle both list and dictionary options formats
if isinstance(question['options'], list):
# List format: ['A) text', 'B) text', ...]
option_keys = []
option_labels = []
for opt in question['options']:
if ')' in opt:
key, label = opt.split(')', 1)
option_keys.append(key.strip())
option_labels.append(label.strip())
# Create format function for radio buttons
def format_option(key):
try:
idx = option_keys.index(key)
return f"{key}) {option_labels[idx]}"
except (ValueError, IndexError):
return f"{key}) Option not found"
selected = st.radio(
"Choose your answer:",
options=option_keys,
format_func=format_option,
key=f"q_{question_index}",
index=None
)
elif isinstance(question['options'], dict):
# Dictionary format: {'A': 'text', 'B': 'text', ...}
option_keys = list(question['options'].keys())
def format_option_dict(key):
return f"{key}) {question['options'].get(key, 'Option not found')}"
selected = st.radio(
"Choose your answer:",
options=option_keys,
format_func=format_option_dict,
key=f"q_{question_index}",
index=None
)
else:
st.error("Invalid options format in question data")
return None
# Hint system
if st.button(f"💡 Need a hint?", key=f"hint_{question_index}"):
if question.get('hint'):
st.info(f"💡 **Hint:** {question['hint']}")
else:
st.info("💡 **Hint:** Try breaking down the problem step by step!")
return selected
def render_exam_interface():
"""Render the main exam interface with improved timer - FIXED"""
questions = st.session_state.questions
total_questions = len(questions)
# Display timer at the top for timed mode
render_timer_display()
# Question navigation
if total_questions > 1:
st.markdown("#### 📍 Question Navigation")
cols = st.columns(min(10, total_questions))
for i in range(total_questions):
with cols[i % 10]:
status = "✅" if questions[i]['user_answer'] else "⭕"
if st.button(f"{status} Q{i+1}", key=f"nav_{i}"):
st.session_state.current_question = i
# Current question
current_q = st.session_state.current_question
if current_q < total_questions:
# FIXED: Call render_question_card as a standalone function
selected = render_question_card(questions[current_q], current_q, total_questions)
questions[current_q]['user_answer'] = selected
# Navigation buttons
col1, col2, col3 = st.columns([1, 2, 1])
with col1:
if current_q > 0:
if st.button("⬅️ Previous", key="prev_btn"):
st.session_state.current_question -= 1
st.rerun()
with col3:
if current_q < total_questions - 1:
if st.button("Next ➡️", key="next_btn"):
st.session_state.current_question += 1
st.rerun()
else:
if st.button("🎯 Submit Exam", key="submit_btn", type="primary"):
unanswered = [i+1 for i, q in enumerate(questions) if not q['user_answer']]
if unanswered:
st.error(f"⚠️ Please answer all questions. Missing: {', '.join(map(str, unanswered))}")
else:
st.session_state.exam_completed = True
st.rerun()
def calculate_badges(score, total, grade):
"""Calculate badges earned based on performance"""
percentage = (score / total) * 100
badges = []
if percentage == 100:
badges.append("🏆 Perfect Score")
elif percentage >= 90:
badges.append("⭐ Excellence")
elif percentage >= 80:
badges.append("🎯 High Achiever")
elif percentage >= 70:
badges.append("📚 Good Progress")
if grade >= 8:
badges.append("🚀 Advanced Level")
return badges
def render_results_section():
"""Render enhanced results with animations and insights"""
st.markdown("---")
st.markdown("### 🎉 Exam Results")
questions = st.session_state.questions
correct_count = sum(1 for q in questions if q['user_answer'] == q['correct_answer'])
total_questions = len(questions)
percentage = (correct_count / total_questions) * 100
# Update user profile
profile = st.session_state.user_profile
profile['total_exams'] += 1
profile['total_questions'] += total_questions
profile['correct_answers'] += correct_count
# Add to history
st.session_state.exam_history.append({
'date': datetime.now(),
'grade': st.session_state.grade_selected,
'score': correct_count,
'total': total_questions,
'percentage': percentage
})
# Calculate badges
new_badges = calculate_badges(correct_count, total_questions, st.session_state.grade_selected)
profile['badges'].extend(new_badges)
# Animated score reveal
col1, col2, col3 = st.columns(3)
with col1:
st.markdown(f"""
<div class="stat-card pulse-animation">
<div class="stat-number">{correct_count}/{total_questions}</div>
<div class="stat-label">Final Score</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="stat-card pulse-animation">
<div class="stat-number">{percentage:.1f}%</div>
<div class="stat-label">percentage</div>
</div>
""", unsafe_allow_html=True)
with col3:
if percentage >= 90:
grade_emoji = "🏆"
grade_text = "Excellent!"
elif percentage >= 80:
grade_emoji = "⭐"
grade_text = "Great Job!"
elif percentage >= 70:
grade_emoji = "👍"
grade_text = "Good Work!"
else:
grade_emoji = "📚"
grade_text = "Keep Practicing!"
st.markdown(f"""
<div class="stat-card pulse-animation">
<div class="stat-number">{grade_emoji}</div>
<div class="stat-label">{grade_text}</div>
</div>
""", unsafe_allow_html=True)
# New badges notification
if new_badges:
st.success(f"🎉 New badges earned: {', '.join(new_badges)}")
# Detailed question review
st.markdown("#### 📝 Question Review")
for i, question in enumerate(questions):
is_correct = question['user_answer'] == question['correct_answer']
with st.expander(f"Question {i+1} - {'✅ Correct' if is_correct else '❌ Incorrect'} ({question.get('difficulty', 'Medium')} Level)"):
st.markdown(f"**Question:** {question['question_text']}")
col1, col2 = st.columns(2)
with col1:
st.markdown(f"**Your Answer:** {question['user_answer']}")
with col2:
st.markdown(f"**Correct Answer:** {question['correct_answer']}")
if question.get('explanation'):
st.markdown(f"**Explanation:** {question['explanation']}")
if not is_correct and question.get('hint'):
st.info(f"💡 **Hint for next time:** {question['hint']}")
# Performance insights with bullet points
exam_system = get_exam_system()
if exam_system.client:
st.markdown("#### 🧠 AI Performance Analysis")
with st.spinner("Generating personalized insights..."):
summary = generate_enhanced_summary(
st.session_state.grade_selected,
questions,
correct_count,
total_questions,
exam_system
)
st.markdown(f"""
<div class="custom-card">
{summary}
</div>
""", unsafe_allow_html=True)
# Show completion message
st.markdown("#### 🎓 Exam Complete!")
st.info("Great job completing the exam! Use the sidebar to generate a new exam or check out the Dashboard tab to view your progress.")
def generate_enhanced_summary(grade, questions, score, total, exam_system):
"""Generate enhanced AI-powered performance summary in bullet points"""
try:
# Analyze performance patterns
correct_questions = [q for q in questions if q['user_answer'] == q['correct_answer']]
incorrect_questions = [q for q in questions if q['user_answer'] != q['correct_answer']]
difficulty_analysis = {}
for q in questions:
diff = q.get('difficulty', 'Medium')
if diff not in difficulty_analysis:
difficulty_analysis[diff] = {'correct': 0, 'total': 0}
difficulty_analysis[diff]['total'] += 1
if q['user_answer'] == q['correct_answer']:
difficulty_analysis[diff]['correct'] += 1
percentage = (score / total * 100) if total > 0 else 0
prompt = f"""Analyze this Math Olympiad performance for a Grade {grade} student:
PERFORMANCE SUMMARY:
- Score: {score}/{total} ({percentage:.1f}%)
- Difficulty Breakdown: {difficulty_analysis}
SAMPLE INCORRECT QUESTIONS:
{chr(10).join([f"- {q['question_text'][:100]}..." for q in incorrect_questions[:2]])}
Please provide a summary in BULLET POINTS with emojis using this exact format:
🎯 **Strengths:**
• [strength 1 with emoji]
• [strength 2 with emoji]
📈 **Areas to Improve:**
• [area to improve 1 with emoji]
• [area to improve 2 with emoji]
💡 **Quick Tips:**
• [tip 1 with emoji]
• [tip 2 with emoji]
🌟 **Motivation:**
• [encouraging message with emoji]
Use Grade {grade} appropriate language and keep each bullet point short and encouraging. Include relevant emojis for each point."""
response = exam_system.client.chat.completions.create(
model="openai/gpt-oss-120b",
messages=[
{"role": "system", "content": "You are an expert Math Olympiad mentor providing detailed, encouraging feedback in bullet points with emojis."},
{"role": "user", "content": prompt}
],
temperature=0,
max_tokens=1000
)
return response.choices[0].message.content
except Exception as e:
return f"""
🎯 **Strengths:**
• ✨ You completed {score} out of {total} questions correctly ({percentage:.1f}%)
• 💪 You showed good problem-solving skills
📈 **Areas to Improve:**
• 📚 Review the questions you missed for better understanding
• ⏰ Work on time management if needed
💡 **Quick Tips:**
• 🔍 Read each question carefully before answering
• 💭 Use hints when you're unsure
🌟 **Motivation:**
• 🎉 Great effort! Keep practicing and you'll improve even more!
"""
def reset_exam():
"""Reset exam state for new attempt"""
st.session_state.questions = []
st.session_state.exam_completed = False
st.session_state.current_question = 0
st.session_state.start_time = None
st.rerun()
def render_sidebar():
"""Enhanced sidebar with user profile and settings"""
exam_system = get_exam_system()
with st.sidebar:
# User profile section
st.markdown(f"""
<div style="text-align: center; padding: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px; color: white; margin-bottom: 1rem;">
<div style="font-size: 3rem; margin-bottom: 0.5rem;">👋</div>
<div style="font-size: 1.2rem; font-weight: 600;">{st.session_state.user_profile['name']}</div>
<div style="font-size: 0.9rem; opacity: 0.9;">Level {st.session_state.user_profile['favorite_grade']} Explorer</div>
</div>
""", unsafe_allow_html=True)
st.markdown("### ⚙️ Exam Configuration")
# Grade selection with enhanced UI
grade_options = list(exam_system.grade_levels.keys())
grade_labels = [f"{exam_system.grade_levels[g]['emoji']} {exam_system.grade_levels[g]['name']}" for g in grade_options]
grade = st.selectbox(
"🎯 Select Grade Level:",
options=grade_options,
format_func=lambda x: f"{exam_system.grade_levels[x]['emoji']} {exam_system.grade_levels[x]['name']}",
key="grade_selector",
help="Choose your current grade level for appropriate difficulty"
)
# Exam mode
exam_mode = st.radio(
"📝 Exam Mode:",
["practice", "timed", "challenge"],
format_func=lambda x: {
"practice": "🎯 Practice Mode",
"timed": "⏰ Timed Mode (1 min/question)",
"challenge": "🔥 Challenge Mode"
}[x],
help="Practice: No time limit | Timed: 1 minute per question | Challenge: Extra hard questions"
)
st.session_state.exam_mode = exam_mode
# Number of questions
if exam_mode == "challenge":
num_questions = st.slider("📊 Questions:", 5, 20, 10, help="Challenge mode: 5-20 questions")
else:
num_questions = st.slider("📊 Questions:", 3, 15, 5, help="Choose number of questions")
# Show timer info for timed mode
if exam_mode == "timed":
st.info(f"⏰ Total time: {num_questions} minutes ({num_questions} questions × 1 min each)")
# Difficulty level
difficulty = st.select_slider(
"⚡ Difficulty:",
options=["easy", "medium", "hard"],
value="medium",
format_func=lambda x: {
"easy": "🟢 Easy",
"medium": "🟡 Medium",
"hard": "🔴 Hard"
}[x]
)
# Generate exam button
if st.button("🚀 Generate New Exam", type="primary", use_container_width=True):
if not exam_system.client:
st.error("🚫 API not available!")
else:
with st.spinner(f"🎭 Creating your {exam_mode} exam with unique questions..."):
# Add loading animation
progress_bar = st.progress(0)
for i in range(100):
time.sleep(0.01)
progress_bar.progress(i + 1)
challenge_difficulty = "hard" if exam_mode == "challenge" else difficulty
questions = exam_system.generate_questions(grade, num_questions, challenge_difficulty)
if questions:
st.session_state.questions = questions
st.session_state.exam_completed = False
st.session_state.grade_selected = grade
st.session_state.current_question = 0
st.session_state.start_time = time.time() if exam_mode == "timed" else None
st.success(f"✨ Generated {len(st.session_state.questions)} unique questions!")
st.balloons()
else:
st.error("❌ Failed to generate exam. Try again!")
st.markdown("---")
# Quick stats
profile = st.session_state.user_profile
st.markdown("### 📈 Quick Stats")
st.metric("🎯 Total Exams", profile['total_exams'])
if profile['total_questions'] > 0:
percentage = (profile['correct_answers'] / profile['total_questions']) * 100
st.metric("🎪 percentage", f"{percentage:.1f}%")
st.metric("🔥 Streak", f"{profile['streak']} days")
# Recent badges
if profile['badges']:
st.markdown("### 🏆 Recent Badges")
for badge in profile['badges'][-3:]:
st.markdown(f"- {badge}")
st.markdown("---")
# Settings
with st.expander("⚙️ Settings"):
new_name = st.text_input("Your Name:", value=profile['name'])
if new_name != profile['name']:
profile['name'] = new_name
st.success("Name updated!")
if st.button("🗑️ Reset Progress"):
if st.checkbox("I'm sure"):
initialize_session_state()
st.success("Progress reset!")
st.rerun()
def render_welcome_screen():
"""Render welcome screen for new users"""
# Main welcome container
st.markdown("""
<div class="custom-card slide-in-up" style="text-align: center; padding: 3rem;">
<div style="font-size: 4rem; margin-bottom: 1rem;">🌟</div>
<h2 style="color: #667eea; margin-bottom: 1rem;">Welcome to MathGenius Academy!</h2>
<p style="font-size: 1.2rem; color: #666; margin-bottom: 2rem;">
Ready to embark on an exciting mathematical journey? Let's discover your potential!
</p>
</div>
""", unsafe_allow_html=True)
# Feature cards using Streamlit columns for better compatibility
st.markdown("### ✨ What Makes Us Special")
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("""
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
<div style="font-size: 3rem; margin-bottom: 1rem;">🎯</div>
<h4 style="margin: 0.5rem 0; color: white;">Unique Questions</h4>
<p style="margin: 0; color: rgba(255,255,255,0.9);">Always new, never repeated</p>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
<div style="font-size: 3rem; margin-bottom: 1rem;">📝</div>
<h4 style="margin: 0.5rem 0; color: white;">Text-Based Only</h4>
<p style="margin: 0; color: rgba(255,255,255,0.9);">Pure math, no visual puzzles</p>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown("""
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
<div style="font-size: 3rem; margin-bottom: 1rem;">⏰</div>
<h4 style="margin: 0.5rem 0; color: white;">Smart Timer</h4>
<p style="margin: 0; color: rgba(255,255,255,0.9);">1 minute per question</p>
</div>
""", unsafe_allow_html=True)
# Getting started message
st.markdown("---")
st.info("👈 Choose your grade level from the sidebar and click 'Generate New Exam' to begin your mathematical adventure!")
# Quick start tips
with st.expander("🚀 Quick Start Guide"):
st.markdown("""
**Ready to begin? Follow these simple steps:**
1. **🎯 Choose Your Grade**: Select your current grade level from the sidebar
2. **📝 Pick Your Mode**:
- 🟢 **Practice Mode**: No time pressure, perfect for learning
- ⏰ **Timed Mode**: 1 minute per question challenge
- 🔥 **Challenge Mode**: Extra difficult questions for advanced learners
3. **⚡ Set Difficulty**: Easy, Medium, or Hard - pick what feels right
4. **📊 Choose Questions**: 3-20 questions depending on your time
5. **🚀 Generate & Start**: Hit the generate button and begin!
**💡 Pro Tips:**
- Start with Practice mode to get comfortable
- Use hints when you're stuck - learning is the goal!
- Review all explanations to understand concepts better
- Track your progress in the Dashboard tab
""")
# Motivational quote
st.markdown("""
<div style="text-align: center; margin: 2rem 0; padding: 2rem; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 20px; color: white;">
<h3 style="margin: 0; color: white;">"Mathematics is not about numbers, equations, or algorithms. It is about understanding." - William Paul Thurston</h3>
</div>
""", unsafe_allow_html=True)
def main():
# Load custom CSS
load_custom_css()
# Initialize session state
initialize_session_state()
# Get exam system
exam_system = get_exam_system()
# Render hero section
render_hero_section()
# Main navigation
tab1, tab2, tab3 = st.tabs(["🎯 Exam", "📊 Dashboard", "ℹ️ About"])
with tab1:
# Render sidebar
render_sidebar()
# Main exam interface
if not st.session_state.questions:
render_welcome_screen()
elif st.session_state.exam_completed:
render_results_section()
else:
st.markdown("### 🧮 Math Olympiad Challenge")
# Show exam info
col1, col2, col3 = st.columns(3)
with col1:
st.info(f"📚 Grade {st.session_state.grade_selected}")
with col2:
if st.session_state.exam_mode == "timed":
total_time = len(st.session_state.questions)
st.info(f"⏱️ {st.session_state.exam_mode.title()} Mode ({total_time} min)")
else:
st.info(f"⏱️ {st.session_state.exam_mode.title()} Mode")
with col3:
st.info(f"📝 {len(st.session_state.questions)} Questions")
render_exam_interface()
with tab2:
render_dashboard()
# Additional dashboard features
if st.session_state.exam_history:
st.markdown("#### 🎯 Detailed Analytics")
df = pd.DataFrame(st.session_state.exam_history)
col1, col2 = st.columns(2)
with col1:
st.markdown("##### 📊 Score Distribution")
fig = px.histogram(df, x='percentage', nbins=10,
title='Score Distribution',
color_discrete_sequence=['#667eea'])
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)')
st.plotly_chart(fig, use_container_width=True)
with col2:
st.markdown("##### 📈 Improvement Trend")
df_trend = df.copy()
df_trend['moving_avg'] = df_trend['percentage'].rolling(window=3, min_periods=1).mean()
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_trend['date'], y=df_trend['percentage'],
mode='markers+lines', name='Actual Score',
line=dict(color='#667eea')))
fig.add_trace(go.Scatter(x=df_trend['date'], y=df_trend['moving_avg'],
mode='lines', name='Trend',
line=dict(color='#764ba2', dash='dash')))
fig.update_layout(title='Performance Trend',
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)')
st.plotly_chart(fig, use_container_width=True)
# Achievement showcase
profile = st.session_state.user_profile
if profile['badges']:
st.markdown("#### 🏆 Your Achievements")
badge_cols = st.columns(min(4, len(profile['badges'])))
for i, badge in enumerate(profile['badges'][-8:]): # Show last 8 badges
with badge_cols[i % 4]:
st.markdown(f"""
<div style="text-align: center; background: linear-gradient(135deg, #667eea, #764ba2);
color: white; padding: 1rem; border-radius: 15px; margin-bottom: 1rem;">
<div style="font-size: 1.5rem; margin-bottom: 0.5rem;">{badge.split()[0]}</div>
<div style="font-size: 0.8rem;">{' '.join(badge.split()[1:])}</div>
</div>
""", unsafe_allow_html=True)
with tab3:
st.markdown("# 📖 About MathGenius Academy")
# Mission section
with st.container():
st.markdown("## 🎯 Our Mission")
st.markdown("""
<div class="custom-card">
<p style="font-size: 1.1rem; line-height: 1.6; color: #444;">
To make mathematics accessible, engaging, and fun for students of all levels through
interactive Olympiad-style training. We believe every student can excel in mathematics
with the right guidance and practice.
</p>
</div>
""", unsafe_allow_html=True)
# Features section
st.markdown("## ✨ Key Features")
col1, col2 = st.columns(2)
with col1:
st.markdown(
"""
<div class="custom-card">
<h4 style="color: #667eea;">🤖 AI-Powered Learning</h4>
<ul style="color: #444;">
<li><strong>Unique Questions:</strong> Never see the same question twice</li>
<li><strong>Smart Difficulty:</strong> Automatic adjustment based on performance</li>
<li><strong>Text-Only Focus:</strong> Pure mathematical reasoning, no visual puzzles</li>
</ul>
<h4 style="color: #667eea;">🎮 Multiple Game Modes</h4>
<ul style="color: #444;">
<li><strong>Practice Mode:</strong> Learn at your own pace</li>
<li><strong>Timed Mode:</strong> 1 minute per question challenge</li>
<li><strong>Challenge Mode:</strong> Push your limits with hard problems</li>
</ul>
</div>
""",
unsafe_allow_html=True
)
with col2:
st.markdown(
"""
<div class="custom-card">
<h4 style="color: #667eea;">📊 Progress & Analytics</h4>
<ul style="color: #444;">
<li><strong>Performance Tracking:</strong> Monitor improvement over time</li>
<li><strong>Detailed Insights:</strong> Bullet-point feedback with emojis</li>
<li><strong>Visual Charts:</strong> Interactive graphs and analysis</li>
</ul>
<h4 style="color: #667eea;">🏆 Achievement System</h4>
<ul style="color: #444;">
<li><strong>Badges & Rewards:</strong> Celebrate your success</li>
<li><strong>Streak Tracking:</strong> Build consistent study habits</li>
<li><strong>Level Progression:</strong> Unlock new challenges</li>
</ul>
</div>
""",
unsafe_allow_html=True
)
# Getting Started Guide
st.markdown("## 🚀 Getting Started")
steps_col1, steps_col2 = st.columns(2)
with steps_col1:
st.markdown("""
<div class="custom-card">
<h4 style="color: #667eea;">📋 Setup Steps</h4>
<ol style="color: #444;">
<li><strong>Select Grade Level:</strong> Choose from Grade 1-10</li>
<li><strong>Pick Exam Mode:</strong> Practice, Timed, or Challenge</li>
<li><strong>Set Difficulty:</strong> Easy, Medium, or Hard</li>
<li><strong>Choose Questions:</strong> 3-20 questions per exam</li>
<li><strong>Generate Exam:</strong> Click the generate button</li>
</ol>
</div>
""", unsafe_allow_html=True)
with steps_col2:
st.markdown("""
<div class="custom-card">
<h4 style="color: #667eea;">💡 Success Tips</h4>
<ul style="color: #444;">
<li><strong>Start Easy:</strong> Begin with Practice mode</li>
<li><strong>Use Hints:</strong> Don't hesitate to ask for help</li>
<li><strong>Review Mistakes:</strong> Learn from wrong answers</li>
<li><strong>Track Progress:</strong> Check your Dashboard regularly</li>
<li><strong>Stay Consistent:</strong> Practice a little each day</li>
</ul>
</div>
""", unsafe_allow_html=True)
# Grade Level Guide
st.markdown("## 🎯 Grade Level Guide")
grade_col1, grade_col2, grade_col3 = st.columns(3)
with grade_col1:
st.markdown("""
<div class="custom-card">
<h4 style="color: #667eea;">🌱 Elementary (Grades 1-4)</h4>
<ul style="color: #444;">
<li>Basic arithmetic operations</li>
<li>Simple word problems</li>
<li>Number patterns and sequences</li>
<li>Basic geometry concepts</li>
<li>Counting and number sense</li>
</ul>
</div>
""", unsafe_allow_html=True)
with grade_col2:
st.markdown("""
<div class="custom-card">
<h4 style="color: #667eea;">🚀 Middle School (Grades 5-8)</h4>
<ul style="color: #444;">
<li>Pre-algebra concepts</li>
<li>Fractions and decimals</li>
<li>Basic statistics</li>
<li>Coordinate geometry</li>
<li>Problem-solving strategies</li>
</ul>
</div>
""", unsafe_allow_html=True)
with grade_col3:
st.markdown("""
<div class="custom-card">
<h4 style="color: #667eea;">⭐ High School (Grades 9-10)</h4>
<ul style="color: #444;">
<li>Advanced algebra</li>
<li>Geometry proofs</li>
<li>Trigonometry basics</li>
<li>Complex problem solving</li>
<li>Mathematical reasoning</li>
</ul>
</div>
""", unsafe_allow_html=True)
# FAQ Section
with st.expander("❓ Frequently Asked Questions"):
st.markdown("""
**Q: How are questions made unique?**
A: We use advanced AI with hash tracking to ensure you never see the same question twice, even across different sessions.
**Q: Why text-based questions only?**
A: We focus purely on mathematical reasoning and calculations, avoiding visual puzzles that don't translate well to text interfaces.
**Q: How does the timer work in timed mode?**
A: You get exactly 1 minute per question. For 5 questions, you get 5 minutes total. The timer is prominently displayed above the questions.
**Q: What if I get stuck on a question?**
A: Use the hint feature! We provide helpful hints to guide your thinking without giving away the answer.
**Q: How is my progress tracked?**
A: We track your percentage, response times, difficulty preferences, and improvement trends with detailed analytics.
**Q: How do I earn badges?**
A: Badges are earned automatically based on your performance, consistency, and achievements.
""")
# Footer
st.markdown("---")
st.markdown("""
<div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; color: white;">
<h3 style="color: white; margin-bottom: 1rem;">Ready to become a Math Genius? 🧮</h3>
<p style="color: rgba(255,255,255,0.9); margin: 0;">
Join thousands of students improving their mathematical skills with unique, challenging questions every day!
</p>
</div>
""", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
st.markdown("**Made with ❤️ for young mathematicians everywhere!**")
if __name__ == "__main__":
main()