LMS — Learner Dashboard
A friendly, focused learner dashboard for an online course platform. Continue-learning cards carry mini progress rings, difficulty pills, and resume buttons, while a streak and XP widget tracks daily momentum with a level bar and animated weekly dots. A weekly-goal progress ring, in-progress versus completed tabs, earned certificates with PDF actions, and color-coded upcoming deadlines round it out, plus an optional dark study mode for late-night sessions.
MCP
Code
:root {
--brand: #5b5bd6;
--brand-d: #4444c2;
--brand-50: #eeeefc;
--accent: #13b981;
--amber: #f59e0b;
--ink: #1a1a2e;
--ink-2: #44465f;
--muted: #6b6e87;
--bg: #f7f7fb;
--surface: #ffffff;
--line: rgba(26, 26, 46, 0.1);
--ok: #13b981;
--danger: #e05656;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow-sm: 0 1px 2px rgba(26, 26, 46, 0.06), 0 1px 3px rgba(26, 26, 46, 0.05);
--shadow-md: 0 6px 20px rgba(26, 26, 46, 0.08);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Dark study mode */
body.study {
--bg: #14141f;
--surface: #1d1d2c;
--ink: #f1f1f8;
--ink-2: #c4c6da;
--muted: #9698b4;
--line: rgba(255, 255, 255, 0.1);
--brand-50: #26264a;
}
.app {
display: grid;
grid-template-columns: 248px 1fr;
min-height: 100vh;
}
/* Sidebar */
.sidebar {
background: var(--surface);
border-right: 1px solid var(--line);
padding: 22px 16px;
display: flex;
flex-direction: column;
gap: 8px;
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 800;
font-size: 1.2rem;
padding: 6px 8px 18px;
}
.brand-mark {
width: 32px; height: 32px;
display: grid; place-items: center;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff;
border-radius: 10px;
font-size: 0.95rem;
}
.nav { display: flex; flex-direction: column; gap: 4px; }
.nav-foot { margin-top: auto; padding-top: 8px; border-top: 1px solid var(--line); }
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: var(--r-sm);
color: var(--ink-2);
text-decoration: none;
font-weight: 500;
font-size: 0.92rem;
transition: background .15s, color .15s;
}
.nav-item:hover { background: var(--brand-50); color: var(--brand); }
.nav-item.is-active { background: var(--brand); color: #fff; box-shadow: var(--shadow-sm); }
.ni-ico { width: 18px; text-align: center; opacity: .9; }
/* Main */
.main { padding: 26px 32px 48px; max-width: 1180px; }
.topbar {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
}
.greeting { margin: 0; color: var(--muted); font-size: .88rem; font-weight: 500; }
.page-title { margin: 2px 0 0; font-size: 1.6rem; font-weight: 800; letter-spacing: -.02em; }
.topbar-actions { display: flex; align-items: center; gap: 12px; }
.avatar {
width: 40px; height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--amber), #fb923c);
color: #fff;
display: grid; place-items: center;
font-weight: 700;
font-size: .82rem;
}
/* Buttons */
.btn {
font: inherit;
font-weight: 600;
border: 1px solid transparent;
border-radius: var(--r-sm);
padding: 9px 16px;
cursor: pointer;
transition: transform .08s, background .15s, box-shadow .15s, border-color .15s;
}
.btn:active { transform: translateY(1px); }
.btn-sm { padding: 7px 13px; font-size: .85rem; }
.btn-accent { background: var(--accent); color: #fff; }
.btn-accent:hover { background: #0fa372; box-shadow: 0 4px 12px rgba(19,185,129,.32); }
.btn-accent.is-done { background: var(--brand-50); color: var(--accent); cursor: default; }
.btn-brand { background: var(--brand); color: #fff; }
.btn-brand:hover { background: var(--brand-d); box-shadow: 0 4px 12px rgba(91,91,214,.3); }
.btn-ghost {
background: var(--surface);
border-color: var(--line);
color: var(--ink-2);
}
.btn-ghost:hover { border-color: var(--brand); color: var(--brand); }
.btn-ghost[aria-pressed="true"] { background: var(--brand); color: #fff; border-color: var(--brand); }
/* Cards */
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
box-shadow: var(--shadow-sm);
}
.card-eyebrow {
margin: 0 0 12px;
font-size: .72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--muted);
}
.grid {
display: grid;
grid-template-columns: 1.4fr 1fr 1fr;
gap: 18px;
margin-bottom: 30px;
}
/* Streak card */
.streak-top { display: flex; justify-content: space-between; align-items: flex-start; }
.streak-count {
margin: 0;
font-size: 2rem;
font-weight: 800;
letter-spacing: -.02em;
display: flex;
align-items: baseline;
gap: 6px;
}
.streak-unit { font-size: 1rem; font-weight: 600; color: var(--muted); }
.flame { font-size: 1.5rem; }
.flame.pop { animation: pop .6s ease; }
@keyframes pop {
0% { transform: scale(1); }
40% { transform: scale(1.5) rotate(-8deg); }
100% { transform: scale(1) rotate(0); }
}
.streak-week {
list-style: none;
display: flex;
gap: 8px;
padding: 0;
margin: 18px 0;
}
.dot {
width: 34px; height: 34px;
display: grid; place-items: center;
border-radius: 50%;
background: var(--bg);
border: 1px solid var(--line);
font-size: .76rem;
font-weight: 600;
color: var(--muted);
transition: transform .25s, background .25s;
}
.dot.done { background: var(--accent); border-color: var(--accent); color: #fff; }
.dot.today { border: 2px dashed var(--brand); color: var(--brand); }
.dot.today.done { border-style: solid; }
.dot.bump { animation: bump .45s ease; }
@keyframes bump { 50% { transform: translateY(-7px) scale(1.12); } }
.xp-row { border-top: 1px solid var(--line); padding-top: 16px; }
.xp-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; }
.lvl-pill {
background: var(--brand-50);
color: var(--brand);
font-weight: 700;
font-size: .74rem;
padding: 3px 10px;
border-radius: 999px;
}
.xp-text { font-size: .85rem; color: var(--ink-2); }
.xp-bar { height: 9px; background: var(--bg); border: 1px solid var(--line); border-radius: 999px; overflow: hidden; }
.xp-fill {
display: block; height: 100%;
background: linear-gradient(90deg, var(--brand), #8b8bf0);
border-radius: 999px;
transition: width .8s cubic-bezier(.4,0,.2,1);
}
/* Goal ring card */
.goal-card { display: flex; flex-direction: column; align-items: center; text-align: center; }
.goal-card .card-eyebrow { align-self: flex-start; }
.goal-ring { position: relative; width: 120px; height: 120px; }
.goal-ring svg { transform: rotate(-90deg); }
.ring-track { fill: none; stroke: var(--bg); stroke-width: 11; }
.ring-prog {
fill: none;
stroke: var(--accent);
stroke-width: 11;
stroke-linecap: round;
stroke-dasharray: 327;
stroke-dashoffset: 327;
transition: stroke-dashoffset 1s cubic-bezier(.4,0,.2,1);
}
.goal-center {
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
}
.goal-center strong { font-size: 1.5rem; font-weight: 800; }
.goal-center small { color: var(--muted); font-size: .76rem; }
.goal-note { margin: 14px 0 0; font-size: .82rem; color: var(--muted); }
/* Stat card */
.stat-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.stat-list li { display: flex; flex-direction: column; gap: 2px; }
.stat-num { font-size: 1.5rem; font-weight: 800; color: var(--brand); letter-spacing: -.02em; }
.stat-lbl { font-size: .78rem; color: var(--muted); font-weight: 500; }
/* Blocks */
.block { margin-bottom: 30px; }
.block-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.block-head h2 { margin: 0; font-size: 1.12rem; font-weight: 700; letter-spacing: -.01em; }
.tabs { display: inline-flex; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; padding: 4px; gap: 2px; }
.tab {
font: inherit;
font-weight: 600;
font-size: .84rem;
border: none;
background: transparent;
color: var(--muted);
padding: 7px 16px;
border-radius: 999px;
cursor: pointer;
transition: background .15s, color .15s;
}
.tab:hover { color: var(--ink); }
.tab.is-active { background: var(--brand); color: #fff; }
/* Courses */
.courses {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 18px;
}
.course {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
display: flex;
flex-direction: column;
gap: 14px;
box-shadow: var(--shadow-sm);
transition: transform .15s, box-shadow .15s, border-color .15s;
}
.course:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: rgba(91,91,214,.35); }
.course-top { display: flex; gap: 14px; align-items: flex-start; }
.course-thumb {
width: 52px; height: 52px;
border-radius: 12px;
display: grid; place-items: center;
font-size: 1.5rem;
flex-shrink: 0;
color: #fff;
}
.course-info { min-width: 0; }
.course-cat { margin: 0; font-size: .72rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.course-title { margin: 2px 0 0; font-size: 1rem; font-weight: 700; line-height: 1.3; }
.course-inst { margin: 4px 0 0; font-size: .8rem; color: var(--muted); }
.diff-pill {
display: inline-block;
font-size: .68rem;
font-weight: 700;
padding: 3px 9px;
border-radius: 999px;
text-transform: uppercase;
letter-spacing: .04em;
}
.diff-beginner { background: #e6f8f1; color: #0c8a5e; }
.diff-intermediate { background: #fff2dc; color: #b9760a; }
.diff-advanced { background: #fdeaea; color: #c23b3b; }
body.study .diff-beginner { background: rgba(19,185,129,.16); }
body.study .diff-intermediate { background: rgba(245,158,11,.16); }
body.study .diff-advanced { background: rgba(224,86,86,.16); }
/* mini progress ring on course */
.course-ring { position: relative; width: 46px; height: 46px; flex-shrink: 0; margin-left: auto; }
.course-ring svg { transform: rotate(-90deg); }
.cr-track { fill: none; stroke: var(--bg); stroke-width: 5; }
.cr-prog { fill: none; stroke: var(--brand); stroke-width: 5; stroke-linecap: round; stroke-dasharray: 125.6; transition: stroke-dashoffset .9s ease; }
.cr-prog.done { stroke: var(--accent); }
.course-ring span { position: absolute; inset: 0; display: grid; place-items: center; font-size: .68rem; font-weight: 700; }
.course-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: .8rem; color: var(--muted); }
.course-meta .lessons { font-weight: 500; }
.course-foot { display: flex; align-items: center; gap: 10px; }
.course-foot .btn { flex: 1; }
.completed-badge {
display: inline-flex; align-items: center; gap: 6px;
background: #e6f8f1; color: #0c8a5e;
font-weight: 600; font-size: .82rem;
padding: 9px 16px; border-radius: var(--r-sm);
flex: 1; justify-content: center;
}
body.study .completed-badge { background: rgba(19,185,129,.16); }
/* Two col layout */
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
/* Certificates */
.cert-list, .deadline-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.cert {
display: flex; align-items: center; gap: 14px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
box-shadow: var(--shadow-sm);
transition: border-color .15s;
}
.cert:hover { border-color: rgba(91,91,214,.35); }
.cert-seal {
width: 44px; height: 44px; flex-shrink: 0;
border-radius: 50%;
display: grid; place-items: center;
background: linear-gradient(135deg, var(--amber), #fbbf24);
color: #fff; font-size: 1.3rem;
}
.cert-body { flex: 1; min-width: 0; }
.cert-body strong { display: block; font-size: .92rem; font-weight: 700; }
.cert-body small { color: var(--muted); font-size: .78rem; }
.cert-dl {
font: inherit; font-weight: 600; font-size: .8rem;
border: 1px solid var(--line); background: var(--surface);
color: var(--brand); padding: 7px 12px; border-radius: var(--r-sm);
cursor: pointer; transition: background .15s, border-color .15s;
}
.cert-dl:hover { background: var(--brand-50); border-color: var(--brand); }
/* Deadlines */
.deadline {
display: flex; align-items: center; gap: 14px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
box-shadow: var(--shadow-sm);
}
.deadline-date {
width: 50px; flex-shrink: 0; text-align: center;
border-radius: var(--r-sm);
background: var(--brand-50); color: var(--brand);
padding: 6px 0;
}
.deadline-date .d-day { display: block; font-size: 1.2rem; font-weight: 800; line-height: 1; }
.deadline-date .d-mon { display: block; font-size: .68rem; font-weight: 600; text-transform: uppercase; }
.deadline-body { flex: 1; min-width: 0; }
.deadline-body strong { display: block; font-size: .9rem; font-weight: 700; }
.deadline-body small { color: var(--muted); font-size: .78rem; }
.deadline-tag {
font-size: .7rem; font-weight: 700; padding: 4px 10px; border-radius: 999px;
white-space: nowrap;
}
.tag-soon { background: #fdeaea; color: var(--danger); }
.tag-week { background: #fff2dc; color: #b9760a; }
.tag-later { background: var(--brand-50); color: var(--brand); }
body.study .tag-soon { background: rgba(224,86,86,.18); }
body.study .tag-week { background: rgba(245,158,11,.18); }
/* Toast */
.toast {
position: fixed;
bottom: 24px; left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--ink);
color: #fff;
padding: 12px 20px;
border-radius: var(--r-md);
font-size: .88rem;
font-weight: 500;
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity .25s, transform .25s;
z-index: 50;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* Responsive */
@media (max-width: 960px) {
.app { grid-template-columns: 1fr; }
.sidebar {
position: static; height: auto; flex-direction: row;
align-items: center; flex-wrap: wrap; gap: 6px;
}
.sidebar .brand { padding: 6px 8px; }
.nav { flex-direction: row; flex-wrap: wrap; }
.nav-foot { margin: 0; border: none; padding: 0; }
.grid { grid-template-columns: 1fr; }
.two-col { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.main { padding: 18px 16px 40px; }
.topbar { flex-direction: column; align-items: flex-start; }
.page-title { font-size: 1.35rem; }
.courses { grid-template-columns: 1fr; }
.block-head { flex-direction: column; align-items: flex-start; }
.stat-list { grid-template-columns: 1fr 1fr; }
.streak-week { gap: 5px; }
.dot { width: 30px; height: 30px; }
}(function () {
"use strict";
/* ---------- Data (fictional) ---------- */
var courses = [
{
id: "c1", title: "Designing Calm Interfaces", cat: "UX Design",
inst: "Priya Raman", thumb: "🎨", grad: ["#5b5bd6", "#8b8bf0"],
diff: "intermediate", done: 14, total: 22, status: "progress", time: "1h 10m left",
},
{
id: "c2", title: "JavaScript Patterns in Depth", cat: "Engineering",
inst: "Dario Voss", thumb: "⚡", grad: ["#f59e0b", "#fb923c"],
diff: "advanced", done: 9, total: 18, status: "progress", time: "2h 40m left",
},
{
id: "c3", title: "Intro to Data Storytelling", cat: "Analytics",
inst: "Lena Hoffmann", thumb: "📊", grad: ["#13b981", "#34d399"],
diff: "beginner", done: 6, total: 12, status: "progress", time: "55m left",
},
{
id: "c4", title: "Mindful Productivity", cat: "Wellbeing",
inst: "Theo Almeida", thumb: "🌿", grad: ["#0ea5e9", "#38bdf8"],
diff: "beginner", done: 3, total: 10, status: "progress", time: "1h 25m left",
},
{
id: "c5", title: "Color Theory Foundations", cat: "UX Design",
inst: "Priya Raman", thumb: "🖌️", grad: ["#ec4899", "#f472b6"],
diff: "beginner", done: 16, total: 16, status: "completed", time: "Finished Apr 28",
},
{
id: "c6", title: "CSS Grid & Flexbox Mastery", cat: "Engineering",
inst: "Dario Voss", thumb: "▦", grad: ["#5b5bd6", "#7c7cf0"],
diff: "intermediate", done: 20, total: 20, status: "completed", time: "Finished May 12",
},
{
id: "c7", title: "Public Speaking Essentials", cat: "Communication",
inst: "Amara Bello", thumb: "🎤", grad: ["#f43f5e", "#fb7185"],
diff: "beginner", done: 9, total: 9, status: "completed", time: "Finished May 30",
},
];
var certificates = [
{ title: "Color Theory Foundations", date: "Apr 28, 2026", id: "LUM-7741" },
{ title: "CSS Grid & Flexbox Mastery", date: "May 12, 2026", id: "LUM-8123" },
{ title: "Public Speaking Essentials", date: "May 30, 2026", id: "LUM-8540" },
{ title: "SQL for Beginners", date: "Mar 09, 2026", id: "LUM-6602" },
];
var deadlines = [
{ day: "17", mon: "Jun", title: "Quiz 3 — JS Patterns", course: "JavaScript Patterns in Depth", tag: "soon", label: "Tomorrow" },
{ day: "19", mon: "Jun", title: "Project: Dashboard redesign", course: "Designing Calm Interfaces", tag: "soon", label: "3 days" },
{ day: "24", mon: "Jun", title: "Peer review submission", course: "Data Storytelling", tag: "week", label: "Next week" },
{ day: "02", mon: "Jul", title: "Final reflection essay", course: "Mindful Productivity", tag: "later", label: "2 weeks" },
];
var CIRC = 2 * Math.PI * 20; // 125.6 for r=20
/* ---------- Helpers ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2400);
}
function diffPill(d) {
var label = d.charAt(0).toUpperCase() + d.slice(1);
return '<span class="diff-pill diff-' + d + '">' + label + "</span>";
}
/* ---------- Render courses ---------- */
var coursesEl = document.getElementById("courses");
var currentTab = "progress";
function renderCourses() {
var list = courses.filter(function (c) { return c.status === currentTab; });
coursesEl.innerHTML = list.map(function (c) {
var pct = Math.round((c.done / c.total) * 100);
var ringDone = c.status === "completed";
var ring =
'<div class="course-ring">' +
'<svg viewBox="0 0 46 46" width="46" height="46">' +
'<circle class="cr-track" cx="23" cy="23" r="20"/>' +
'<circle class="cr-prog' + (ringDone ? " done" : "") + '" cx="23" cy="23" r="20" ' +
'style="stroke-dashoffset:' + CIRC + '" data-pct="' + pct + '"/>' +
"</svg>" +
"<span>" + pct + "%</span>" +
"</div>";
var foot = c.status === "completed"
? '<span class="completed-badge">✓ Completed</span>' +
'<button class="btn btn-ghost btn-sm" data-review="' + c.id + '">Review</button>'
: '<button class="btn btn-brand" data-resume="' + c.id + '">▶ Resume</button>';
return (
'<article class="course">' +
'<div class="course-top">' +
'<div class="course-thumb" style="background:linear-gradient(135deg,' + c.grad[0] + "," + c.grad[1] + ')">' + c.thumb + "</div>" +
'<div class="course-info">' +
'<p class="course-cat">' + c.cat + "</p>" +
'<h3 class="course-title">' + c.title + "</h3>" +
'<p class="course-inst">with ' + c.inst + "</p>" +
"</div>" +
ring +
"</div>" +
'<div class="course-meta">' +
diffPill(c.diff) +
'<span class="lessons">' + c.done + " / " + c.total + " lessons · " + c.time + "</span>" +
"</div>" +
'<div class="course-foot">' + foot + "</div>" +
"</article>"
);
}).join("");
// animate rings after paint
requestAnimationFrame(function () {
coursesEl.querySelectorAll(".cr-prog").forEach(function (r) {
var pct = parseFloat(r.getAttribute("data-pct"));
r.style.strokeDashoffset = CIRC - (CIRC * pct) / 100;
});
});
}
/* Tabs */
document.querySelectorAll(".tab").forEach(function (tab) {
tab.addEventListener("click", function () {
if (tab.dataset.tab === currentTab) return;
document.querySelectorAll(".tab").forEach(function (t) {
t.classList.remove("is-active");
t.setAttribute("aria-selected", "false");
});
tab.classList.add("is-active");
tab.setAttribute("aria-selected", "true");
currentTab = tab.dataset.tab;
renderCourses();
});
});
/* Course buttons (delegated) */
coursesEl.addEventListener("click", function (e) {
var resume = e.target.closest("[data-resume]");
var review = e.target.closest("[data-review]");
if (resume) {
var c1 = courses.find(function (c) { return c.id === resume.dataset.resume; });
toast("Resuming “" + c1.title + "” — lesson " + (c1.done + 1));
} else if (review) {
var c2 = courses.find(function (c) { return c.id === review.dataset.review; });
toast("Opening review for “" + c2.title + "”");
}
});
/* ---------- Certificates ---------- */
document.getElementById("certList").innerHTML = certificates.map(function (c) {
return (
'<li class="cert">' +
'<span class="cert-seal" aria-hidden="true">🏅</span>' +
'<div class="cert-body">' +
"<strong>" + c.title + "</strong>" +
"<small>Issued " + c.date + " · ID " + c.id + "</small>" +
"</div>" +
'<button class="cert-dl" data-cert="' + c.id + '">↓ PDF</button>' +
"</li>"
);
}).join("");
document.getElementById("certList").addEventListener("click", function (e) {
var b = e.target.closest("[data-cert]");
if (b) toast("Preparing certificate " + b.dataset.cert + " for download…");
});
/* ---------- Deadlines ---------- */
document.getElementById("deadlineList").innerHTML = deadlines.map(function (d) {
return (
'<li class="deadline">' +
'<div class="deadline-date"><span class="d-day">' + d.day + '</span><span class="d-mon">' + d.mon + "</span></div>" +
'<div class="deadline-body"><strong>' + d.title + "</strong><small>" + d.course + "</small></div>" +
'<span class="deadline-tag tag-' + d.tag + '">' + d.label + "</span>" +
"</li>"
);
}).join("");
/* ---------- Streak + XP ---------- */
var streakNum = document.getElementById("streakNum");
var streakBtn = document.getElementById("streakBtn");
var flame = document.getElementById("flame");
var todayDot = document.getElementById("todayDot");
var xpNum = document.getElementById("xpNum");
var xpFill = document.getElementById("xpFill");
var loggedToday = false;
var xp = 2480;
streakBtn.addEventListener("click", function () {
if (loggedToday) return;
loggedToday = true;
var streak = parseInt(streakNum.textContent, 10) + 1;
streakNum.textContent = streak;
flame.classList.add("pop");
setTimeout(function () { flame.classList.remove("pop"); }, 600);
todayDot.classList.add("done", "bump");
setTimeout(function () { todayDot.classList.remove("bump"); }, 450);
// bump each day dot in a ripple
var dots = document.querySelectorAll("#streakWeek .dot");
dots.forEach(function (d, i) {
setTimeout(function () {
d.classList.add("bump");
setTimeout(function () { d.classList.remove("bump"); }, 450);
}, i * 70);
});
xp += 50;
var pct = Math.min(100, (xp / 3000) * 100);
xpNum.textContent = xp.toLocaleString();
xpFill.style.width = pct + "%";
xpFill.parentElement.setAttribute("aria-valuenow", String(xp));
streakBtn.textContent = "✓ Logged";
streakBtn.classList.add("is-done");
toast("Streak extended to " + streak + " days! +50 XP 🔥");
});
/* ---------- Weekly goal ring ---------- */
var goalRing = document.getElementById("goalRing");
var GOAL_CIRC = 2 * Math.PI * 52; // 326.7
var goalPctVal = 73;
requestAnimationFrame(function () {
goalRing.style.strokeDashoffset = GOAL_CIRC - (GOAL_CIRC * goalPctVal) / 100;
});
/* ---------- Study mode toggle ---------- */
var studyToggle = document.getElementById("studyToggle");
studyToggle.addEventListener("click", function () {
var on = document.body.classList.toggle("study");
studyToggle.setAttribute("aria-pressed", String(on));
studyToggle.textContent = on ? "☀️ Light mode" : "🌙 Study mode";
toast(on ? "Study mode on — easy on the eyes" : "Light mode on");
});
/* ---------- Init ---------- */
renderCourses();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LMS — Learner Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar" aria-label="Primary navigation">
<div class="brand">
<span class="brand-mark" aria-hidden="true">◆</span>
<span class="brand-name">Lumora</span>
</div>
<nav class="nav">
<a href="#" class="nav-item is-active"><span class="ni-ico" aria-hidden="true">▦</span>Dashboard</a>
<a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">▤</span>My Courses</a>
<a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">◷</span>Schedule</a>
<a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">★</span>Achievements</a>
<a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">⚲</span>Browse</a>
</nav>
<div class="nav-foot">
<a href="#" class="nav-item"><span class="ni-ico" aria-hidden="true">⚙</span>Settings</a>
</div>
</aside>
<!-- Main -->
<main class="main">
<header class="topbar">
<div>
<p class="greeting">Good evening, Maya</p>
<h1 class="page-title">Your learning dashboard</h1>
</div>
<div class="topbar-actions">
<button class="btn btn-ghost" id="studyToggle" aria-pressed="false">🌙 Study mode</button>
<div class="avatar" title="Maya Okonkwo" aria-label="Maya Okonkwo">MO</div>
</div>
</header>
<section class="grid">
<!-- Streak / XP widget -->
<div class="card streak-card">
<div class="streak-top">
<div>
<p class="card-eyebrow">Daily streak</p>
<p class="streak-count"><span id="streakNum">17</span> <span class="streak-unit">days</span> <span class="flame" id="flame" aria-hidden="true">🔥</span></p>
</div>
<button class="btn btn-sm btn-accent" id="streakBtn">Log today</button>
</div>
<ul class="streak-week" id="streakWeek" aria-label="This week's activity">
<li><span class="dot done">M</span></li>
<li><span class="dot done">T</span></li>
<li><span class="dot done">W</span></li>
<li><span class="dot done">T</span></li>
<li><span class="dot done">F</span></li>
<li><span class="dot today" id="todayDot">S</span></li>
<li><span class="dot">S</span></li>
</ul>
<div class="xp-row">
<div class="xp-meta">
<span class="lvl-pill">Lvl 8</span>
<span class="xp-text"><strong id="xpNum">2,480</strong> / 3,000 XP</span>
</div>
<div class="xp-bar" role="progressbar" aria-valuemin="0" aria-valuemax="3000" aria-valuenow="2480">
<span class="xp-fill" id="xpFill" style="width:82.6%"></span>
</div>
</div>
</div>
<!-- Weekly goal ring -->
<div class="card goal-card">
<p class="card-eyebrow">Weekly goal</p>
<div class="goal-ring">
<svg viewBox="0 0 120 120" width="120" height="120" aria-hidden="true">
<circle class="ring-track" cx="60" cy="60" r="52" />
<circle class="ring-prog" id="goalRing" cx="60" cy="60" r="52" />
</svg>
<div class="goal-center">
<strong id="goalPct">73%</strong>
<small>5.1 / 7h</small>
</div>
</div>
<p class="goal-note">Keep going — <strong>1.9h</strong> left to hit your weekly target.</p>
</div>
<!-- Quick stats -->
<div class="card stat-card">
<p class="card-eyebrow">At a glance</p>
<ul class="stat-list">
<li><span class="stat-num">4</span><span class="stat-lbl">In progress</span></li>
<li><span class="stat-num">11</span><span class="stat-lbl">Completed</span></li>
<li><span class="stat-num">6</span><span class="stat-lbl">Certificates</span></li>
<li><span class="stat-num">128</span><span class="stat-lbl">Lessons done</span></li>
</ul>
</div>
</section>
<!-- Continue learning -->
<section class="block">
<div class="block-head">
<h2>Continue learning</h2>
<div class="tabs" role="tablist" aria-label="Course filter">
<button class="tab is-active" role="tab" aria-selected="true" data-tab="progress">In progress</button>
<button class="tab" role="tab" aria-selected="false" data-tab="completed">Completed</button>
</div>
</div>
<div class="courses" id="courses"><!-- injected by JS --></div>
</section>
<div class="two-col">
<!-- Certificates -->
<section class="block">
<div class="block-head"><h2>Earned certificates</h2></div>
<ul class="cert-list" id="certList"><!-- injected --></ul>
</section>
<!-- Deadlines -->
<section class="block">
<div class="block-head"><h2>Upcoming deadlines</h2></div>
<ul class="deadline-list" id="deadlineList"><!-- injected --></ul>
</section>
</div>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Learner Dashboard
A calm, focused home screen for an e-learning platform. The layout pairs a sticky sidebar with a three-card header row: a streak and XP widget, a circular weekly-goal tracker, and an at-a-glance stats card. Below, continue-learning cards present each course with a colorful thumbnail, instructor, difficulty pill, lesson count, and a mini progress ring that animates into place on load.
Interactions are all vanilla JavaScript. The In-progress / Completed tabs re-render the course grid in place, with completed courses swapping their resume button for a finished badge and review action. Pressing Log today extends the streak, ripples the weekday dots with a bounce, pops the flame, and adds XP to the level bar. Resume, review, and certificate buttons fire a lightweight toast, and a study-mode toggle flips the whole dashboard into a soft dark theme.
Everything is self-contained, keyboard-usable, and responsive down to ~360px, where the sidebar collapses to a top bar and cards stack into a single column.
Illustrative UI only — fictional courses, not a real learning platform.