Maths-Olymps / src /streamlit_app.py
DataMine's picture
Update src/streamlit_app.py
c7ccd4c verified
Raw History Blame Contribute Delete
66.4 kB
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()