add progress tracking with XP, levels, and stats dashboard

This commit is contained in:
sultanbek_askaruly 2026-06-19 06:33:31 +00:00
parent c5bec7fed6
commit 69f0d9f78b
12 changed files with 452 additions and 7 deletions

2
.git-credentials Normal file
View File

@ -0,0 +1,2 @@
http://sultanbek_askaruly:ccaf3980723058bd093e312780c167c82b9a54e6@172.18.0.1%3a3300
https://sultanbek_askaruly:ccaf3980723058bd093e312780c167c82b9a54e6@git.vibe42.kz

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>

View File

@ -1014,6 +1014,167 @@ footer a:hover {
}
}
/* ===== PROGRESS PAGE ===== */
.level-hero {
text-align: center;
padding: 48px 32px;
background: linear-gradient(135deg, var(--accent-50), var(--green-light));
border-radius: 24px;
margin-bottom: 32px;
}
.level-hero .level-icon {
font-size: 56px;
margin-bottom: 12px;
}
.level-hero .level-title {
font-size: 36px;
font-weight: 800;
color: var(--accent);
margin-bottom: 4px;
}
.level-hero .level-desc {
font-size: 16px;
color: var(--gray-500);
margin-bottom: 20px;
}
.level-hero .level-xp-total {
font-size: 20px;
font-weight: 700;
color: var(--ink);
}
.level-bar {
height: 12px;
background: var(--gray-100);
border-radius: 6px;
overflow: hidden;
max-width: 400px;
margin: 12px auto 0;
}
.level-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent), var(--green));
border-radius: 6px;
transition: width 0.6s ease;
width: 0%;
}
.stat-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: var(--white);
border: 1px solid var(--gray-100);
border-radius: 16px;
padding: 24px;
text-align: center;
box-shadow: var(--shadow);
transition: all 0.3s;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-hover);
}
.stat-card .stat-icon {
font-size: 28px;
margin-bottom: 8px;
}
.stat-card .stat-value {
font-size: 28px;
font-weight: 800;
color: var(--accent);
}
.stat-card .stat-label {
font-size: 13px;
color: var(--gray-500);
margin-top: 4px;
}
.activity-list {
background: var(--white);
border: 1px solid var(--gray-100);
border-radius: 20px;
padding: 28px;
box-shadow: var(--shadow);
}
.activity-list h3 {
font-size: 20px;
font-weight: 700;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.activity-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--gray-100);
font-size: 15px;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-item .act-icon {
width: 36px;
height: 36px;
background: var(--accent-50);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.activity-item .act-text {
flex: 1;
color: var(--gray-500);
}
.activity-item .act-xp {
font-weight: 700;
color: var(--green);
font-size: 14px;
}
.level-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--accent-50);
padding: 6px 16px 6px 12px;
border-radius: 20px;
font-size: 13px;
}
.level-badge .level-label {
font-weight: 700;
color: var(--accent);
}
.level-badge .level-xp {
color: var(--gray-500);
}
/* ===== ANIMATIONS ===== */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
@ -79,6 +80,12 @@
<p>Listen to words and phrases spoken aloud. Practice your pronunciation and improve your speaking skills.</p>
<a href="pronunciation.html" class="btn btn-primary">Start Speaking</a>
</div>
<div class="card">
<div class="card-icon">📊</div>
<h3>My Progress</h3>
<p>Track your XP, level, and completed activities. Watch yourself improve from Beginner to Advanced!</p>
<a href="progress.html" class="btn btn-primary">View Progress</a>
</div>
</div>
</div>
</section>

View File

