Coach — Habit Tracker
An energetic dark habit-tracker board where a weekly grid pairs habits against days, tap any cell to mark it done, and watch per-habit streaks, weekly completion rings, and a live overall percentage light up in electric lime. Add your own habits on the fly, reset the week, and let the motivational stats keep you accountable one glowing dot at a time.
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;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(900px 500px at 100% -10%, rgba(194, 245, 66, 0.10), transparent 60%),
radial-gradient(700px 480px at -10% 110%, rgba(255, 107, 53, 0.10), transparent 55%),
var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrap {
max-width: 960px;
margin: 0 auto;
padding: 32px 20px 64px;
}
/* Topbar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 26px;
}
.brand { display: flex; align-items: center; gap: 14px; }
.brand-mark {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border-radius: var(--r-md);
background: linear-gradient(160deg, var(--lime), var(--lime-d));
color: #10130a;
font-size: 22px;
box-shadow: 0 8px 24px rgba(194, 245, 66, 0.28);
}
.brand-kicker {
display: block;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--lime-d);
}
.brand-text h1 {
margin: 2px 0 0;
font-size: clamp(22px, 4.5vw, 30px);
font-weight: 800;
letter-spacing: 0.01em;
text-transform: uppercase;
}
/* Buttons */
.btn {
font: inherit;
font-weight: 700;
border-radius: var(--r-sm);
border: 1px solid transparent;
padding: 11px 18px;
cursor: pointer;
transition: transform 0.12s ease, background 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:focus-visible {
outline: 3px solid rgba(194, 245, 66, 0.55);
outline-offset: 2px;
}
.btn-primary {
background: linear-gradient(160deg, var(--lime), var(--lime-d));
color: #10130a;
box-shadow: 0 8px 22px rgba(194, 245, 66, 0.30);
}
.btn-primary:hover {
box-shadow: 0 10px 30px rgba(194, 245, 66, 0.45);
transform: translateY(-1px);
}
.btn-ghost {
background: var(--surface-2);
color: var(--ink);
border-color: var(--line);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 12px;
}
.btn-ghost:hover { border-color: var(--line-2); background: #262b35; }
/* Stats */
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr) 1.4fr;
gap: 14px;
margin-bottom: 22px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.stat-value {
font-size: 30px;
font-weight: 800;
letter-spacing: 0.01em;
line-height: 1.1;
}
.stat-value--lime { color: var(--lime); }
.stat-value--orange { color: var(--orange); }
.stat-unit { font-size: 15px; font-weight: 700; opacity: 0.7; margin-left: 2px; }
.stat-sub { font-size: 12px; color: var(--muted); }
.stat-card--wide { justify-content: center; }
.stat-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.stat-head .stat-value { font-size: 26px; }
.progress {
position: relative;
height: 10px;
border-radius: 999px;
background: var(--surface-2);
overflow: hidden;
border: 1px solid var(--line);
}
.progress-fill {
position: absolute;
inset: 0;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--lime-d), var(--lime));
box-shadow: 0 0 16px rgba(194, 245, 66, 0.5);
transition: width 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Board */
.board {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 8px 10px 12px;
overflow-x: auto;
}
.grid-head,
.habit-row {
display: grid;
grid-template-columns: minmax(150px, 1.6fr) repeat(7, minmax(38px, 1fr));
align-items: center;
gap: 6px;
}
.grid-head {
padding: 12px 8px 10px;
border-bottom: 1px solid var(--line);
margin-bottom: 4px;
}
.head-habit { color: var(--muted); }
.cell-habit {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.day-head {
text-align: center;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.day-head.is-today {
color: var(--lime);
}
.habit-row {
padding: 12px 8px;
border-radius: var(--r-md);
transition: background 0.15s ease;
}
.habit-row:hover { background: rgba(255, 255, 255, 0.03); }
.habit-info {
display: flex;
align-items: center;
gap: 11px;
min-width: 0;
}
.habit-emoji {
flex: none;
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: var(--r-sm);
background: var(--surface-2);
border: 1px solid var(--line);
font-size: 18px;
}
.habit-meta { min-width: 0; }
.habit-name {
font-size: 14px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.habit-streak {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 3px;
font-size: 11px;
font-weight: 700;
color: var(--orange);
}
.habit-streak.is-zero { color: var(--muted); }
.streak-badge {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 1px 7px;
border-radius: 999px;
background: rgba(255, 107, 53, 0.14);
border: 1px solid rgba(255, 107, 53, 0.3);
}
.habit-streak.is-zero .streak-badge {
background: rgba(255, 255, 255, 0.05);
border-color: var(--line);
}
.remove-habit {
margin-left: 4px;
background: none;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 15px;
line-height: 1;
padding: 4px;
border-radius: 6px;
opacity: 0;
transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.habit-row:hover .remove-habit,
.remove-habit:focus-visible { opacity: 1; }
.remove-habit:hover { color: var(--orange); background: rgba(255, 107, 53, 0.12); }
/* Cells */
.cell {
position: relative;
margin: 0 auto;
width: 34px;
height: 34px;
border-radius: 10px;
background: var(--surface-2);
border: 1px solid var(--line);
cursor: pointer;
display: grid;
place-items: center;
color: transparent;
font-size: 16px;
font-weight: 800;
transition: transform 0.12s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.cell:hover { border-color: var(--line-2); transform: translateY(-1px); }
.cell:focus-visible {
outline: 3px solid rgba(194, 245, 66, 0.55);
outline-offset: 2px;
}
.cell.is-done {
background: linear-gradient(160deg, var(--lime), var(--lime-d));
border-color: transparent;
color: #10130a;
box-shadow: 0 6px 16px rgba(194, 245, 66, 0.35);
}
.cell.is-today:not(.is-done) {
border-color: rgba(194, 245, 66, 0.45);
}
.cell.pop { animation: pop 0.34s cubic-bezier(0.2, 0.9, 0.3, 1.3); }
@keyframes pop {
0% { transform: scale(0.7); }
55% { transform: scale(1.18); }
100% { transform: scale(1); }
}
/* Composer */
.composer { margin-top: 22px; }
.add-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 14px 16px;
}
.emoji-pick { display: flex; gap: 6px; }
.emoji-btn {
width: 40px;
height: 40px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--surface-2);
font-size: 19px;
cursor: pointer;
transition: transform 0.12s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.emoji-btn:hover { transform: translateY(-1px); border-color: var(--line-2); }
.emoji-btn:focus-visible {
outline: 3px solid rgba(194, 245, 66, 0.55);
outline-offset: 2px;
}
.emoji-btn.is-active {
border-color: var(--lime);
box-shadow: 0 0 0 1px var(--lime), 0 6px 16px rgba(194, 245, 66, 0.25);
}
.add-input {
flex: 1 1 200px;
min-width: 0;
font: inherit;
font-weight: 500;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 11px 14px;
}
.add-input::placeholder { color: var(--muted); }
.add-input:focus {
outline: none;
border-color: var(--lime);
box-shadow: 0 0 0 3px rgba(194, 245, 66, 0.18);
}
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--surface-2);
color: var(--ink);
border: 1px solid var(--line-2);
border-left: 3px solid var(--lime);
padding: 12px 18px;
border-radius: var(--r-sm);
font-size: 14px;
font-weight: 600;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.28s ease;
z-index: 50;
}
.toast.show { transform: translate(-50%, 0); opacity: 1; }
/* Responsive */
@media (max-width: 720px) {
.stats { grid-template-columns: 1fr 1fr; }
.stat-card--wide { grid-column: 1 / -1; }
}
@media (max-width: 520px) {
.wrap { padding: 22px 14px 56px; }
.topbar { flex-direction: column; align-items: flex-start; gap: 12px; }
.btn-ghost { align-self: stretch; }
.stats { grid-template-columns: 1fr; }
.grid-head,
.habit-row {
grid-template-columns: minmax(120px, 1.4fr) repeat(7, minmax(30px, 1fr));
}
.habit-emoji { width: 32px; height: 32px; font-size: 15px; }
.cell { width: 28px; height: 28px; border-radius: 8px; font-size: 13px; }
.day-head { font-size: 10px; }
.add-form { flex-direction: column; align-items: stretch; }
.emoji-pick { justify-content: space-between; }
.btn-primary { width: 100%; }
}(function () {
"use strict";
var DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
// JS getDay(): 0=Sun..6=Sat -> map to our Mon-first index
var jsDay = new Date().getDay();
var TODAY = (jsDay + 6) % 7;
// Seeded starter data — realistic but fictional. done = boolean[7]
var habits = [
{ id: id(), emoji: "🏋️", name: "Strength training", done: [true, true, false, true, true, false, false] },
{ id: id(), emoji: "💧", name: "Drink 3L water", done: [true, true, true, true, false, false, false] },
{ id: id(), emoji: "🥗", name: "Hit protein target", done: [true, true, true, true, true, false, false] },
{ id: id(), emoji: "🏃", name: "10k steps", done: [false, true, true, false, true, false, false] },
{ id: id(), emoji: "😴", name: "Sleep by 11pm", done: [true, false, true, true, false, false, false] }
];
var gridHead = document.getElementById("gridHead");
var gridBody = document.getElementById("gridBody");
var toastEl = document.getElementById("toast");
var toastTimer;
function id() {
return "h" + Math.random().toString(36).slice(2, 9);
}
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
// Current streak = consecutive done days ending at today, walking backwards.
function streakFor(habit) {
var s = 0;
for (var i = TODAY; i >= 0; i--) {
if (habit.done[i]) s++;
else break;
}
return s;
}
function buildHead() {
gridHead.innerHTML = "";
var label = document.createElement("span");
label.className = "cell-habit head-habit";
label.textContent = "Habit";
gridHead.appendChild(label);
DAYS.forEach(function (d, i) {
var h = document.createElement("span");
h.className = "day-head" + (i === TODAY ? " is-today" : "");
h.textContent = d;
gridHead.appendChild(h);
});
}
function renderRows() {
gridBody.innerHTML = "";
habits.forEach(function (habit) {
var row = document.createElement("div");
row.className = "habit-row";
var info = document.createElement("div");
info.className = "habit-info cell-habit";
info.style.textTransform = "none";
info.style.letterSpacing = "0";
var emoji = document.createElement("span");
emoji.className = "habit-emoji";
emoji.textContent = habit.emoji;
var meta = document.createElement("div");
meta.className = "habit-meta";
var name = document.createElement("div");
name.className = "habit-name";
name.textContent = habit.name;
var streak = document.createElement("div");
var s = streakFor(habit);
streak.className = "habit-streak" + (s === 0 ? " is-zero" : "");
streak.innerHTML = '<span class="streak-badge">🔥 ' + s + "</span> day streak";
meta.appendChild(name);
meta.appendChild(streak);
var remove = document.createElement("button");
remove.className = "remove-habit";
remove.type = "button";
remove.setAttribute("aria-label", "Remove " + habit.name);
remove.textContent = "✕";
remove.addEventListener("click", function () {
habits = habits.filter(function (h) { return h.id !== habit.id; });
render();
toast("Removed “" + habit.name + "”");
});
info.appendChild(emoji);
info.appendChild(meta);
info.appendChild(remove);
row.appendChild(info);
DAYS.forEach(function (d, i) {
var cell = document.createElement("button");
cell.type = "button";
cell.className = "cell" +
(habit.done[i] ? " is-done" : "") +
(i === TODAY ? " is-today" : "");
cell.textContent = "✓";
cell.setAttribute(
"aria-pressed",
habit.done[i] ? "true" : "false"
);
cell.setAttribute("aria-label", habit.name + " — " + d + (habit.done[i] ? " (done)" : ""));
cell.addEventListener("click", function () {
habit.done[i] = !habit.done[i];
if (habit.done[i]) cell.classList.add("pop");
render();
});
row.appendChild(cell);
});
gridBody.appendChild(row);
});
}
function renderStats() {
var total = habits.length * 7;
var checks = 0;
var longest = 0;
habits.forEach(function (h) {
h.done.forEach(function (v) { if (v) checks++; });
var s = streakFor(h);
if (s > longest) longest = s;
});
var pct = total ? Math.round((checks / total) * 100) : 0;
document.getElementById("statChecks").textContent = checks;
document.getElementById("statChecksSub").textContent =
habits.length + (habits.length === 1 ? " habit" : " habits") + " tracked";
document.getElementById("statStreak").innerHTML = longest + '<span class="stat-unit">d</span>';
document.getElementById("statPct").textContent = pct + "%";
var bar = document.getElementById("weekProgress");
document.getElementById("weekProgressFill").style.width = pct + "%";
bar.setAttribute("aria-valuenow", String(pct));
}
function render() {
renderRows();
renderStats();
}
// Emoji picker
var selectedEmoji = "🏋️";
var emojiBtns = document.querySelectorAll(".emoji-btn");
emojiBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
emojiBtns.forEach(function (b) { b.classList.remove("is-active"); });
btn.classList.add("is-active");
selectedEmoji = btn.getAttribute("data-emoji");
});
});
// Add habit
var form = document.getElementById("addForm");
var input = document.getElementById("habitName");
form.addEventListener("submit", function (e) {
e.preventDefault();
var name = input.value.trim();
if (!name) {
toast("Give your habit a name first");
input.focus();
return;
}
habits.push({
id: id(),
emoji: selectedEmoji,
name: name,
done: [false, false, false, false, false, false, false]
});
input.value = "";
render();
toast(selectedEmoji + " Added “" + name + "” — let's go!");
// scroll new row into view on small screens
var rows = gridBody.querySelectorAll(".habit-row");
if (rows.length) rows[rows.length - 1].scrollIntoView({ behavior: "smooth", block: "nearest" });
});
// Reset week
var resetArmed = false;
var resetTimer;
var resetBtn = document.getElementById("resetWeek");
resetBtn.addEventListener("click", function () {
if (!resetArmed) {
resetArmed = true;
resetBtn.textContent = "Tap again to confirm";
toast("This clears every check for the week");
clearTimeout(resetTimer);
resetTimer = setTimeout(function () {
resetArmed = false;
resetBtn.textContent = "Reset week";
}, 3000);
return;
}
resetArmed = false;
clearTimeout(resetTimer);
resetBtn.textContent = "Reset week";
habits.forEach(function (h) {
h.done = [false, false, false, false, false, false, false];
});
render();
toast("Week reset — fresh start 💪");
});
buildHead();
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Coach — Habit Tracker</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="wrap">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">⚡</span>
<div class="brand-text">
<span class="brand-kicker">Ironpath Coaching</span>
<h1>Weekly Habit Tracker</h1>
</div>
</div>
<button class="btn btn-ghost" id="resetWeek" type="button">Reset week</button>
</header>
<section class="stats" aria-label="Weekly summary">
<article class="stat-card">
<span class="stat-label">Checks logged</span>
<span class="stat-value" id="statChecks">0</span>
<span class="stat-sub" id="statChecksSub">this week</span>
</article>
<article class="stat-card">
<span class="stat-label">Longest streak</span>
<span class="stat-value stat-value--orange" id="statStreak">0<span class="stat-unit">d</span></span>
<span class="stat-sub">consecutive days</span>
</article>
<article class="stat-card stat-card--wide">
<div class="stat-head">
<span class="stat-label">Weekly completion</span>
<span class="stat-value stat-value--lime" id="statPct">0%</span>
</div>
<div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="weekProgress" aria-label="Weekly completion">
<span class="progress-fill" id="weekProgressFill"></span>
</div>
</article>
</section>
<section class="board" aria-label="Habit grid">
<div class="grid-head" id="gridHead">
<span class="cell-habit head-habit">Habit</span>
<!-- day headers injected -->
</div>
<div class="grid-body" id="gridBody">
<!-- rows injected -->
</div>
</section>
<section class="composer" aria-label="Add a habit">
<form id="addForm" class="add-form" autocomplete="off">
<div class="emoji-pick" role="group" aria-label="Choose an icon">
<button class="emoji-btn is-active" type="button" data-emoji="🏋️">🏋️</button>
<button class="emoji-btn" type="button" data-emoji="🥗">🥗</button>
<button class="emoji-btn" type="button" data-emoji="💧">💧</button>
<button class="emoji-btn" type="button" data-emoji="🏃">🏃</button>
<button class="emoji-btn" type="button" data-emoji="🧘">🧘</button>
<button class="emoji-btn" type="button" data-emoji="😴">😴</button>
</div>
<input class="add-input" id="habitName" type="text" maxlength="28" placeholder="New habit — e.g. Cold shower" aria-label="Habit name" />
<button class="btn btn-primary" type="submit">Add habit</button>
</form>
</section>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A high-energy weekly habit tracker built for a fitness coaching brand. The layout centers on a responsive grid: each row is a habit (with an emoji chip, name, and running streak) and each column is a day of the week. Every cell is a tappable dot — click or press it with the keyboard to toggle completion, and it snaps to a glowing lime state with a satisfying pop micro-interaction.
Above the grid, three stat cards summarize the week: total checks logged, the longest active streak across all habits, and an overall completion percentage that animates as you fill cells. Each habit also carries its own mini completion ring so you can spot which routines are on track and which are slipping.
You can add a new habit through the inline composer — pick an emoji, type a name, and it drops straight into the grid. A reset control clears the week after a confirm toast, and every interaction fires a short toast for feedback. The whole thing is vanilla JS with no dependencies, keyboard-accessible, and collapses cleanly to a single-column-friendly view on narrow screens.