Coach — Booking 1on1
An energetic dark 1-on-1 coaching booking flow where athletes choose a session type, scan a live availability calendar with time-zone aware slots, fill in their training details, and lock in a glowing lime confirmation — every step gated, keyboard friendly, and packed with motivational 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;
--glow: 0 0 0 1px rgba(194, 245, 66, 0.5), 0 8px 30px rgba(194, 245, 66, 0.28);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background:
radial-gradient(1100px 600px at 85% -10%, rgba(255, 107, 53, 0.10), transparent 60%),
radial-gradient(900px 500px at 0% 0%, rgba(194, 245, 66, 0.08), 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;
min-height: 100vh;
}
.shell {
max-width: 1080px;
margin: 0 auto;
padding: 24px 20px 64px;
}
/* Topbar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand__mark {
display: grid;
place-items: center;
width: 44px; height: 44px;
border-radius: var(--r-md);
background: linear-gradient(150deg, var(--lime), var(--lime-d));
color: #10130a;
font-size: 22px;
box-shadow: 0 6px 20px rgba(194, 245, 66, 0.35);
}
.brand__txt { display: flex; flex-direction: column; line-height: 1.15; }
.brand__txt strong { font-weight: 800; letter-spacing: 0.14em; font-size: 15px; }
.brand__txt span { color: var(--muted); font-size: 12.5px; }
.coach { display: flex; align-items: center; gap: 12px; }
.coach__avatar {
width: 42px; height: 42px;
border-radius: 50%;
display: grid; place-items: center;
font-weight: 800; font-size: 15px;
color: #10130a;
background: linear-gradient(150deg, #ffd0a8, var(--orange));
}
.coach__meta { display: flex; flex-direction: column; }
.coach__name { font-weight: 700; font-size: 14px; }
.coach__badge { color: var(--lime); font-size: 12px; font-weight: 600; }
/* Steps */
.steps { margin: 28px 0 24px; }
.steps ol {
list-style: none;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin: 0 0 12px;
padding: 0;
}
.step { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 13.5px; font-weight: 600; }
.step__dot {
width: 28px; height: 28px;
border-radius: 50%;
display: grid; place-items: center;
background: var(--surface-2);
border: 1px solid var(--line);
font-size: 13px;
transition: all 0.25s ease;
}
.step.is-active .step__dot,
.step.is-done .step__dot {
background: var(--lime);
border-color: var(--lime);
color: #10130a;
box-shadow: 0 0 0 4px rgba(194, 245, 66, 0.14);
}
.step.is-active .step__label { color: var(--ink); }
.step.is-done .step__dot::after { content: "✓"; }
.step.is-done .step__dot { font-size: 0; }
.step.is-done .step__dot::after { font-size: 14px; }
.steps__bar {
height: 6px;
background: var(--surface-2);
border-radius: 999px;
overflow: hidden;
border: 1px solid var(--line);
}
.steps__bar span {
display: block;
height: 100%;
width: 25%;
background: linear-gradient(90deg, var(--lime-d), var(--lime));
border-radius: 999px;
transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Layout */
.layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 22px;
align-items: start;
}
.panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 26px;
min-height: 460px;
}
.pane { display: none; animation: rise 0.3s ease; }
.pane.is-active { display: block; }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.h {
margin: 0 0 6px;
font-weight: 800;
letter-spacing: -0.01em;
font-size: clamp(22px, 3vw, 28px);
}
.sub { margin: 0 0 22px; color: var(--muted); font-size: 14.5px; max-width: 52ch; }
/* Session cards */
.cards { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.scard {
position: relative;
text-align: left;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 16px;
color: var(--ink);
cursor: pointer;
display: flex;
flex-direction: column;
gap: 6px;
font: inherit;
transition: transform 0.15s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.scard:hover { transform: translateY(-3px); border-color: var(--line-2); }
.scard:focus-visible { outline: none; box-shadow: var(--glow); }
.scard[aria-checked="true"] {
border-color: var(--lime);
box-shadow: var(--glow);
background: linear-gradient(180deg, rgba(194, 245, 66, 0.10), var(--surface-2));
}
.scard__ico { font-size: 26px; }
.scard__title { font-weight: 700; font-size: 16px; }
.scard__desc { color: var(--muted); font-size: 13px; }
.scard__foot {
margin-top: 8px;
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 13px;
color: var(--muted);
border-top: 1px solid var(--line);
padding-top: 10px;
}
.scard__price { font-weight: 800; font-size: 17px; color: var(--lime); }
.scard__tag {
position: absolute;
top: 12px; right: 12px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
padding: 4px 8px;
border-radius: 999px;
background: var(--lime);
color: #10130a;
}
.scard__tag--alt { background: var(--orange); color: #fff; }
/* Calendar */
.cal__nav {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.cal__range { font-weight: 700; font-size: 15px; }
.iconbtn {
width: 38px; height: 38px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--surface-2);
color: var(--ink);
font-size: 20px;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease;
}
.iconbtn:hover:not(:disabled) { border-color: var(--line-2); background: #262b35; }
.iconbtn:disabled { opacity: 0.35; cursor: not-allowed; }
.iconbtn:focus-visible { outline: none; box-shadow: var(--glow); }
.cal__grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
}
.day {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 10px 8px;
min-height: 150px;
display: flex;
flex-direction: column;
gap: 6px;
}
.day__head { text-align: center; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.day__dow { display: block; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }
.day__num { display: block; font-size: 17px; font-weight: 800; }
.day.is-off .day__num { color: var(--muted); }
.slot {
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.03);
color: var(--ink);
border-radius: var(--r-sm);
padding: 6px 4px;
font: 600 12.5px Inter, sans-serif;
cursor: pointer;
transition: all 0.15s ease;
}
.slot:hover:not(:disabled) { border-color: var(--lime); color: var(--lime); transform: translateY(-1px); }
.slot:focus-visible { outline: none; box-shadow: var(--glow); }
.slot:disabled { opacity: 0.3; cursor: not-allowed; text-decoration: line-through; }
.slot.is-picked {
background: var(--lime);
border-color: var(--lime);
color: #10130a;
font-weight: 800;
box-shadow: 0 4px 14px rgba(194, 245, 66, 0.3);
}
.day__empty { font-size: 11px; color: var(--muted); text-align: center; margin-top: auto; margin-bottom: auto; }
.tznote {
margin: 18px 0 0;
font-size: 12.5px;
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 10px 12px;
}
.tznote strong { color: var(--ink); }
/* Form */
.form { display: grid; gap: 16px; max-width: 480px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; font-weight: 600; }
.field .opt { color: var(--muted); font-weight: 400; }
.field input, .field select, .field textarea {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
color: var(--ink);
padding: 11px 12px;
font: inherit;
font-size: 14px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.field textarea { resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
outline: none;
border-color: var(--lime);
box-shadow: 0 0 0 3px rgba(194, 245, 66, 0.16);
}
.field.has-err input, .field.has-err select { border-color: var(--orange); }
.err { color: var(--orange); font-size: 12px; min-height: 0; }
/* Confirmation */
.done { text-align: center; padding: 20px 0; }
.done__ring {
width: 68px; height: 68px;
margin: 0 auto 18px;
border-radius: 50%;
display: grid; place-items: center;
font-size: 32px;
color: #10130a;
background: linear-gradient(150deg, var(--lime), var(--lime-d));
box-shadow: 0 0 0 8px rgba(194, 245, 66, 0.14), 0 10px 30px rgba(194, 245, 66, 0.35);
animation: pop 0.4s cubic-bezier(0.2, 1.4, 0.4, 1);
}
@keyframes pop { from { transform: scale(0.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.ref {
display: inline-block;
margin: 6px 0 20px;
font-size: 13px;
color: var(--muted);
background: var(--surface-2);
border: 1px dashed var(--line-2);
border-radius: 999px;
padding: 6px 16px;
}
.ref strong { color: var(--lime); letter-spacing: 0.06em; }
.done__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
max-width: 420px;
margin: 0 auto 22px;
text-align: left;
}
.done__grid div {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 12px;
}
.done__grid dt { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
.done__grid dd { margin: 4px 0 0; font-weight: 700; font-size: 14px; }
/* Summary rail */
.summary {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
position: sticky;
top: 20px;
}
.summary__h { margin: 0 0 14px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); }
.summary__list { margin: 0; display: grid; gap: 12px; }
.summary__list div { display: flex; justify-content: space-between; gap: 12px; font-size: 13.5px; }
.summary__list dt { color: var(--muted); }
.summary__list dd { margin: 0; font-weight: 600; text-align: right; }
.summary__total {
margin: 16px 0;
padding: 14px 0;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
align-items: baseline;
}
.summary__total strong { font-size: 24px; font-weight: 800; color: var(--lime); }
.summary__actions { display: flex; gap: 10px; }
.summary__note { margin: 12px 0 0; font-size: 11.5px; color: var(--muted); text-align: center; }
.btn {
border: none;
border-radius: var(--r-sm);
padding: 12px 16px;
font: 700 14px Inter, sans-serif;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.btn:focus-visible { outline: none; box-shadow: var(--glow); }
.btn--go {
flex: 1;
background: linear-gradient(150deg, var(--lime), var(--lime-d));
color: #10130a;
box-shadow: 0 6px 20px rgba(194, 245, 66, 0.3);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.btn--go:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(194, 245, 66, 0.45); }
.btn--go:active:not(:disabled) { transform: translateY(0); }
.btn--go:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; filter: grayscale(0.4); }
.btn--ghost {
background: var(--surface-2);
color: var(--ink);
border: 1px solid var(--line);
}
.btn--ghost:hover { border-color: var(--line-2); background: #262b35; }
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 20px);
background: var(--surface-2);
border: 1px solid var(--lime);
color: var(--ink);
padding: 12px 18px;
border-radius: var(--r-md);
font-size: 14px;
font-weight: 600;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 860px) {
.layout { grid-template-columns: 1fr; }
.summary { position: static; }
}
@media (max-width: 520px) {
.shell { padding: 18px 14px 48px; }
.cards { grid-template-columns: 1fr; }
.step__label { display: none; }
.steps ol { gap: 4px; }
.panel { padding: 18px; min-height: 0; }
.cal__grid {
grid-auto-flow: column;
grid-template-columns: none;
grid-auto-columns: 78%;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 8px;
}
.day { scroll-snap-align: start; }
.done__grid { grid-template-columns: 1fr; }
.coach__meta { display: none; }
}(function () {
"use strict";
// ---------- State ----------
var state = {
step: 1,
session: null, // { id, name, dur, price }
slot: null, // { date: Date, time: "07:00", label: "07:00 AM" }
weekOffset: 0,
details: {}
};
var DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var MON = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// ---------- Helpers ----------
var $ = function (s, r) { return (r || document).querySelector(s); };
var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2600);
}
function startOfWeek(offset) {
var d = new Date();
d.setHours(0, 0, 0, 0);
d.setDate(d.getDate() - d.getDay() + offset * 7); // Sunday start
return d;
}
function fmtTime(h, m) {
var ampm = h >= 12 ? "PM" : "AM";
var hh = h % 12; if (hh === 0) hh = 12;
return (hh < 10 ? "0" + hh : hh) + ":" + (m < 10 ? "0" + m : m) + " " + ampm;
}
function money(n) { return "$" + n; }
// Deterministic pseudo-availability so the calendar feels real & stable
function slotsForDay(date) {
var day = date.getDay();
if (day === 0) return []; // Sunday off
var seed = date.getDate() + date.getMonth() * 31;
var base = [7, 9, 12, 17, 19]; // hours
var out = [];
for (var i = 0; i < base.length; i++) {
var h = base[i];
// saturdays: only mornings
if (day === 6 && h > 12) continue;
var booked = (seed + h) % 3 === 0; // some already taken
var past = date < new Date(new Date().setHours(0, 0, 0, 0)); // whole past days
out.push({ h: h, m: 0, booked: booked || past });
}
return out;
}
// ---------- Steps ----------
var nextBtn = $("#nextBtn");
var backBtn = $("#backBtn");
var progressFill = $("#progressFill");
function setStep(n) {
state.step = n;
$$(".pane").forEach(function (p) {
p.classList.toggle("is-active", +p.dataset.pane === n);
});
$$(".step").forEach(function (s) {
var i = +s.dataset.step;
s.classList.toggle("is-active", i === n);
s.classList.toggle("is-done", i < n);
});
progressFill.style.width = (n * 25) + "%";
backBtn.hidden = n === 1 || n === 4;
if (n === 4) {
nextBtn.hidden = true;
backBtn.hidden = true;
} else {
nextBtn.hidden = false;
nextBtn.textContent = n === 3 ? "Confirm booking" : "Continue";
}
refreshGate();
if (n === 2) renderCalendar();
window.scrollTo({ top: 0, behavior: "smooth" });
}
function refreshGate() {
var ok = false;
if (state.step === 1) ok = !!state.session;
else if (state.step === 2) ok = !!state.slot;
else if (state.step === 3) ok = true; // validated on submit
nextBtn.disabled = !ok && state.step !== 3;
}
// ---------- Step 1: session cards ----------
$$(".scard").forEach(function (card) {
card.addEventListener("click", function () {
$$(".scard").forEach(function (c) { c.setAttribute("aria-checked", "false"); });
card.setAttribute("aria-checked", "true");
state.session = {
id: card.dataset.id,
name: card.dataset.name,
dur: +card.dataset.dur,
price: +card.dataset.price
};
updateSummary();
refreshGate();
toast(state.session.name + " selected");
});
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); card.click(); }
});
});
// ---------- Step 2: calendar ----------
var calGrid = $("#calGrid");
var weekLabel = $("#weekLabel");
var prevWeek = $("#prevWeek");
var nextWeek = $("#nextWeek");
try {
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (tz) $("#tzName").textContent = tz.replace(/_/g, " ");
} catch (e) { /* keep default */ }
function renderCalendar() {
var start = startOfWeek(state.weekOffset);
var end = new Date(start); end.setDate(end.getDate() + 6);
weekLabel.textContent = MON[start.getMonth()] + " " + start.getDate() + " – " +
(start.getMonth() === end.getMonth() ? "" : MON[end.getMonth()] + " ") + end.getDate();
prevWeek.disabled = state.weekOffset <= 0;
calGrid.innerHTML = "";
for (var i = 0; i < 7; i++) {
var date = new Date(start); date.setDate(date.getDate() + i);
var col = document.createElement("div");
col.className = "day";
var slots = slotsForDay(date);
var anyOpen = slots.some(function (s) { return !s.booked; });
if (!anyOpen) col.classList.add("is-off");
var head = document.createElement("div");
head.className = "day__head";
head.innerHTML = '<span class="day__dow">' + DOW[date.getDay()] + '</span>' +
'<span class="day__num">' + date.getDate() + '</span>';
col.appendChild(head);
if (slots.length === 0) {
var empty = document.createElement("div");
empty.className = "day__empty";
empty.textContent = "Rest day";
col.appendChild(empty);
} else {
slots.forEach(function (s) {
var btn = document.createElement("button");
btn.className = "slot";
btn.type = "button";
btn.textContent = fmtTime(s.h, s.m).replace(" ", "");
btn.disabled = s.booked;
var slotDate = new Date(date);
if (!s.booked) {
var isPicked = state.slot &&
state.slot.date.getTime() === slotDate.getTime() &&
state.slot.h === s.h;
if (isPicked) btn.classList.add("is-picked");
btn.setAttribute("aria-pressed", isPicked ? "true" : "false");
(function (sd, sl) {
btn.addEventListener("click", function () {
state.slot = { date: sd, h: sl.h, m: sl.m, label: fmtTime(sl.h, sl.m) };
renderCalendar();
updateSummary();
refreshGate();
toast("Slot held: " + DOW[sd.getDay()] + " " + fmtTime(sl.h, sl.m));
});
})(slotDate, s);
} else {
btn.title = "Unavailable";
}
col.appendChild(btn);
});
}
calGrid.appendChild(col);
}
}
prevWeek.addEventListener("click", function () {
if (state.weekOffset > 0) { state.weekOffset--; renderCalendar(); }
});
nextWeek.addEventListener("click", function () {
if (state.weekOffset < 6) { state.weekOffset++; renderCalendar(); }
else toast("Bookings open 6 weeks ahead");
});
// ---------- Step 3: form validation ----------
var form = $("#detForm");
function setErr(name, msg) {
var small = $('.err[data-for="' + name + '"]');
var field = small ? small.closest(".field") : null;
if (small) small.textContent = msg || "";
if (field) field.classList.toggle("has-err", !!msg);
}
function validate() {
var d = {
name: $("#fName").value.trim(),
email: $("#fEmail").value.trim(),
level: $("#fLevel").value,
goal: $("#fGoal").value.trim()
};
var ok = true;
if (d.name.length < 2) { setErr("name", "Please enter your name."); ok = false; } else setErr("name", "");
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(d.email)) { setErr("email", "Enter a valid email."); ok = false; } else setErr("email", "");
if (!d.level) { setErr("level", "Pick your level."); ok = false; } else setErr("level", "");
if (ok) state.details = d;
return ok;
}
["fName", "fEmail", "fLevel"].forEach(function (id) {
$("#" + id).addEventListener("input", function () {
var map = { fName: "name", fEmail: "email", fLevel: "level" };
setErr(map[id], "");
});
});
// ---------- Summary ----------
function updateSummary() {
$("#sumSession").textContent = state.session ? state.session.name : "—";
$("#sumDur").textContent = state.session ? state.session.dur + " min" : "—";
$("#sumPrice").textContent = state.session ? money(state.session.price) : "$0";
if (state.slot) {
$("#sumDate").textContent = DOW[state.slot.date.getDay()] + ", " +
MON[state.slot.date.getMonth()] + " " + state.slot.date.getDate();
$("#sumTime").textContent = state.slot.label;
} else {
$("#sumDate").textContent = "—";
$("#sumTime").textContent = "—";
}
}
// ---------- Confirmation ----------
function makeRef() {
var s = "APX-";
var chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
for (var i = 0; i < 6; i++) s += chars[Math.floor(Math.random() * chars.length)];
return s;
}
function fillConfirmation() {
$("#bookingRef").textContent = makeRef();
$("#doneEmail").textContent = state.details.email || "your inbox";
var rows = [
["Session", state.session.name],
["Coach", "Rae Mercer"],
["Date", DOW[state.slot.date.getDay()] + ", " + MON[state.slot.date.getMonth()] + " " + state.slot.date.getDate()],
["Time", state.slot.label + " · " + state.session.dur + " min"],
["Athlete", state.details.name],
["Total", money(state.session.price)]
];
var wrap = $("#doneSummary");
wrap.innerHTML = "";
rows.forEach(function (r) {
var div = document.createElement("div");
div.innerHTML = "<dt>" + r[0] + "</dt><dd>" + r[1] + "</dd>";
wrap.appendChild(div);
});
}
// ---------- Navigation buttons ----------
nextBtn.addEventListener("click", function () {
if (state.step === 1) {
if (!state.session) { toast("Choose a session first"); return; }
setStep(2);
} else if (state.step === 2) {
if (!state.slot) { toast("Pick an open slot"); return; }
setStep(3);
} else if (state.step === 3) {
if (!validate()) { toast("Check the highlighted fields"); return; }
fillConfirmation();
setStep(4);
toast("Booking confirmed! 💪");
}
});
backBtn.addEventListener("click", function () {
if (state.step > 1) setStep(state.step - 1);
});
$("#againBtn").addEventListener("click", function () {
state.session = null; state.slot = null; state.weekOffset = 0; state.details = {};
$$(".scard").forEach(function (c) { c.setAttribute("aria-checked", "false"); });
form.reset();
["name", "email", "level"].forEach(function (n) { setErr(n, ""); });
updateSummary();
setStep(1);
});
// ---------- Init ----------
updateSummary();
setStep(1);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Book a 1-on-1 — Apex Coaching</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="shell">
<header class="topbar">
<div class="brand">
<span class="brand__mark" aria-hidden="true">⚡</span>
<div class="brand__txt">
<strong>APEX COACHING</strong>
<span>1-on-1 performance sessions</span>
</div>
</div>
<div class="coach">
<div class="coach__avatar" aria-hidden="true">RM</div>
<div class="coach__meta">
<span class="coach__name">Coach Rae Mercer</span>
<span class="coach__badge">★ 4.9 · 820 sessions</span>
</div>
</div>
</header>
<nav class="steps" aria-label="Booking progress">
<ol>
<li class="step is-active" data-step="1"><span class="step__dot">1</span><span class="step__label">Session</span></li>
<li class="step" data-step="2"><span class="step__dot">2</span><span class="step__label">Time</span></li>
<li class="step" data-step="3"><span class="step__dot">3</span><span class="step__label">Details</span></li>
<li class="step" data-step="4"><span class="step__dot">4</span><span class="step__label">Done</span></li>
</ol>
<div class="steps__bar"><span id="progressFill"></span></div>
</nav>
<main class="layout">
<section class="panel" aria-live="polite">
<!-- STEP 1: session type -->
<div class="pane is-active" data-pane="1">
<h1 class="h">Pick your session</h1>
<p class="sub">Every session is fully personalized. Choose the focus that matches your goal this week.</p>
<div class="cards" role="radiogroup" aria-label="Session type">
<button class="scard" role="radio" aria-checked="false" data-id="strength" data-name="Strength Reset" data-dur="60" data-price="85">
<span class="scard__tag">POPULAR</span>
<span class="scard__ico" aria-hidden="true">🏋️</span>
<span class="scard__title">Strength Reset</span>
<span class="scard__desc">Rebuild your big lifts with cueing and load strategy.</span>
<span class="scard__foot"><span>60 min</span><span class="scard__price">$85</span></span>
</button>
<button class="scard" role="radio" aria-checked="false" data-id="hiit" data-name="HIIT Sprint" data-dur="45" data-price="65">
<span class="scard__ico" aria-hidden="true">🔥</span>
<span class="scard__title">HIIT Sprint</span>
<span class="scard__desc">High-intensity conditioning, dialed to your engine.</span>
<span class="scard__foot"><span>45 min</span><span class="scard__price">$65</span></span>
</button>
<button class="scard" role="radio" aria-checked="false" data-id="mobility" data-name="Mobility Rebuild" data-dur="45" data-price="60">
<span class="scard__ico" aria-hidden="true">🧘</span>
<span class="scard__title">Mobility Rebuild</span>
<span class="scard__desc">Unlock hips, shoulders and ankles for pain-free reps.</span>
<span class="scard__foot"><span>45 min</span><span class="scard__price">$60</span></span>
</button>
<button class="scard" role="radio" aria-checked="false" data-id="assessment" data-name="Full Assessment" data-dur="90" data-price="120">
<span class="scard__tag scard__tag--alt">DEEP DIVE</span>
<span class="scard__ico" aria-hidden="true">📋</span>
<span class="scard__title">Full Assessment</span>
<span class="scard__desc">Movement screen, goal mapping and a 4-week plan.</span>
<span class="scard__foot"><span>90 min</span><span class="scard__price">$120</span></span>
</button>
</div>
</div>
<!-- STEP 2: calendar -->
<div class="pane" data-pane="2">
<h1 class="h">Choose a time</h1>
<p class="sub">Live availability from Coach Rae's calendar. Grab a glowing slot below.</p>
<div class="cal">
<div class="cal__nav">
<button id="prevWeek" class="iconbtn" aria-label="Previous week">‹</button>
<span id="weekLabel" class="cal__range" aria-live="polite">This week</span>
<button id="nextWeek" class="iconbtn" aria-label="Next week">›</button>
</div>
<div id="calGrid" class="cal__grid" role="group" aria-label="Available days and time slots"></div>
<p class="tznote"><span aria-hidden="true">🕒</span> All times shown in <strong id="tzName">your local time</strong>. Coach converts automatically on confirm.</p>
</div>
</div>
<!-- STEP 3: details -->
<div class="pane" data-pane="3">
<h1 class="h">Your details</h1>
<p class="sub">So Coach Rae can prep the right warm-up and equipment.</p>
<form id="detForm" class="form" novalidate>
<div class="field">
<label for="fName">Full name</label>
<input id="fName" name="name" type="text" autocomplete="name" placeholder="e.g. Jordan Vega" />
<small class="err" data-for="name"></small>
</div>
<div class="field">
<label for="fEmail">Email</label>
<input id="fEmail" name="email" type="email" autocomplete="email" placeholder="[email protected]" />
<small class="err" data-for="email"></small>
</div>
<div class="field">
<label for="fLevel">Training level</label>
<select id="fLevel" name="level">
<option value="">Select one…</option>
<option>Just starting out</option>
<option>Returning after a break</option>
<option>Intermediate</option>
<option>Advanced / competitive</option>
</select>
<small class="err" data-for="level"></small>
</div>
<div class="field">
<label for="fGoal">Main goal <span class="opt">(optional)</span></label>
<textarea id="fGoal" name="goal" rows="3" placeholder="Anything Coach should know — injuries, targets, PRs…"></textarea>
</div>
</form>
</div>
<!-- STEP 4: confirmation -->
<div class="pane" data-pane="4">
<div class="done">
<div class="done__ring" aria-hidden="true">✓</div>
<h1 class="h">You're locked in.</h1>
<p class="sub">A calendar invite and prep checklist are on the way to <strong id="doneEmail">your inbox</strong>.</p>
<div class="ref">Booking ref <strong id="bookingRef">—</strong></div>
<div class="done__grid" id="doneSummary"></div>
<button class="btn btn--ghost" id="againBtn" type="button">Book another session</button>
</div>
</div>
</section>
<!-- Summary rail -->
<aside class="summary" aria-label="Booking summary">
<h2 class="summary__h">Your booking</h2>
<dl class="summary__list">
<div><dt>Coach</dt><dd>Rae Mercer</dd></div>
<div><dt>Session</dt><dd id="sumSession">—</dd></div>
<div><dt>Date</dt><dd id="sumDate">—</dd></div>
<div><dt>Time</dt><dd id="sumTime">—</dd></div>
<div><dt>Duration</dt><dd id="sumDur">—</dd></div>
</dl>
<div class="summary__total">
<span>Total</span><strong id="sumPrice">$0</strong>
</div>
<div class="summary__actions">
<button class="btn btn--ghost" id="backBtn" type="button" hidden>Back</button>
<button class="btn btn--go" id="nextBtn" type="button" disabled>Continue</button>
</div>
<p class="summary__note">Free cancellation up to 12h before your session.</p>
</aside>
</main>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A full 1-on-1 booking experience for a fitness coach, built around a four-step flow: pick a session type (Strength Reset, HIIT Sprint, Mobility Rebuild, or Full Assessment), choose an open slot from a week-view availability calendar, enter your details, then land on a punchy confirmation screen. A sticky progress rail up top tracks where you are, and the primary electric-lime CTA glows and only unlocks once the current step is satisfied.
The calendar renders a real seven-day strip with per-day slot chips; unavailable and already-booked chips are visibly muted and unfocusable, while open chips respond to hover, click, and keyboard selection. A timezone note keeps everyone honest, and a live summary card mirrors your session, date, time, and price as you go so the choice never feels blind.
Interactions are all vanilla JS: step gating, calendar navigation between weeks, slot selection, inline form validation with accessible error text, a toast helper for feedback, and a generated booking reference on confirmation. The layout collapses gracefully to a single column under 520px, keeps WCAG AA contrast on the dark surface, and shows a clear focus ring on every control.