Coach — Nutrition Plan
An energetic dark nutrition dashboard for a fitness coaching client: an animated daily macros ring tracks protein, carbs and fat against calorie goals, meal cards log breakfast through snacks with one-tap swap alternatives, a segmented water tracker fills glass by glass, and a grocery checklist crosses off items as you shop. Built with vanilla JS, electric-lime accents and buttery micro-interactions.
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;
--carb: #6ec9ff;
--fat: var(--orange);
}
* { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body {
margin: 0;
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(900px 500px at 88% -10%, rgba(194, 245, 66, 0.10), transparent 60%),
radial-gradient(700px 500px at -5% 110%, rgba(255, 107, 53, 0.10), transparent 60%),
var(--bg);
min-height: 100vh;
}
.wrap { max-width: 1080px; margin: 0 auto; padding: 28px 20px 64px; }
/* ---------- Top bar ---------- */
.topbar {
display: flex; align-items: center; justify-content: space-between;
gap: 16px; margin-bottom: 24px;
}
.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(150deg, var(--lime), var(--lime-d));
color: #10130a; font-size: 20px; font-weight: 800;
box-shadow: 0 0 22px rgba(194, 245, 66, 0.35);
}
.brand__eyebrow {
margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 0.18em;
text-transform: uppercase; color: var(--lime);
}
.brand__title {
margin: 0; font-size: 24px; font-weight: 800; letter-spacing: 0.02em;
text-transform: uppercase;
}
.topbar__meta { display: flex; align-items: center; gap: 12px; }
.avatar {
display: grid; place-items: center; width: 42px; height: 42px;
border-radius: 50%; font-weight: 700; font-size: 13px; color: #10130a;
background: linear-gradient(150deg, var(--orange), #ff9466);
}
.pill {
font-size: 12px; font-weight: 600; padding: 7px 13px; border-radius: 999px;
border: 1px solid var(--line-2); color: var(--muted);
}
.pill--day { color: var(--ink); background: var(--surface-2); }
/* ---------- Layout ---------- */
.grid {
display: grid; gap: 18px;
grid-template-columns: repeat(2, 1fr);
}
.ring-card { grid-column: 1 / 2; }
.meals-card { grid-column: 2 / 3; grid-row: 1 / 3; }
.water-card { grid-column: 1 / 2; }
.grocery-card { grid-column: 1 / 3; }
.card {
background: linear-gradient(180deg, var(--surface), var(--surface));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
}
.card__head {
display: flex; align-items: center; justify-content: space-between;
gap: 12px; margin-bottom: 16px;
}
.card__title {
margin: 0; font-size: 14px; font-weight: 800; letter-spacing: 0.14em;
text-transform: uppercase;
}
.badge {
font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
padding: 5px 10px; border-radius: 999px;
color: var(--muted); background: var(--surface-2);
border: 1px solid var(--line);
}
.badge--goal { color: var(--lime); border-color: rgba(194, 245, 66, 0.3); }
.badge--water { color: var(--carb); }
.badge--grocery { color: var(--orange); }
/* ---------- Ring ---------- */
.ring-block { display: flex; flex-direction: column; align-items: center; gap: 22px; }
.ring {
--pct: 0;
position: relative; width: 200px; height: 200px; border-radius: 50%;
background: conic-gradient(var(--lime) calc(var(--pct) * 1%), var(--surface-2) 0);
display: grid; place-items: center;
transition: background 0.7s cubic-bezier(.22,1,.36,1);
box-shadow: inset 0 0 0 1px var(--line);
}
.ring::after {
content: ""; position: absolute; inset: 0; border-radius: 50%;
box-shadow: 0 0 32px rgba(194, 245, 66, calc(var(--pct) * 0.004));
pointer-events: none;
}
.ring--over { background: conic-gradient(var(--orange) calc(var(--pct) * 1%), var(--surface-2) 0); }
.ring__hole {
width: 148px; height: 148px; border-radius: 50%;
background: var(--surface); display: grid; place-content: center;
text-align: center; gap: 2px; border: 1px solid var(--line);
}
.ring__num { font-size: 38px; font-weight: 800; line-height: 1; letter-spacing: -0.02em; }
.ring__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted); }
.ring__left { font-size: 12px; font-weight: 600; color: var(--lime); margin-top: 4px; }
.macros { list-style: none; margin: 0; padding: 0; width: 100%; display: grid; gap: 14px; }
.macro { display: grid; grid-template-columns: 12px 1fr; grid-template-rows: auto auto; column-gap: 10px; align-items: center; }
.macro__dot { width: 10px; height: 10px; border-radius: 50%; grid-row: 1; }
.macro[data-macro="protein"] .macro__dot { background: var(--lime); }
.macro[data-macro="carbs"] .macro__dot { background: var(--carb); }
.macro[data-macro="fat"] .macro__dot { background: var(--fat); }
.macro__body { grid-column: 2; display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.macro__name { font-size: 13px; font-weight: 600; }
.macro__val { font-size: 12px; color: var(--muted); }
.macro__val b { color: var(--ink); font-weight: 700; }
.macro__val i { font-style: normal; }
.macro__bar {
grid-column: 2; grid-row: 2; height: 6px; border-radius: 999px;
background: var(--surface-2); overflow: hidden; margin-top: 6px;
}
.macro__bar span { display: block; height: 100%; width: 0; border-radius: 999px; transition: width 0.6s cubic-bezier(.22,1,.36,1); }
.macro[data-macro="protein"] .macro__bar span { background: linear-gradient(90deg, var(--lime-d), var(--lime)); }
.macro[data-macro="carbs"] .macro__bar span { background: linear-gradient(90deg, #3fa3e0, var(--carb)); }
.macro[data-macro="fat"] .macro__bar span { background: linear-gradient(90deg, #d8562a, var(--orange)); }
/* ---------- Meals ---------- */
.meals { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.meal {
background: var(--surface-2); border: 1px solid var(--line);
border-radius: var(--r-md); padding: 14px; display: grid; gap: 10px;
transition: border-color 0.2s, transform 0.2s;
}
.meal:hover { border-color: var(--line-2); transform: translateY(-2px); }
.meal--logged { border-color: rgba(194, 245, 66, 0.4); }
.meal__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.meal__slot { font-size: 10px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.meal__kcal { font-size: 13px; font-weight: 800; color: var(--lime); }
.meal__name { margin: 0; font-size: 16px; font-weight: 700; }
.meal__macros { display: flex; flex-wrap: wrap; gap: 6px; }
.tag { font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: rgba(255,255,255,0.05); border: 1px solid var(--line); color: var(--muted); }
.meal__actions { display: flex; gap: 8px; }
/* ---------- Buttons ---------- */
.btn {
font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
border-radius: var(--r-sm); padding: 9px 14px; border: 1px solid var(--line-2);
background: transparent; color: var(--ink);
transition: transform 0.12s, background 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.btn:hover { border-color: var(--line-2); background: rgba(255,255,255,0.05); }
.btn:active { transform: scale(0.96); }
.btn:focus-visible { outline: 3px solid var(--lime); outline-offset: 2px; }
.btn--lime {
background: linear-gradient(150deg, var(--lime), var(--lime-d));
color: #10130a; border-color: transparent;
box-shadow: 0 0 0 rgba(194, 245, 66, 0);
}
.btn--lime:hover { box-shadow: 0 6px 20px rgba(194, 245, 66, 0.35); background: linear-gradient(150deg, var(--lime), var(--lime)); }
.btn--swap { flex: 1; }
.btn--log { flex: 1; }
.btn--ghost { padding: 6px 14px; font-size: 18px; line-height: 1; min-width: 42px; }
.btn--full { width: 100%; margin-top: 14px; }
/* ---------- Water ---------- */
.water { display: grid; grid-template-columns: repeat(8, 1fr); gap: 8px; }
.glass {
position: relative; aspect-ratio: 3 / 4; border-radius: 6px 6px 8px 8px;
border: 1px solid var(--line-2); background: var(--surface-2);
cursor: pointer; overflow: hidden; padding: 0;
transition: transform 0.12s, border-color 0.2s;
}
.glass:hover { transform: translateY(-2px); border-color: var(--carb); }
.glass:active { transform: scale(0.94); }
.glass:focus-visible { outline: 3px solid var(--carb); outline-offset: 2px; }
.glass::before {
content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 0;
background: linear-gradient(180deg, #7fd3ff, var(--carb));
transition: height 0.4s cubic-bezier(.22,1,.36,1);
}
.glass--full::before { height: 100%; }
.glass--full { border-color: var(--carb); }
.water__foot { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 16px; }
.water__ml { margin: 0; font-size: 13px; color: var(--muted); min-width: 140px; text-align: center; }
.water__ml b { color: var(--carb); font-weight: 800; }
/* ---------- Grocery ---------- */
.grocery { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.groc {
display: flex; align-items: center; gap: 11px; padding: 11px 13px;
background: var(--surface-2); border: 1px solid var(--line);
border-radius: var(--r-sm); cursor: pointer;
transition: border-color 0.2s, opacity 0.2s;
}
.groc:hover { border-color: var(--line-2); }
.groc:focus-within { outline: 2px solid var(--lime); outline-offset: 1px; }
.groc__box {
width: 20px; height: 20px; border-radius: 6px; flex: none;
border: 2px solid var(--line-2); display: grid; place-items: center;
transition: background 0.2s, border-color 0.2s;
}
.groc__box svg { width: 12px; height: 12px; opacity: 0; transition: opacity 0.15s; }
.groc__name { font-size: 14px; font-weight: 500; }
.groc__qty { margin-left: auto; font-size: 12px; color: var(--muted); }
.groc input { position: absolute; opacity: 0; width: 0; height: 0; }
.groc--done { opacity: 0.5; }
.groc--done .groc__box { background: var(--lime); border-color: var(--lime); }
.groc--done .groc__box svg { opacity: 1; }
.groc--done .groc__name { text-decoration: line-through; text-decoration-color: var(--muted); }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 26px; transform: translate(-50%, 24px);
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-md); font-size: 13px; font-weight: 600;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
opacity: 0; pointer-events: none; transition: transform 0.3s, opacity 0.3s; z-index: 50;
}
.toast--show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 820px) {
.grid { grid-template-columns: 1fr; }
.ring-card, .meals-card, .water-card, .grocery-card { grid-column: 1 / 2; grid-row: auto; }
}
@media (max-width: 520px) {
.wrap { padding: 20px 14px 48px; }
.brand__title { font-size: 20px; }
.pill--day { display: none; }
.ring { width: 172px; height: 172px; }
.ring__hole { width: 128px; height: 128px; }
.ring__num { font-size: 32px; }
.grocery { grid-template-columns: 1fr; }
.water { gap: 6px; }
}(function () {
"use strict";
// ---------- Goals ----------
var GOAL = { kcal: 2200, p: 180, c: 210, f: 70 };
// ---------- Meal data (each meal has swap alternatives) ----------
var MEALS = [
{
slot: "Breakfast",
options: [
{ name: "Greek Yogurt & Berry Bowl", kcal: 420, p: 34, c: 48, f: 10 },
{ name: "Spinach Egg-White Omelette", kcal: 380, p: 38, c: 12, f: 18 },
{ name: "Overnight Oats & Whey", kcal: 440, p: 32, c: 55, f: 9 }
]
},
{
slot: "Lunch",
options: [
{ name: "Grilled Chicken & Quinoa", kcal: 610, p: 52, c: 58, f: 16 },
{ name: "Turkey Rice Power Bowl", kcal: 590, p: 48, c: 62, f: 14 },
{ name: "Tuna & Sweet Potato Plate", kcal: 560, p: 50, c: 52, f: 13 }
]
},
{
slot: "Dinner",
options: [
{ name: "Salmon, Greens & Rice", kcal: 640, p: 46, c: 50, f: 24 },
{ name: "Lean Beef Stir-Fry", kcal: 620, p: 50, c: 45, f: 22 },
{ name: "Tofu & Soba Noodles", kcal: 540, p: 30, c: 68, f: 15 }
]
},
{
slot: "Snack",
options: [
{ name: "Protein Shake & Almonds", kcal: 300, p: 30, c: 14, f: 13 },
{ name: "Cottage Cheese & Peach", kcal: 240, p: 26, c: 20, f: 6 },
{ name: "Rice Cakes & Peanut Butter", kcal: 280, p: 12, c: 32, f: 12 }
]
}
];
var state = MEALS.map(function () { return { idx: 0, logged: false }; });
var GROCERY = [
{ name: "Chicken breast", qty: "1 kg", done: false },
{ name: "Greek yogurt", qty: "500 g", done: false },
{ name: "Quinoa", qty: "400 g", done: false },
{ name: "Salmon fillet", qty: "300 g", done: false },
{ name: "Mixed berries", qty: "250 g", done: false },
{ name: "Baby spinach", qty: "200 g", done: false },
{ name: "Sweet potato", qty: "6 pcs", done: false },
{ name: "Almonds", qty: "150 g", done: false }
];
var WATER_GLASSES = 8, WATER_ML = 250;
var water = 0;
// ---------- Helpers ----------
var $ = function (id) { return document.getElementById(id); };
function fmt(n) { return n.toLocaleString("en-US"); }
function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
var toastEl = $("toast"), toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("toast--show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("toast--show"); }, 2200);
}
// ---------- Totals ----------
function totals() {
var t = { kcal: 0, p: 0, c: 0, f: 0 };
state.forEach(function (s, i) {
if (!s.logged) return;
var m = MEALS[i].options[s.idx];
t.kcal += m.kcal; t.p += m.p; t.c += m.c; t.f += m.f;
});
return t;
}
// ---------- Render ring + macros ----------
function renderRing() {
var t = totals();
var pct = clamp(Math.round((t.kcal / GOAL.kcal) * 100), 0, 100);
var ring = $("ring");
ring.style.setProperty("--pct", pct);
ring.classList.toggle("ring--over", t.kcal > GOAL.kcal);
$("calNum").textContent = fmt(t.kcal);
var left = GOAL.kcal - t.kcal;
var leftEl = $("calLeft");
if (left >= 0) { leftEl.textContent = fmt(left) + " left"; leftEl.style.color = "var(--lime)"; }
else { leftEl.textContent = fmt(-left) + " over"; leftEl.style.color = "var(--orange)"; }
setMacro("p", t.p, GOAL.p);
setMacro("c", t.c, GOAL.c);
setMacro("f", t.f, GOAL.f);
var loggedCount = state.filter(function (s) { return s.logged; }).length;
$("loggedBadge").textContent = loggedCount + " / 4 logged";
}
function setMacro(key, val, goal) {
$(key + "Val").textContent = val;
$(key + "Goal").textContent = "/ " + goal + "g";
$(key + "Bar").style.width = clamp((val / goal) * 100, 0, 100) + "%";
}
// ---------- Render meals ----------
function renderMeals() {
var ul = $("meals");
ul.innerHTML = "";
MEALS.forEach(function (meal, i) {
var s = state[i];
var m = meal.options[s.idx];
var li = document.createElement("li");
li.className = "meal" + (s.logged ? " meal--logged" : "");
li.innerHTML =
'<div class="meal__top">' +
'<span class="meal__slot">' + meal.slot + '</span>' +
'<span class="meal__kcal">' + m.kcal + ' kcal</span>' +
'</div>' +
'<h3 class="meal__name">' + m.name + '</h3>' +
'<div class="meal__macros">' +
'<span class="tag">P ' + m.p + 'g</span>' +
'<span class="tag">C ' + m.c + 'g</span>' +
'<span class="tag">F ' + m.f + 'g</span>' +
'</div>' +
'<div class="meal__actions">' +
'<button class="btn btn--swap" data-act="swap" data-i="' + i + '" type="button">↺ Swap</button>' +
'<button class="btn btn--log ' + (s.logged ? '' : 'btn--lime') + '" data-act="log" data-i="' + i + '" type="button">' +
(s.logged ? "✓ Logged" : "Log meal") +
'</button>' +
'</div>';
ul.appendChild(li);
});
}
$("meals").addEventListener("click", function (e) {
var btn = e.target.closest("button[data-act]");
if (!btn) return;
var i = +btn.dataset.i, s = state[i], meal = MEALS[i];
if (btn.dataset.act === "swap") {
s.idx = (s.idx + 1) % meal.options.length;
toast("Swapped " + meal.slot + " → " + meal.options[s.idx].name);
} else {
s.logged = !s.logged;
toast(s.logged ? meal.slot + " logged 🔥" : meal.slot + " un-logged");
}
renderMeals();
renderRing();
});
// ---------- Water ----------
function renderWater() {
var box = $("water");
box.innerHTML = "";
for (var i = 0; i < WATER_GLASSES; i++) {
var b = document.createElement("button");
b.type = "button";
b.className = "glass" + (i < water ? " glass--full" : "");
b.dataset.i = i;
b.setAttribute("aria-label", "Glass " + (i + 1) + (i < water ? ", full" : ", empty"));
b.setAttribute("aria-pressed", i < water ? "true" : "false");
box.appendChild(b);
}
$("waterBadge").textContent = water + " / " + WATER_GLASSES + " glasses";
$("waterMl").textContent = water * WATER_ML;
}
function setWater(n, announce) {
var prev = water;
water = clamp(n, 0, WATER_GLASSES);
renderWater();
if (announce && water !== prev) {
if (water === WATER_GLASSES) toast("Hydration goal smashed! 💧");
else toast(water + " glasses · " + (water * WATER_ML) + " ml");
}
}
$("water").addEventListener("click", function (e) {
var g = e.target.closest(".glass");
if (!g) return;
var i = +g.dataset.i;
// tapping the highest full glass empties it, otherwise fill up to that glass
setWater(i + 1 === water ? i : i + 1, true);
});
$("waterPlus").addEventListener("click", function () { setWater(water + 1, true); });
$("waterMinus").addEventListener("click", function () { setWater(water - 1, true); });
// ---------- Grocery ----------
function renderGrocery() {
var ul = $("grocery");
ul.innerHTML = "";
GROCERY.forEach(function (item, i) {
var li = document.createElement("li");
li.className = "groc" + (item.done ? " groc--done" : "");
li.innerHTML =
'<label class="groc" style="all:unset;display:contents">' +
'<input type="checkbox" ' + (item.done ? "checked" : "") + ' data-i="' + i + '" />' +
'<span class="groc__box"><svg viewBox="0 0 24 24" fill="none"><path d="M5 12l4 4L19 6" stroke="#10130a" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span>' +
'<span class="groc__name">' + item.name + '</span>' +
'<span class="groc__qty">' + item.qty + '</span>' +
'</label>';
ul.appendChild(li);
});
var left = GROCERY.filter(function (x) { return !x.done; }).length;
$("groceryBadge").textContent = left === 0 ? "All set ✓" : left + " to buy";
}
$("grocery").addEventListener("change", function (e) {
var cb = e.target.closest("input[type=checkbox]");
if (!cb) return;
var i = +cb.dataset.i;
GROCERY[i].done = cb.checked;
renderGrocery();
if (GROCERY.every(function (x) { return x.done; })) toast("Grocery list complete! 🛒");
});
$("resetGrocery").addEventListener("click", function () {
GROCERY.forEach(function (x) { x.done = false; });
renderGrocery();
toast("Grocery list reset");
});
// ---------- Init ----------
renderMeals();
renderGrocery();
renderWater();
// pre-log breakfast + lunch for a realistic starting state, animate ring after paint
state[0].logged = true;
state[1].logged = true;
renderMeals();
setWater(3, false);
requestAnimationFrame(function () {
requestAnimationFrame(renderRing);
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Coach — Nutrition Plan</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 -->
<header class="topbar">
<div class="brand">
<span class="brand__mark" aria-hidden="true">◆</span>
<div>
<p class="brand__eyebrow">Fuel Coach</p>
<h1 class="brand__title">Nutrition Plan</h1>
</div>
</div>
<div class="topbar__meta">
<span class="pill pill--day">Day 12 · Cut Phase</span>
<div class="avatar" aria-hidden="true">MJ</div>
</div>
</header>
<main class="grid">
<!-- Macros ring -->
<section class="card ring-card" aria-labelledby="ring-h">
<div class="card__head">
<h2 id="ring-h" class="card__title">Daily Macros</h2>
<span class="badge badge--goal" id="goalBadge">Goal 2,200 kcal</span>
</div>
<div class="ring-block">
<div class="ring" id="ring" role="img" aria-label="Calories consumed today">
<div class="ring__hole">
<span class="ring__num" id="calNum">0</span>
<span class="ring__label">kcal eaten</span>
<span class="ring__left" id="calLeft">2,200 left</span>
</div>
</div>
<ul class="macros" id="macros">
<li class="macro" data-macro="protein">
<span class="macro__dot"></span>
<div class="macro__body">
<span class="macro__name">Protein</span>
<span class="macro__val"><b id="pVal">0</b>g <i id="pGoal">/ 180g</i></span>
</div>
<div class="macro__bar"><span id="pBar"></span></div>
</li>
<li class="macro" data-macro="carbs">
<span class="macro__dot"></span>
<div class="macro__body">
<span class="macro__name">Carbs</span>
<span class="macro__val"><b id="cVal">0</b>g <i id="cGoal">/ 210g</i></span>
</div>
<div class="macro__bar"><span id="cBar"></span></div>
</li>
<li class="macro" data-macro="fat">
<span class="macro__dot"></span>
<div class="macro__body">
<span class="macro__name">Fat</span>
<span class="macro__val"><b id="fVal">0</b>g <i id="fGoal">/ 70g</i></span>
</div>
<div class="macro__bar"><span id="fBar"></span></div>
</li>
</ul>
</div>
</section>
<!-- Meals -->
<section class="card meals-card" aria-labelledby="meals-h">
<div class="card__head">
<h2 id="meals-h" class="card__title">Today's Meals</h2>
<span class="badge" id="loggedBadge">0 / 4 logged</span>
</div>
<ul class="meals" id="meals"><!-- injected by JS --></ul>
</section>
<!-- Water -->
<section class="card water-card" aria-labelledby="water-h">
<div class="card__head">
<h2 id="water-h" class="card__title">Water</h2>
<span class="badge badge--water" id="waterBadge">0 / 8 glasses</span>
</div>
<div class="water" id="water" role="group" aria-label="Water intake, tap a glass"><!-- glasses --></div>
<div class="water__foot">
<button class="btn btn--ghost" id="waterMinus" type="button">−</button>
<p class="water__ml"><b id="waterMl">0</b> ml of 2000 ml</p>
<button class="btn btn--ghost" id="waterPlus" type="button">+</button>
</div>
</section>
<!-- Grocery -->
<section class="card grocery-card" aria-labelledby="grocery-h">
<div class="card__head">
<h2 id="grocery-h" class="card__title">Grocery List</h2>
<span class="badge badge--grocery" id="groceryBadge">0 to buy</span>
</div>
<ul class="grocery" id="grocery"><!-- items --></ul>
<button class="btn btn--lime btn--full" id="resetGrocery" type="button">Reset list</button>
</section>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A single-screen nutrition plan built for a fitness coach’s client app. The hero is a conic-gradient macros ring that animates on load and recalculates whenever you log or swap a meal, showing calories consumed against the daily target with protein, carbs and fat broken out into their own dial segments and stat chips.
Below the ring, four meal cards (breakfast, lunch, dinner, snack) list the planned dish, its calories and macro badges. Each card carries a Swap button that cycles through coach-approved alternatives with matching macros, instantly re-totalling the ring. A water tracker renders eight tappable glasses that fill with a lime shimmer, and a grocery list lets you check off ingredients with a satisfying strike-through and a live “items left” counter.
Everything is keyboard-usable with visible focus rings, announces changes through a small toast helper, and collapses to a clean single column under 520px. No frameworks, no build step — just semantic HTML, a themed stylesheet and one vanilla JS file.