Coach — Client Dashboard
An energetic dark client dashboard for a fitness coaching app: a hero card for todays workout with a start/complete toggle, a swipeable weekly plan strip, a live progress mini-chart, next-session countdown, threaded coach messages with quick replies, plus animated streak and goal-progress widgets. Electric-lime CTAs glow on hover, rings and bars fill on load, and every control is keyboard-reachable with AA contrast.
MCP
Code
:root {
--bg: #0f1115;
--surface: #171a21;
--surface-2: #1f232c;
--lime: #c2f542;
--lime-d: #a3d62f;
--orange: #ff6b35;
--ink: #f2f4f0;
--muted: #9aa0a6;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--glow: 0 0 0 1px rgba(194, 245, 66, 0.35), 0 10px 30px -8px rgba(194, 245, 66, 0.45);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 600px at 80% -10%, rgba(194, 245, 66, 0.10), transparent 60%),
radial-gradient(900px 500px at -10% 20%, rgba(255, 107, 53, 0.08), transparent 55%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
}
.app {
max-width: 1180px;
margin: 0 auto;
padding: 20px 20px 64px;
}
/* focus */
:focus-visible {
outline: 3px solid var(--lime);
outline-offset: 2px;
border-radius: 6px;
}
.eyebrow {
margin: 0 0 4px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--lime);
}
.muted { color: var(--muted); font-size: 13px; }
/* ============ TOP BAR ============ */
.topbar {
display: flex;
align-items: center;
gap: 18px;
padding: 10px 4px 22px;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
display: grid;
place-items: center;
width: 34px; height: 34px;
border-radius: 10px;
background: var(--lime);
color: #0b0d10;
font-size: 14px;
box-shadow: var(--glow);
}
.brand-name {
font-weight: 800;
letter-spacing: 0.08em;
font-size: 17px;
}
.brand-name span { color: var(--lime); }
.topnav {
display: flex;
gap: 6px;
margin-left: 8px;
}
.topnav a {
padding: 8px 14px;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
color: var(--muted);
text-decoration: none;
transition: color .18s, background .18s;
}
.topnav a:hover { color: var(--ink); background: var(--surface); }
.topnav a.active { color: #0b0d10; background: var(--lime); }
.topbar-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
.icon-btn {
position: relative;
width: 40px; height: 40px;
border: 1px solid var(--line);
background: var(--surface);
border-radius: 12px;
color: var(--ink);
font-size: 16px;
cursor: pointer;
transition: border-color .18s, transform .1s;
}
.icon-btn:hover { border-color: var(--line-2); }
.icon-btn:active { transform: scale(.94); }
.icon-btn .dot {
position: absolute;
top: 9px; right: 9px;
width: 8px; height: 8px;
border-radius: 50%;
background: var(--orange);
box-shadow: 0 0 0 3px var(--surface);
}
.avatar {
display: grid;
place-items: center;
width: 40px; height: 40px;
border-radius: 12px;
font-weight: 700;
font-size: 13px;
color: #0b0d10;
background: linear-gradient(135deg, var(--lime), var(--lime-d));
}
.avatar-lg { width: 46px; height: 46px; border-radius: 14px; font-size: 15px; }
/* ============ GREETING ============ */
.greet {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
margin-bottom: 22px;
flex-wrap: wrap;
}
.greet h1 {
margin: 0;
font-size: clamp(28px, 5vw, 44px);
font-weight: 800;
letter-spacing: -0.02em;
text-transform: uppercase;
}
.greet h1 span { color: var(--lime); }
.greet-stats { display: flex; gap: 10px; }
.mini {
display: flex;
flex-direction: column;
padding: 10px 16px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
}
.mini b { font-size: 20px; font-weight: 800; }
.mini span { font-size: 11px; color: var(--muted); letter-spacing: 0.04em; }
/* ============ GRID ============ */
.grid {
display: grid;
grid-template-columns: 1.55fr 1fr;
gap: 18px;
align-items: start;
}
.col-main, .col-side { display: flex; flex-direction: column; gap: 18px; }
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 18px;
transition: border-color .2s, transform .2s;
}
.card:hover { border-color: var(--line-2); }
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.card-head h3 {
margin: 0;
font-size: 15px;
font-weight: 800;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.badge {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
padding: 4px 10px;
border-radius: 999px;
background: var(--surface-2);
color: var(--muted);
border: 1px solid var(--line);
}
.badge-online { color: var(--lime); border-color: rgba(194, 245, 66, 0.3); }
/* ============ WORKOUT CARD ============ */
.workout { padding: 0; overflow: hidden; }
.workout-media {
position: relative;
height: 128px;
background:
linear-gradient(120deg, rgba(194, 245, 66, 0.22), transparent 55%),
linear-gradient(300deg, rgba(255, 107, 53, 0.30), transparent 60%),
#101319;
display: flex;
align-items: flex-start;
padding: 14px;
}
.workout-media-grad {
position: absolute;
inset: 0;
background: repeating-linear-gradient(115deg, rgba(255,255,255,0.03) 0 2px, transparent 2px 22px);
pointer-events: none;
}
.tag {
position: relative;
z-index: 1;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 6px 12px;
border-radius: 999px;
background: rgba(11, 13, 16, 0.55);
border: 1px solid var(--line-2);
backdrop-filter: blur(6px);
}
.tag-live.active {
background: var(--lime);
color: #0b0d10;
border-color: var(--lime);
animation: pulse 1.6s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(194, 245, 66, 0.45); }
50% { box-shadow: 0 0 0 8px rgba(194, 245, 66, 0); }
}
.workout-body { padding: 18px; }
.workout-head {
display: flex;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.workout-head h2 {
margin: 0;
font-size: 22px;
font-weight: 800;
letter-spacing: -0.01em;
}
.workout-meta { display: flex; gap: 6px; flex-wrap: wrap; align-content: flex-start; }
.chip {
font-size: 12px;
font-weight: 600;
padding: 5px 10px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--line);
white-space: nowrap;
}
.exercises { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.exercises li {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid transparent;
transition: border-color .18s, background .18s;
}
.exercises li:hover { border-color: var(--line-2); }
.exercises li.done { opacity: 0.62; }
.exercises li.done .ex-info b { text-decoration: line-through; }
.ex-check {
flex: none;
width: 24px; height: 24px;
border-radius: 8px;
border: 2px solid var(--line-2);
background: transparent;
cursor: pointer;
position: relative;
transition: background .15s, border-color .15s;
}
.ex-check:hover { border-color: var(--lime); }
.ex-check.on {
background: var(--lime);
border-color: var(--lime);
}
.ex-check.on::after {
content: "✓";
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #0b0d10;
font-size: 14px;
font-weight: 800;
}
.ex-info { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.ex-info b { font-size: 14px; font-weight: 600; }
.ex-info span { font-size: 12px; color: var(--muted); }
.ex-tag {
font-size: 11px;
font-weight: 700;
color: var(--muted);
padding: 3px 9px;
border-radius: 999px;
border: 1px solid var(--line);
}
.workout-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.progress-inline { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 160px; }
.pbar {
flex: 1;
height: 8px;
border-radius: 999px;
background: var(--surface-2);
overflow: hidden;
}
.pbar i {
display: block;
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--lime), var(--lime-d));
transition: width .4s cubic-bezier(.4,0,.2,1);
}
.progress-inline span { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* ============ BUTTONS ============ */
.btn {
font-family: inherit;
font-weight: 700;
font-size: 14px;
letter-spacing: 0.03em;
padding: 12px 22px;
border-radius: var(--r-md);
border: 1px solid transparent;
cursor: pointer;
transition: transform .1s, box-shadow .2s, background .2s, color .2s;
}
.btn:active { transform: translateY(1px) scale(.99); }
.btn-lime {
background: var(--lime);
color: #0b0d10;
box-shadow: 0 8px 22px -10px rgba(194, 245, 66, 0.7);
}
.btn-lime:hover {
background: var(--lime-d);
box-shadow: var(--glow);
}
.btn-lime.running { background: var(--orange); color: var(--white); box-shadow: 0 8px 22px -10px rgba(255,107,53,.7); }
.btn-lime.done { background: var(--surface-2); color: var(--lime); box-shadow: none; border-color: rgba(194,245,66,.35); }
.btn-ghost {
background: transparent;
color: var(--ink);
border: 1px solid var(--line-2);
width: 100%;
}
.btn-ghost:hover { border-color: var(--lime); color: var(--lime); }
/* ============ WEEK STRIP ============ */
.week-strip {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
margin-bottom: 14px;
}
.day {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 12px 4px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid var(--line);
cursor: pointer;
color: var(--ink);
font-family: inherit;
transition: border-color .18s, transform .12s, background .18s;
}
.day:hover { border-color: var(--line-2); transform: translateY(-2px); }
.day .dow { font-size: 10px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted); text-transform: uppercase; }
.day .dnum { font-size: 16px; font-weight: 800; }
.day .dstate {
width: 18px; height: 18px;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 10px;
background: transparent;
border: 1.5px solid var(--line-2);
}
.day.done .dstate { background: var(--lime); border-color: var(--lime); color: #0b0d10; }
.day.rest .dstate { border-style: dashed; }
.day.active {
background: linear-gradient(180deg, rgba(194,245,66,.14), var(--surface-2));
border-color: var(--lime);
}
.day.active .dow { color: var(--lime); }
.day.today .dnum::after {
content: "";
display: block;
width: 5px; height: 5px;
margin: 3px auto 0;
border-radius: 50%;
background: var(--orange);
}
.day-preview {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 16px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid var(--line);
flex-wrap: wrap;
}
.day-preview h4 { margin: 0; font-size: 15px; font-weight: 700; }
.day-preview .dp-meta { display: flex; gap: 6px; flex-wrap: wrap; }
/* ============ COACH ============ */
.coach-row { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.coach-row b { display: block; font-size: 14px; }
.thread { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.msg {
max-width: 88%;
padding: 10px 14px;
border-radius: 14px;
font-size: 13.5px;
}
.msg p { margin: 0; }
.msg time { display: block; margin-top: 4px; font-size: 10px; color: var(--muted); }
.msg.in { align-self: flex-start; background: var(--surface-2); border: 1px solid var(--line); border-bottom-left-radius: 4px; }
.msg.out { align-self: flex-end; background: var(--lime); color: #0b0d10; border-bottom-right-radius: 4px; }
.msg.out time { color: rgba(11,13,16,.6); }
.msg.pop { animation: pop .25s ease; }
@keyframes pop { from { opacity: 0; transform: translateY(8px) scale(.96); } to { opacity: 1; transform: none; } }
.quick-replies { display: flex; gap: 8px; flex-wrap: wrap; }
.qr {
font-family: inherit;
font-size: 12.5px;
font-weight: 600;
padding: 8px 14px;
border-radius: 999px;
background: transparent;
border: 1px solid var(--line-2);
color: var(--ink);
cursor: pointer;
transition: background .16s, color .16s, border-color .16s;
}
.qr:hover { background: var(--lime); color: #0b0d10; border-color: var(--lime); }
/* ============ CHART ============ */
.chart-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.chart-top b { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; }
.delta { font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: 999px; }
.delta.up { color: var(--lime); background: rgba(194,245,66,.12); }
.delta.down { color: var(--orange); background: rgba(255,107,53,.14); }
.seg {
display: inline-flex;
padding: 3px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--line);
}
.seg-btn {
font-family: inherit;
font-size: 12px;
font-weight: 600;
padding: 5px 12px;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--muted);
cursor: pointer;
transition: background .16s, color .16s;
}
.seg-btn.active { background: var(--lime); color: #0b0d10; }
.chart-wrap { margin-top: 6px; }
#chart {
width: 100%;
height: 120px;
display: block;
overflow: visible;
}
#chartLine {
stroke: var(--lime);
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 6px rgba(194,245,66,.5));
transition: all .5s ease;
}
#chartFill { fill: url(#g); transition: all .5s ease; }
.chart-x {
display: grid;
grid-template-columns: repeat(7, 1fr);
margin-top: 6px;
font-size: 10px;
color: var(--muted);
text-align: center;
}
/* ============ NEXT SESSION ============ */
.next-body { display: flex; flex-direction: column; gap: 14px; }
.next-when b { font-size: 16px; font-weight: 700; }
.next-when span { display: block; }
.countdown { display: flex; gap: 8px; }
.cd {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 4px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid var(--line);
}
.cd b { font-size: 24px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--lime); }
.cd span { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
/* ============ SIDE DUO ============ */
.side-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.streak, .goal { display: flex; flex-direction: column; }
.streak-num { display: flex; align-items: baseline; gap: 6px; margin: 2px 0 4px; }
.streak-num b { font-size: 40px; font-weight: 800; color: var(--orange); letter-spacing: -0.03em; }
.streak-num span { font-size: 13px; color: var(--muted); }
.streak-dots { display: flex; gap: 5px; margin-top: 12px; flex-wrap: wrap; }
.streak-dots i {
width: 12px; height: 12px;
border-radius: 4px;
background: var(--surface-2);
border: 1px solid var(--line);
}
.streak-dots i.on { background: var(--orange); border-color: var(--orange); box-shadow: 0 0 8px rgba(255,107,53,.5); }
.ring-wrap { position: relative; width: 120px; margin: 6px auto 8px; }
.ring { width: 120px; height: 120px; transform: rotate(-90deg); }
.ring-bg { fill: none; stroke: var(--surface-2); stroke-width: 10; }
.ring-fg {
fill: none;
stroke: var(--lime);
stroke-width: 10;
stroke-linecap: round;
stroke-dasharray: 327;
stroke-dashoffset: 327;
filter: drop-shadow(0 0 6px rgba(194,245,66,.5));
transition: stroke-dashoffset 1.1s cubic-bezier(.4,0,.2,1);
}
.ring-center {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.ring-center b { font-size: 22px; font-weight: 800; }
.ring-center span { font-size: 11px; color: var(--muted); }
.goal { text-align: center; }
.goal .eyebrow { text-align: left; }
/* ============ TOAST ============ */
.toast-wrap {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 8px;
z-index: 50;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 18px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
font-size: 13.5px;
font-weight: 600;
box-shadow: 0 14px 40px -12px rgba(0,0,0,.6);
animation: toastIn .3s ease, toastOut .3s ease 2.6s forwards;
}
.toast b { color: var(--lime); }
@keyframes toastIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(14px); } }
/* ============ RESPONSIVE ============ */
@media (max-width: 900px) {
.grid { grid-template-columns: 1fr; }
.topnav { display: none; }
}
@media (max-width: 520px) {
.app { padding: 14px 12px 48px; }
.greet-stats { width: 100%; }
.mini { flex: 1; }
.greet h1 { font-size: 30px; }
.workout-head h2 { font-size: 19px; }
.side-duo { grid-template-columns: 1fr; }
.week-strip { grid-template-columns: repeat(7, 1fr); gap: 5px; }
.day { padding: 9px 2px; }
.day .dnum { font-size: 14px; }
.workout-foot .btn-lime { width: 100%; }
.chart-top b { font-size: 24px; }
.brand-name { font-size: 15px; }
}
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastWrap = document.getElementById("toastWrap");
function toast(msg) {
var t = document.createElement("div");
t.className = "toast";
t.innerHTML = msg;
toastWrap.appendChild(t);
setTimeout(function () {
if (t.parentNode) t.parentNode.removeChild(t);
}, 3000);
}
/* ---------- today's workout: exercise checks + progress ---------- */
var exList = document.getElementById("exList");
var exBar = document.getElementById("exBar");
var exCount = document.getElementById("exCount");
var statMin = document.getElementById("statMin");
var streakNum = document.getElementById("streakNum");
var checks = Array.prototype.slice.call(exList.querySelectorAll(".ex-check"));
var total = checks.length;
function refreshProgress() {
var done = 0;
checks.forEach(function (c) {
if (c.classList.contains("on")) done++;
});
exBar.style.width = (done / total) * 100 + "%";
exCount.textContent = done + " / " + total + " done";
return done;
}
checks.forEach(function (c) {
c.addEventListener("click", function () {
c.classList.toggle("on");
c.closest("li").classList.toggle("done", c.classList.contains("on"));
var done = refreshProgress();
if (done === total) {
toast("Every set logged — <b>beast mode</b> 💪");
finishWorkout();
}
});
});
/* ---------- start / running / complete flow ---------- */
var startBtn = document.getElementById("startBtn");
var workoutTag = document.getElementById("workoutTag");
var state = "idle"; // idle -> running -> done
var timerId = null;
var elapsed = 0;
var completed = false;
function fmtClock(s) {
var m = Math.floor(s / 60);
var sec = s % 60;
return (m < 10 ? "0" : "") + m + ":" + (sec < 10 ? "0" : "") + sec;
}
startBtn.addEventListener("click", function () {
if (state === "idle") {
state = "running";
elapsed = 0;
startBtn.classList.add("running");
startBtn.classList.remove("done");
workoutTag.textContent = "● Live · in progress";
workoutTag.classList.add("active");
toast("Session started — <b>let's go!</b>");
timerId = setInterval(function () {
elapsed++;
startBtn.textContent = "Complete · " + fmtClock(elapsed);
statMin.textContent = Math.max(1, Math.floor(elapsed / 60) + 1);
}, 1000);
startBtn.textContent = "Complete · 00:00";
} else if (state === "running") {
finishWorkout();
}
});
function finishWorkout() {
if (completed) return;
completed = true;
state = "done";
if (timerId) clearInterval(timerId);
startBtn.classList.remove("running");
startBtn.classList.add("done");
startBtn.textContent = "✓ Completed";
startBtn.disabled = false;
workoutTag.textContent = "Completed today";
workoutTag.classList.remove("active");
// mark all exercises done
checks.forEach(function (c) {
c.classList.add("on");
c.closest("li").classList.add("done");
});
refreshProgress();
// bump streak
var n = parseInt(streakNum.textContent, 10) + 1;
streakNum.textContent = n;
bumpGoal();
toast("Workout complete — streak now <b>" + n + " days</b> 🔥");
}
/* ---------- weekly plan strip ---------- */
var weekStrip = document.getElementById("weekStrip");
var dayPreview = document.getElementById("dayPreview");
var days = [
{ dow: "Mon", num: 9, state: "done", focus: "Lower — Strength", meta: ["⏱ 58 min", "5 moves", "Done"] },
{ dow: "Tue", num: 10, state: "done", focus: "Conditioning + Core", meta: ["⏱ 35 min", "HIIT", "Done"] },
{ dow: "Wed", num: 11, state: "today", focus: "Upper Body — Hypertrophy", meta: ["⏱ 52 min", "6 moves", "Today"] },
{ dow: "Thu", num: 12, state: "rest", focus: "Active Recovery — Mobility", meta: ["⏱ 20 min", "Mobility"] },
{ dow: "Fri", num: 13, state: "planned", focus: "Leg Day + Mobility", meta: ["⏱ 60 min", "6 moves", "Studio B"] },
{ dow: "Sat", num: 14, state: "planned", focus: "Full Body Circuit", meta: ["⏱ 45 min", "Circuit"] },
{ dow: "Sun", num: 15, state: "rest", focus: "Rest Day — Walk 8k steps", meta: ["Recovery"] },
];
function selectDay(i) {
Array.prototype.forEach.call(weekStrip.children, function (el, idx) {
var on = idx === i;
el.classList.toggle("active", on);
el.setAttribute("aria-selected", on ? "true" : "false");
el.tabIndex = on ? 0 : -1;
});
var d = days[i];
var chips = d.meta
.map(function (m) {
return '<span class="chip">' + m + "</span>";
})
.join("");
dayPreview.innerHTML =
'<div><p class="eyebrow">' +
d.dow +
" " +
d.num +
'</p><h4>' +
d.focus +
"</h4></div>" +
'<div class="dp-meta">' +
chips +
"</div>";
}
days.forEach(function (d, i) {
var btn = document.createElement("button");
btn.className = "day " + d.state;
if (d.state === "today") btn.classList.add("today");
btn.setAttribute("role", "tab");
btn.tabIndex = -1;
var mark = d.state === "done" ? "✓" : d.state === "rest" ? "z" : "";
btn.innerHTML =
'<span class="dow">' +
d.dow +
'</span><span class="dnum">' +
d.num +
'</span><span class="dstate">' +
mark +
"</span>";
btn.addEventListener("click", function () {
selectDay(i);
});
btn.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
e.preventDefault();
var next = e.key === "ArrowRight" ? (i + 1) % days.length : (i - 1 + days.length) % days.length;
selectDay(next);
weekStrip.children[next].focus();
}
});
weekStrip.appendChild(btn);
});
selectDay(2); // Wednesday today
/* ---------- progress mini-chart ---------- */
var metricData = {
volume: { pts: [8.4, 9.1, 8.8, 10.2, 11.5, 10.9, 12.1], val: "12,480", unit: "kg lifted · this week", delta: "▲ 8.2%", up: true },
weight: { pts: [72.4, 72.1, 71.8, 71.9, 71.4, 71.2, 70.9], val: "70.9 kg", unit: "body weight · trending down", delta: "▼ 1.5 kg", up: false },
reps: { pts: [180, 210, 195, 240, 265, 250, 288], val: "288", unit: "total reps · this week", delta: "▲ 11%", up: true },
};
var chartLine = document.getElementById("chartLine");
var chartFill = document.getElementById("chartFill");
var chartVal = document.getElementById("chartVal");
var chartUnit = document.getElementById("chartUnit");
var chartDelta = document.getElementById("chartDelta");
var svg = document.getElementById("chart");
// inject gradient def
var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
defs.innerHTML =
'<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">' +
'<stop offset="0%" stop-color="rgba(194,245,66,0.30)"/>' +
'<stop offset="100%" stop-color="rgba(194,245,66,0)"/></linearGradient>';
svg.insertBefore(defs, svg.firstChild);
function drawChart(metric) {
var d = metricData[metric];
var pts = d.pts;
var min = Math.min.apply(null, pts);
var max = Math.max.apply(null, pts);
var range = max - min || 1;
var W = 320, H = 120, pad = 10;
var coords = pts.map(function (p, i) {
var x = (i / (pts.length - 1)) * (W - pad * 2) + pad;
var y = H - pad - ((p - min) / range) * (H - pad * 2);
return x.toFixed(1) + "," + y.toFixed(1);
});
chartLine.setAttribute("points", coords.join(" "));
chartFill.setAttribute("points", pad + "," + (H - pad) + " " + coords.join(" ") + " " + (W - pad) + "," + (H - pad));
chartVal.textContent = d.val;
chartUnit.textContent = d.unit;
chartDelta.textContent = d.delta;
chartDelta.className = "delta " + (d.up ? "up" : "down");
}
var segBtns = Array.prototype.slice.call(document.querySelectorAll(".seg-btn"));
segBtns.forEach(function (b) {
b.addEventListener("click", function () {
segBtns.forEach(function (x) {
x.classList.remove("active");
x.setAttribute("aria-selected", "false");
});
b.classList.add("active");
b.setAttribute("aria-selected", "true");
drawChart(b.dataset.metric);
});
});
drawChart("volume");
/* ---------- next-session countdown ---------- */
var cdH = document.getElementById("cdH");
var cdM = document.getElementById("cdM");
var cdS = document.getElementById("cdS");
var target = new Date();
target.setDate(target.getDate() + 1);
target.setHours(6, 30, 0, 0);
function tick() {
var diff = Math.max(0, target - new Date());
var s = Math.floor(diff / 1000);
var h = Math.floor(s / 3600);
var m = Math.floor((s % 3600) / 60);
var sec = s % 60;
cdH.textContent = (h < 10 ? "0" : "") + h;
cdM.textContent = (m < 10 ? "0" : "") + m;
cdS.textContent = (sec < 10 ? "0" : "") + sec;
}
tick();
setInterval(tick, 1000);
document.getElementById("rescheduleBtn").addEventListener("click", function () {
toast("Reschedule request sent to <b>Coach Darius</b>");
});
/* ---------- coach quick replies ---------- */
var thread = document.getElementById("thread");
var quickReplies = document.getElementById("quickReplies");
quickReplies.addEventListener("click", function (e) {
var btn = e.target.closest(".qr");
if (!btn) return;
var li = document.createElement("li");
li.className = "msg out pop";
var now = new Date();
var hh = now.getHours() % 12 || 12;
var mm = (now.getMinutes() < 10 ? "0" : "") + now.getMinutes();
var ap = now.getHours() < 12 ? "AM" : "PM";
li.innerHTML = "<p>" + btn.textContent + "</p><time>" + hh + ":" + mm + " " + ap + "</time>";
thread.appendChild(li);
btn.remove();
thread.scrollTop = thread.scrollHeight;
toast("Message sent to <b>Coach Darius</b>");
// simulated coach reply
if (!quickReplies.querySelector(".qr")) {
setTimeout(function () {
var r = document.createElement("li");
r.className = "msg in pop";
r.innerHTML = "<p>Perfect. Hydrate and crush it — I'll review your logs tonight. 👊</p><time>just now</time>";
thread.appendChild(r);
thread.scrollTop = thread.scrollHeight;
}, 1100);
}
});
/* ---------- streak dots ---------- */
var streakDots = document.getElementById("streakDots");
for (var i = 0; i < 21; i++) {
var dot = document.createElement("i");
if (i < 14) dot.className = "on";
streakDots.appendChild(dot);
}
/* ---------- goal ring ---------- */
var ringFg = document.getElementById("ringFg");
var goalPct = document.getElementById("goalPct");
var CIRC = 2 * Math.PI * 52; // ~327
var goalDone = 4, goalTotal = 5;
function setGoal(done) {
var pct = Math.min(1, done / goalTotal);
ringFg.style.strokeDashoffset = CIRC * (1 - pct);
goalPct.textContent = Math.round(pct * 100) + "%";
var span = ringFg.closest(".ring-wrap").querySelector(".ring-center span");
span.textContent = done + " / " + goalTotal;
}
function bumpGoal() {
if (goalDone < goalTotal) {
goalDone++;
setGoal(goalDone);
if (goalDone === goalTotal) toast("Weekly goal <b>smashed</b> — 5 / 5 done! 🏆");
}
}
// animate widgets on load
requestAnimationFrame(function () {
setTimeout(function () {
setGoal(goalDone);
}, 200);
});
/* ---------- notifications ---------- */
document.getElementById("bellBtn").addEventListener("click", function () {
toast("You have <b>2 new</b> plan updates from your coach");
});
refreshProgress();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Coach — Client 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">
<!-- ============ TOP BAR ============ -->
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">▲</span>
<span class="brand-name">APEX<span>COACH</span></span>
</div>
<nav class="topnav" aria-label="Primary">
<a href="#today" class="active">Today</a>
<a href="#plan">Plan</a>
<a href="#progress">Progress</a>
<a href="#coach">Coach</a>
</nav>
<div class="topbar-right">
<button class="icon-btn" id="bellBtn" aria-label="Notifications">
<span aria-hidden="true">🔔</span>
<span class="dot" aria-hidden="true"></span>
</button>
<div class="avatar" title="Maya Okonkwo" aria-label="Maya Okonkwo">MO</div>
</div>
</header>
<!-- ============ GREETING ============ -->
<section class="greet">
<div>
<p class="eyebrow">Wednesday · Push Day</p>
<h1>Let's move, <span>Maya</span></h1>
</div>
<div class="greet-stats">
<div class="mini">
<b id="statKcal">640</b><span>kcal today</span>
</div>
<div class="mini">
<b id="statMin">0</b><span>active min</span>
</div>
<div class="mini">
<b>4.8</b><span>coach rating</span>
</div>
</div>
</section>
<main class="grid">
<!-- ============ LEFT COLUMN ============ -->
<div class="col-main">
<!-- Today's workout -->
<article class="card workout" id="today">
<div class="workout-media" aria-hidden="true">
<span class="tag tag-live" id="workoutTag">Scheduled 5:30 PM</span>
<div class="workout-media-grad"></div>
</div>
<div class="workout-body">
<div class="workout-head">
<div>
<p class="eyebrow">Today's Workout</p>
<h2>Upper Body — Hypertrophy</h2>
</div>
<div class="workout-meta">
<span class="chip">⏱ 52 min</span>
<span class="chip">🔥 High</span>
<span class="chip">6 moves</span>
</div>
</div>
<ul class="exercises" id="exList">
<li>
<button class="ex-check" aria-label="Toggle Barbell Bench Press"></button>
<div class="ex-info"><b>Barbell Bench Press</b><span>4 × 8 · 60 kg</span></div>
<span class="ex-tag">Chest</span>
</li>
<li>
<button class="ex-check" aria-label="Toggle Incline Dumbbell Press"></button>
<div class="ex-info"><b>Incline Dumbbell Press</b><span>3 × 10 · 22 kg</span></div>
<span class="ex-tag">Chest</span>
</li>
<li>
<button class="ex-check" aria-label="Toggle Seated Cable Row"></button>
<div class="ex-info"><b>Seated Cable Row</b><span>4 × 12 · 45 kg</span></div>
<span class="ex-tag">Back</span>
</li>
<li>
<button class="ex-check" aria-label="Toggle Lateral Raise"></button>
<div class="ex-info"><b>Lateral Raise</b><span>3 × 15 · 8 kg</span></div>
<span class="ex-tag">Shoulders</span>
</li>
</ul>
<div class="workout-foot">
<div class="progress-inline">
<div class="pbar"><i id="exBar"></i></div>
<span id="exCount">0 / 4 done</span>
</div>
<button class="btn btn-lime" id="startBtn">Start Session</button>
</div>
</div>
</article>
<!-- Weekly plan strip -->
<article class="card" id="plan">
<div class="card-head">
<h3>Weekly Plan</h3>
<span class="muted">Week 6 of 12 · Strength Block</span>
</div>
<div class="week-strip" id="weekStrip" role="tablist" aria-label="Days of the week"></div>
<div class="day-preview" id="dayPreview" role="tabpanel"></div>
</article>
<!-- Coach messages -->
<article class="card" id="coach">
<div class="card-head">
<h3>Coach Messages</h3>
<span class="badge badge-online">● Online</span>
</div>
<div class="coach-row">
<div class="avatar avatar-lg" aria-hidden="true">DK</div>
<div>
<b>Coach Darius Kline</b>
<span class="muted">Certified S&C · replies in ~15 min</span>
</div>
</div>
<ul class="thread" id="thread">
<li class="msg in">
<p>Great tempo on Monday's squats 💪 Keep the same pace today and log your bench numbers.</p>
<time>9:02 AM</time>
</li>
<li class="msg in">
<p>If your shoulder feels tight, swap laterals for face pulls — your call.</p>
<time>9:03 AM</time>
</li>
</ul>
<div class="quick-replies" id="quickReplies">
<button class="qr">On it, Coach! 🔥</button>
<button class="qr">Shoulder feels good</button>
<button class="qr">Can we call later?</button>
</div>
</article>
</div>
<!-- ============ RIGHT COLUMN ============ -->
<aside class="col-side">
<!-- Progress mini-chart -->
<article class="card" id="progress">
<div class="card-head">
<h3>Progress</h3>
<div class="seg" id="metricSeg" role="tablist" aria-label="Metric">
<button class="seg-btn active" data-metric="volume" role="tab" aria-selected="true">Volume</button>
<button class="seg-btn" data-metric="weight" role="tab" aria-selected="false">Weight</button>
<button class="seg-btn" data-metric="reps" role="tab" aria-selected="false">Reps</button>
</div>
</div>
<div class="chart-top">
<b id="chartVal">12,480</b>
<span class="delta up" id="chartDelta">▲ 8.2%</span>
<span class="muted" id="chartUnit">kg lifted · this week</span>
</div>
<div class="chart-wrap">
<svg id="chart" viewBox="0 0 320 120" preserveAspectRatio="none" role="img" aria-label="Weekly progress chart">
<polyline id="chartLine" fill="none" points="" />
<polygon id="chartFill" points="" />
</svg>
<div class="chart-x">
<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span>
</div>
</div>
</article>
<!-- Next session -->
<article class="card next">
<div class="card-head">
<h3>Next Session</h3>
<span class="badge">Live coaching</span>
</div>
<div class="next-body">
<div class="next-when">
<b>Leg Day + Mobility</b>
<span class="muted">Tomorrow · 6:30 AM · Studio B</span>
</div>
<div class="countdown" id="countdown" aria-live="polite">
<div class="cd"><b id="cdH">00</b><span>hrs</span></div>
<div class="cd"><b id="cdM">00</b><span>min</span></div>
<div class="cd"><b id="cdS">00</b><span>sec</span></div>
</div>
<button class="btn btn-ghost" id="rescheduleBtn">Reschedule</button>
</div>
</article>
<!-- Streak + goal -->
<div class="side-duo">
<article class="card streak">
<p class="eyebrow">Current Streak</p>
<div class="streak-num"><b id="streakNum">14</b><span>days</span></div>
<p class="muted">Best: 21 days 🔥</p>
<div class="streak-dots" id="streakDots"></div>
</article>
<article class="card goal">
<p class="eyebrow">Weekly Goal</p>
<div class="ring-wrap">
<svg viewBox="0 0 120 120" class="ring" aria-hidden="true">
<circle class="ring-bg" cx="60" cy="60" r="52"></circle>
<circle class="ring-fg" id="ringFg" cx="60" cy="60" r="52"></circle>
</svg>
<div class="ring-center"><b id="goalPct">0%</b><span>4 / 5</span></div>
</div>
<p class="muted">1 workout to hit target</p>
</article>
</div>
</aside>
</main>
</div>
<div class="toast-wrap" id="toastWrap" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A motivational client home screen for a personal-training platform, built in a single self-contained HTML/CSS/JS bundle. The layout leads with a full-bleed “Today” workout card — exercise list, estimated duration, and an electric-lime Start Session button that flips to a live-timer / Mark Complete state and bumps the streak. Below it a horizontal weekly plan strip highlights the current day, marks done sessions with a check, and lets you tab through the week to preview each day’s focus.
The right rail stacks the widgets a client checks at a glance: an SVG progress mini-chart of weekly volume that redraws when you switch the Volume / Weight / Reps toggle, a next-session countdown that ticks in real time, and animated streak and goal-ring cards that fill on load. A coach-messages panel shows a short thread from your trainer with tappable quick-reply chips that append to the conversation and fire a toast.
Everything is vanilla: no frameworks, no build step. Interactions include the workout start/complete flow, day switching, chart metric toggling, the live countdown, quick replies, and a lightweight toast() helper. Controls use real <button>/aria semantics with visible focus rings, the grid collapses to a single column under 520px, and all data is realistic but clearly fictional.