@ -50,13 +50,14 @@ function checkExercise(btn) {
if (isCorrect) {
parent.classList.add('correct');
feedback.textContent = 'Correct!';
feedback.textContent = 'Correct! +10 XP';
feedback.className = 'feedback correct';
var exId = exercise.getAttribute('data-ex-id') || exercise.querySelector('h3').textContent.trim();
markExerciseDone(exId);
} else {
parent.classList.add('wrong');
feedback.textContent = 'Try again!';
feedback.className = 'feedback wrong';
// Show correct answer
options.forEach(function(opt) {
const radio = opt.querySelector('input[type="radio"]');
if (radio && radio.value === 'true') {
@ -76,8 +77,10 @@ function checkFill(input) {
if (input.value.trim().toLowerCase() === answer.toLowerCase()) {
input.classList.add('correct');
feedback.textContent = 'Correct!';
feedback.textContent = 'Correct! +10 XP';
feedback.className = 'feedback correct';
var exId = input.closest('.exercise').getAttribute('data-ex-id') || input.closest('.exercise').querySelector('h3').textContent.trim();
markExerciseDone(exId);
} else {
input.classList.add('wrong');
feedback.textContent = 'Try again! Correct answer: ' + answer;
@ -96,8 +99,10 @@ function checkTranslate(btn) {
if (input.value.trim().toLowerCase() === answer.toLowerCase()) {
input.classList.add('correct');
feedback.textContent = 'Correct!';
feedback.textContent = 'Correct! +10 XP';
feedback.className = 'feedback correct';
var exId = exercise.getAttribute('data-ex-id') || exercise.querySelector('h3').textContent.trim();
markExerciseDone(exId);
} else {
input.classList.add('wrong');
feedback.textContent = 'Try again! Correct answer: ' + answer;
@ -124,8 +129,10 @@ function checkMatch(btn) {
});
if (allCorrect) {
feedback.textContent = 'All correct! Great job!';
feedback.textContent = 'All correct! Great job! +10 XP';
feedback.className = 'feedback correct';
var exId = exercise.getAttribute('data-ex-id') || exercise.querySelector('h3').textContent.trim();
markExerciseDone(exId);
} else {
feedback.textContent = 'Some answers are wrong. Try again!';
feedback.className = 'feedback wrong';
@ -146,12 +153,21 @@ function checkTest() {
}
});
markTestDone(score, total);
var data = getProgress();
var info = getLevelProgress(data.xp);
const resultBox = document.getElementById('test-result');
const scoreDisplay = document.getElementById('score');
scoreDisplay.textContent = score + '/' + total;
resultBox.classList.add('show');
// Scroll to result
var detail = document.getElementById('test-detail');
if (detail) {
detail.innerHTML = 'Level: <strong>' + info.current.label + '</strong> &nbsp;|&nbsp; XP: <strong>' + data.xp + '</strong> &nbsp;|&nbsp; Best: <strong>' + data.bestTestScore + '/' + total + '</strong>';
}
resultBox.scrollIntoView({ behavior: 'smooth' });
}
@ -184,6 +200,7 @@ function repeatWord(btn) {
var word = btn.getAttribute('data-word');
speakWord(word, 0.8);
btn.textContent = '🔊 Playing...';
markWordListened();
setTimeout(function() { btn.textContent = '🔊 Listen'; }, 1500);
}
@ -220,6 +237,107 @@ function speakPhrase(btn) {
setTimeout(function() { btn.innerHTML = '🔊 Hear Phrase'; }, 1500);
}
// --- Progress Tracking ---
var PROGRESS_KEY = 'english_easy_progress';
var LEVELS = [
{ min: 0, label: 'A0 Beginner', desc: 'You are just starting your English journey!' },
{ min: 50, label: 'A1 Starter', desc: 'You know basic words and phrases.' },
{ min: 150, label: 'A2 Elementary', desc: 'You can understand simple sentences.' },
{ min: 300, label: 'B1 Intermediate', desc: 'You can talk about familiar topics.' },
{ min: 500, label: 'B2 Upper Intermediate', desc: 'You can communicate fluently.' },
{ min: 800, label: 'C1 Advanced', desc: 'You understand complex texts and ideas.' }
];
function getProgress() {
var data = JSON.parse(localStorage.getItem(PROGRESS_KEY));
if (!data) {
data = {
xp: 0,
exercisesDone: [],
testsDone: 0,
bestTestScore: 0,
lastTestScore: 0,
wordsListened: 0,
readingDone: [],
joined: new Date().toISOString().split('T')[0]
};
localStorage.setItem(PROGRESS_KEY, JSON.stringify(data));
}
return data;
}
function saveProgress(data) {
localStorage.setItem(PROGRESS_KEY, JSON.stringify(data));
}
function addXP(amount, reason) {
var data = getProgress();
data.xp += amount;
saveProgress(data);
updateLevelBadge();
return data;
}
function getLevel(xp) {
for (var i = LEVELS.length - 1; i >= 0; i--) {
if (xp >= LEVELS[i].min) return LEVELS[i];
}
return LEVELS[0];
}
function getLevelProgress(xp) {
var current = getLevel(xp);
var nextMin = 9999;
for (var i = 0; i < LEVELS.length; i++) {
if (LEVELS[i].min > current.min) {
nextMin = LEVELS[i].min;
break;
}
}
var range = nextMin - current.min;
var progress = xp - current.min;
return { current: current, nextMin: nextMin, progress: progress, range: range, percent: range > 0 ? Math.min(100, Math.round(progress / range * 100)) : 100 };
}
function updateLevelBadge() {
var data = getProgress();
var info = getLevelProgress(data.xp);
var badge = document.querySelector('.level-badge');
if (badge) {
badge.innerHTML = '<span class="level-label">' + info.current.label + '</span><span class="level-xp">' + data.xp + ' XP</span>';
}
var bar = document.querySelector('.level-bar-fill');
if (bar) bar.style.width = info.percent + '%';
}
function markExerciseDone(id) {
var data = getProgress();
if (data.exercisesDone.indexOf(id) === -1) {
data.exercisesDone.push(id);
data.xp += 10;
saveProgress(data);
}
}
function markTestDone(score, total) {
var data = getProgress();
data.testsDone++;
data.lastTestScore = score;
if (score > data.bestTestScore) data.bestTestScore = score;
data.xp += 15 + score * 2;
saveProgress(data);
}
function markWordListened() {
var data = getProgress();
data.wordsListened++;
if (data.wordsListened % 5 === 0) {
data.xp += 5;
saveProgress(data);
}
}
// --- Daily Reminder at 20:00 ---
(function() {
var LS_KEY = 'english_easy_daily';

150
progress.html Normal file
View File

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My Progress — English Easy</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<div class="header-inner">
<a href="index.html" class="logo"><span class="logo-icon">E</span>English <span>Easy</span></a>
<button class="menu-toggle" aria-label="Menu"></button>
<nav>
<ul class="nav-list">
<li><a href="index.html">Home</a></li>
<li><a href="vocabulary.html">Vocabulary</a></li>
<li><a href="grammar.html">Grammar</a></li>
<li><a href="pronunciation.html">Pronunciation</a></li>
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
</div>
</header>
<section class="page-header">
<div class="container">
<h1>My Progress</h1>
<p>Track your learning journey and see how much you've achieved.</p>
</div>
</section>
<section class="section">
<div class="container" id="progress-root">
<div class="level-hero" id="level-hero">
<div class="level-icon" id="level-icon">🌱</div>
<div class="level-title" id="level-title">A0 Beginner</div>
<div class="level-desc" id="level-desc">You are just starting your English journey!</div>
<div class="level-xp-total" id="level-xp-total">0 XP</div>
<div class="level-bar"><div class="level-bar-fill" id="level-bar-fill" style="width:0%"></div></div>
</div>
<div class="stat-grid" id="stats-grid">
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-value" id="stat-exercises">0</div>
<div class="stat-label">Exercises Done</div>
</div>
<div class="stat-card">
<div class="stat-icon">📝</div>
<div class="stat-value" id="stat-tests">0</div>
<div class="stat-label">Tests Taken</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏆</div>
<div class="stat-value" id="stat-best"></div>
<div class="stat-label">Best Test Score</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔊</div>
<div class="stat-value" id="stat-words">0</div>
<div class="stat-label">Words Practiced</div>
</div>
</div>
<div class="activity-list">
<h3>📋 Recent Activity</h3>
<div id="activity-feed">
<div class="activity-item"><div class="act-icon">👋</div><div class="act-text">Start learning to see your activity here!</div></div>
</div>
</div>
</div>
</section>
<footer>
<div class="container">
<p>&copy; 2026 <a href="index.html">English Easy</a>. Made for learning English.</p>
</div>
</footer>
<script src="js/script.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
renderProgress();
});
function renderProgress() {
var data = getProgress();
var info = getLevelProgress(data.xp);
var icons = ['🌱', '🌿', '🌳', '⭐', '🌟', '👑'];
var levelIndex = LEVELS.indexOf(info.current);
document.getElementById('level-icon').textContent = icons[levelIndex] || '🌱';
document.getElementById('level-title').textContent = info.current.label;
document.getElementById('level-desc').textContent = info.current.desc;
document.getElementById('level-xp-total').textContent = data.xp + ' XP';
document.getElementById('level-bar-fill').style.width = info.percent + '%';
document.getElementById('stat-exercises').textContent = data.exercisesDone.length;
document.getElementById('stat-tests').textContent = data.testsDone;
document.getElementById('stat-best').textContent = data.bestTestScore > 0 ? data.bestTestScore + '/10' : '—';
document.getElementById('stat-words').textContent = data.wordsListened;
// Activity feed
var feed = document.getElementById('activity-feed');
feed.innerHTML = '';
var activities = [];
if (data.exercisesDone.length > 0) {
activities.push({ icon: '✅', text: 'Completed ' + data.exercisesDone.length + ' exercise' + (data.exercisesDone.length > 1 ? 's' : ''), xp: data.exercisesDone.length * 10 });
}
if (data.testsDone > 0) {
activities.push({ icon: '📝', text: 'Took ' + data.testsDone + ' test' + (data.testsDone > 1 ? 's' : ''), xp: 15 + data.bestTestScore * 2 });
}
if (data.wordsListened > 0) {
activities.push({ icon: '🔊', text: 'Practiced ' + data.wordsListened + ' word' + (data.wordsListened > 1 ? 's' : '') + ' with pronunciation', xp: Math.floor(data.wordsListened / 5) * 5 });
}
if (data.readingDone && data.readingDone.length > 0) {
activities.push({ icon: '📚', text: 'Read ' + data.readingDone.length + ' text' + (data.readingDone.length > 1 ? 's' : ''), xp: 0 });
}
if (activities.length === 0) {
feed.innerHTML = '<div class="activity-item"><div class="act-icon">👋</div><div class="act-text">Complete exercises and tests to see your activity here!</div></div>';
} else {
activities.forEach(function(a) {
var div = document.createElement('div');
div.className = 'activity-item';
div.innerHTML = '<div class="act-icon">' + a.icon + '</div><div class="act-text">' + a.text + '</div><div class="act-xp">+' + a.xp + ' XP</div>';
feed.appendChild(div);
});
// Total
var totalDiv = document.createElement('div');
totalDiv.className = 'activity-item';
totalDiv.style.borderTop = '2px solid var(--gray-100)';
totalDiv.style.paddingTop = '16px';
totalDiv.style.marginTop = '8px';
totalDiv.innerHTML = '<div class="act-icon"></div><div class="act-text"><strong>Total XP</strong></div><div class="act-xp" style="font-size:18px;">' + data.xp + ' XP</div>';
feed.appendChild(totalDiv);
}
}
</script>
</body>
</html>

View File

@ -117,6 +117,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
@ -132,7 +133,7 @@
<div class="result-box" id="test-result">
<div class="score" id="score">0/10</div>
<p>Keep learning and try again!</p>
<p id="test-detail">Keep learning and try again!</p>
</div>
</div>

View File

@ -20,6 +20,7 @@
<li><a href="exercises.html">Exercises</a></li>
<li><a href="tests.html">Tests</a></li>
<li><a href="reading.html">Reading</a></li>
<li><a href="progress.html">Progress</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>