Gym — Trainers Page
A high-energy gym coaches page on a dark athletic theme — a bold neon hero with headline stats, specialty filter chips for Strength, Mobility, Boxing, Yoga and Nutrition, and a responsive grid of trainer cards showing avatar, name, specialty tags, bio, certifications and a Book session call to action. Clicking any card animates open a detail modal with full bio, achievements and selectable availability slots, while filtering smoothly fades and rescales the grid. All vanilla JS.
MCP
コード
:root {
--bg: #0d0f12;
--surface: #15181d;
--surface-2: #1d2127;
--elevated: #23282f;
--ink: #f4f6f8;
--ink-2: #c2c8d0;
--muted: #8b929c;
--neon: #c6ff3a;
--neon-d: #a6e016;
--neon-50: rgba(198, 255, 58, 0.12);
--orange: #ff6a2b;
--orange-soft: rgba(255, 106, 43, 0.14);
--line: rgba(255, 255, 255, 0.08);
--line-2: rgba(255, 255, 255, 0.16);
--ok: #34d399;
--warn: #fbbf24;
--danger: #f87171;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
--shadow-2: 0 10px 30px rgba(0, 0, 0, 0.45);
--shadow-3: 0 24px 60px rgba(0, 0, 0, 0.6);
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3 { margin: 0; line-height: 1.05; letter-spacing: -0.02em; }
p { margin: 0; }
img { max-width: 100%; display: block; }
.wrap { width: min(1140px, 92vw); margin-inline: auto; }
a { color: inherit; text-decoration: none; }
.skip-link {
position: absolute;
left: -999px;
top: 0;
z-index: 100;
background: var(--neon);
color: #0a0c0e;
padding: 10px 16px;
border-radius: var(--r-sm);
font-weight: 700;
}
.skip-link:focus { left: 12px; top: 12px; }
:focus-visible {
outline: 3px solid var(--neon);
outline-offset: 2px;
border-radius: 6px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.22em;
font-size: 0.72rem;
font-weight: 700;
color: var(--neon);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid var(--line-2);
background: var(--elevated);
color: var(--ink);
font-family: inherit;
font-weight: 700;
font-size: 0.95rem;
padding: 13px 20px;
border-radius: var(--r-md);
cursor: pointer;
transition: transform 0.12s ease, background 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn--sm { padding: 9px 16px; font-size: 0.85rem; }
.btn--neon {
background: var(--neon);
color: #0a0c0e;
border-color: var(--neon);
}
.btn--neon:hover { background: var(--neon-d); box-shadow: 0 8px 24px var(--neon-50); }
.btn--orange {
background: var(--orange);
color: #160a04;
border-color: var(--orange);
}
.btn--orange:hover { box-shadow: 0 8px 24px var(--orange-soft); }
.btn--ghost { background: transparent; }
.btn--block { width: 100%; }
/* Topbar */
.topbar {
position: sticky;
top: 0;
z-index: 30;
background: rgba(13, 15, 18, 0.82);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
display: flex;
align-items: center;
gap: 24px;
height: 66px;
}
.brand { display: inline-flex; align-items: center; gap: 9px; }
.brand__mark {
display: grid;
place-items: center;
width: 30px;
height: 30px;
background: var(--neon);
color: #0a0c0e;
border-radius: 9px;
font-size: 0.85rem;
transform: rotate(0deg);
}
.brand__name { font-weight: 900; letter-spacing: 0.14em; font-size: 1rem; }
.nav { margin-left: auto; display: flex; gap: 26px; }
.nav a {
color: var(--ink-2);
font-weight: 600;
font-size: 0.9rem;
transition: color 0.15s ease;
}
.nav a:hover { color: var(--ink); }
/* Hero */
.hero {
position: relative;
padding: 84px 0 64px;
overflow: hidden;
}
.hero::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(60% 70% at 82% -10%, var(--neon-50), transparent 60%),
radial-gradient(50% 60% at 8% 110%, var(--orange-soft), transparent 60%);
pointer-events: none;
}
.hero__inner { position: relative; }
.hero h1 {
font-weight: 900;
font-size: clamp(2.6rem, 6.5vw, 4.6rem);
margin: 16px 0 18px;
}
.hl { color: var(--neon); }
.hero__lead {
color: var(--ink-2);
max-width: 54ch;
font-size: 1.05rem;
}
.hero__stats { display: flex; gap: 14px; margin-top: 30px; flex-wrap: wrap; }
.stat {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 20px;
min-width: 120px;
}
.stat strong {
display: block;
font-size: 1.7rem;
font-weight: 900;
color: var(--ink);
}
.stat span {
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.68rem;
color: var(--muted);
font-weight: 600;
}
/* Roster */
.roster { padding: 24px 0 80px; }
.roster__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.roster__head h2 { font-size: clamp(1.5rem, 3vw, 2.1rem); font-weight: 800; }
.roster__sub { color: var(--muted); font-size: 0.9rem; font-weight: 600; }
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 26px;
}
.chip {
font-family: inherit;
font-weight: 700;
font-size: 0.85rem;
color: var(--ink-2);
background: var(--surface);
border: 1px solid var(--line-2);
padding: 9px 17px;
border-radius: 999px;
cursor: pointer;
transition: all 0.16s ease;
}
.chip:hover { color: var(--ink); border-color: var(--neon); }
.chip.is-active {
background: var(--neon);
color: #0a0c0e;
border-color: var(--neon);
}
/* Grid + cards */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 18px;
}
.card {
position: relative;
display: flex;
flex-direction: column;
text-align: left;
background: linear-gradient(180deg, var(--surface-2), var(--surface));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 18px;
cursor: pointer;
font-family: inherit;
color: inherit;
box-shadow: var(--shadow-1);
transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1),
opacity 0.35s ease, border-color 0.2s ease, box-shadow 0.25s ease;
}
.card:hover {
transform: translateY(-5px);
border-color: var(--line-2);
box-shadow: var(--shadow-2);
}
.card.is-hidden {
display: none;
opacity: 0;
transform: scale(0.94);
pointer-events: none;
}
.card__top { display: flex; align-items: center; gap: 14px; }
.avatar {
flex: none;
width: 60px;
height: 60px;
border-radius: 16px;
display: grid;
place-items: center;
font-weight: 900;
font-size: 1.25rem;
color: #0a0c0e;
}
.card__name { font-size: 1.12rem; font-weight: 800; }
.card__role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
font-weight: 700;
}
.rating {
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 4px;
font-size: 0.82rem;
font-weight: 700;
color: var(--warn);
}
.rating span { color: var(--muted); font-weight: 600; }
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 14px 0; }
.tag {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 4px 10px;
border-radius: 999px;
background: var(--neon-50);
color: var(--neon);
border: 1px solid rgba(198, 255, 58, 0.25);
}
.card__bio {
color: var(--ink-2);
font-size: 0.9rem;
margin-bottom: 14px;
}
.card__cert {
display: flex;
align-items: center;
gap: 7px;
font-size: 0.78rem;
color: var(--muted);
font-weight: 600;
margin-bottom: 16px;
}
.card__cert::before { content: "✓"; color: var(--ok); font-weight: 900; }
.card__foot { margin-top: auto; }
.empty {
text-align: center;
color: var(--muted);
padding: 48px 0;
font-weight: 600;
}
/* Footer */
.footer { border-top: 1px solid var(--line); padding: 30px 0; }
.footer__inner { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.footer p { color: var(--muted); font-size: 0.85rem; }
/* Modal */
.modal {
position: fixed;
inset: 0;
z-index: 60;
display: grid;
place-items: center;
padding: 18px;
}
.modal[hidden] { display: none; }
.modal__backdrop {
position: absolute;
inset: 0;
background: rgba(5, 6, 8, 0.74);
backdrop-filter: blur(4px);
animation: fade 0.2s ease;
}
.modal__panel {
position: relative;
width: min(620px, 100%);
max-height: 90vh;
overflow-y: auto;
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
box-shadow: var(--shadow-3);
animation: pop 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.modal__close {
position: absolute;
top: 14px;
right: 14px;
width: 38px;
height: 38px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--elevated);
color: var(--ink);
font-size: 0.9rem;
cursor: pointer;
z-index: 2;
transition: background 0.15s ease;
}
.modal__close:hover { background: var(--danger); color: #1a0808; }
.m-hero {
padding: 28px 28px 22px;
border-bottom: 1px solid var(--line);
display: flex;
gap: 18px;
align-items: center;
}
.m-hero .avatar { width: 76px; height: 76px; font-size: 1.6rem; }
.m-hero h3 { font-size: 1.5rem; font-weight: 900; }
.m-section { padding: 22px 28px; }
.m-section + .m-section { border-top: 1px solid var(--line); }
.m-section h4 {
margin: 0 0 12px;
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.72rem;
color: var(--neon);
font-weight: 800;
}
.m-section p { color: var(--ink-2); }
.ach-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.ach-list li {
display: flex;
gap: 10px;
align-items: flex-start;
color: var(--ink-2);
font-size: 0.92rem;
}
.ach-list li::before { content: "🏆"; flex: none; }
.slots { display: flex; flex-wrap: wrap; gap: 9px; }
.slot {
font-family: inherit;
font-weight: 700;
font-size: 0.85rem;
padding: 9px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--surface-2);
color: var(--ink);
cursor: pointer;
transition: all 0.15s ease;
}
.slot:hover:not(.is-taken) { border-color: var(--neon); color: var(--neon); }
.slot.is-selected { background: var(--neon); color: #0a0c0e; border-color: var(--neon); }
.slot.is-taken {
color: var(--muted);
cursor: not-allowed;
text-decoration: line-through;
opacity: 0.55;
}
.m-foot { padding: 0 28px 28px; }
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%) translateY(140%);
background: var(--elevated);
color: var(--ink);
border: 1px solid var(--line-2);
border-left: 4px solid var(--neon);
padding: 13px 20px;
border-radius: var(--r-md);
font-weight: 600;
font-size: 0.92rem;
box-shadow: var(--shadow-2);
z-index: 80;
opacity: 0;
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.3s ease;
max-width: min(90vw, 420px);
}
.toast.is-show { transform: translateX(-50%) translateY(0); opacity: 1; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop {
from { opacity: 0; transform: translateY(14px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; scroll-behavior: auto; }
}
@media (max-width: 520px) {
.nav { display: none; }
.topbar__inner { gap: 12px; }
.hero { padding: 56px 0 44px; }
.hero__stats { gap: 10px; }
.stat { min-width: calc(50% - 5px); flex: 1; padding: 12px 14px; }
.roster__head { flex-direction: column; gap: 4px; }
.grid { grid-template-columns: 1fr; }
.m-hero { flex-direction: column; text-align: center; }
}(function () {
"use strict";
/* ---------- Data ---------- */
var TRAINERS = [
{
id: "marcus",
name: "Marcus Vale",
role: "Head Strength Coach",
initials: "MV",
color: "#c6ff3a",
rating: 4.9,
reviews: 214,
specialties: ["Strength", "Mobility"],
certs: "NSCA-CSCS · 9 yrs",
bio: "Powerlifting background turned hybrid coach. Builds bulletproof joints before chasing big numbers.",
full: "Marcus spent six years on the competitive powerlifting circuit before moving into full-time coaching. His sessions blend heavy compound work with deliberate mobility drills so members get strong without breaking down. Expect honest feedback, clean technique, and a plan that scales with you.",
achievements: [
"Coached 30+ members to their first bodyweight deadlift",
"Regional Raw Powerlifting silver medalist, 2021",
"Author of the Ironforge Foundations strength template"
],
slots: [
{ label: "Mon 6:00 AM", taken: false },
{ label: "Mon 5:30 PM", taken: true },
{ label: "Wed 7:00 AM", taken: false },
{ label: "Thu 6:30 PM", taken: false },
{ label: "Sat 9:00 AM", taken: true }
]
},
{
id: "elena",
name: "Elena Brooks",
role: "Mobility & Recovery",
initials: "EB",
color: "#34d399",
rating: 5.0,
reviews: 168,
specialties: ["Mobility", "Yoga"],
certs: "FRC · RYT-500 · 7 yrs",
bio: "Movement specialist who turns stiff lifters into supple athletes with smart joint work.",
full: "Elena combines Functional Range Conditioning with a yoga foundation to rebuild range of motion that desk jobs and heavy training take away. Her work is the quiet reason a lot of Ironforge members stay injury-free season after season.",
achievements: [
"FRC Mobility Specialist, top-rated cohort",
"Led 200+ recovery workshops at Ironforge",
"Reduced reported nagging injuries by 40% in her clients"
],
slots: [
{ label: "Tue 8:00 AM", taken: false },
{ label: "Tue 12:00 PM", taken: false },
{ label: "Fri 7:30 AM", taken: true },
{ label: "Sun 10:00 AM", taken: false }
]
},
{
id: "darnell",
name: "Darnell Cruz",
role: "Boxing Coach",
initials: "DC",
color: "#ff6a2b",
rating: 4.8,
reviews: 191,
specialties: ["Boxing", "Strength"],
certs: "USA Boxing L2 · 11 yrs",
bio: "Ex-amateur boxer who teaches footwork first and conditioning that actually transfers.",
full: "Darnell came up in amateur boxing and now runs Ironforge's most popular conditioning classes. Whether you want to spar or just hit pads to blow off steam, he meets you where you are and sharpens fundamentals fast.",
achievements: [
"37-5 amateur record across two weight classes",
"Built the Ironforge Sweat Science boxing program",
"Corner-coached three regional Golden Gloves finalists"
],
slots: [
{ label: "Mon 7:00 PM", taken: false },
{ label: "Wed 6:00 AM", taken: true },
{ label: "Thu 7:00 PM", taken: false },
{ label: "Sat 11:00 AM", taken: false }
]
},
{
id: "priya",
name: "Priya Nair",
role: "Vinyasa & Breath",
initials: "PN",
color: "#a78bfa",
rating: 4.9,
reviews: 142,
specialties: ["Yoga", "Mobility"],
certs: "RYT-500 · 8 yrs",
bio: "Flow-based yoga with a strength bias — power and calm in the same hour.",
full: "Priya's classes pair athletic vinyasa flows with breath work that down-regulates a wired nervous system. Lifters love her for the shoulder and hip openers; everyone leaves a little taller.",
achievements: [
"Certified breath-work facilitator (Oxygen Advantage)",
"Hosts the monthly Ironforge Reset retreat day",
"Designed the pre-lift activation flow used gym-wide"
],
slots: [
{ label: "Tue 6:30 PM", taken: false },
{ label: "Thu 9:00 AM", taken: false },
{ label: "Fri 6:00 PM", taken: true },
{ label: "Sun 8:00 AM", taken: false }
]
},
{
id: "sofia",
name: "Sofia Reyes",
role: "Performance Nutrition",
initials: "SR",
color: "#fbbf24",
rating: 5.0,
reviews: 203,
specialties: ["Nutrition", "Strength"],
certs: "RD · Pn1 · 10 yrs",
bio: "Registered dietitian who builds eating plans you'll actually stick to.",
full: "Sofia is a registered dietitian focused on body-composition and performance fueling. No fad diets — just sustainable habits, smart macro targets, and check-ins that keep you honest while still letting you have a life.",
achievements: [
"Registered Dietitian, sports nutrition focus",
"Guided 120+ members through body-recomposition",
"Runs the Ironforge Fuel Lab cooking series"
],
slots: [
{ label: "Mon 11:00 AM", taken: false },
{ label: "Wed 1:00 PM", taken: true },
{ label: "Wed 5:00 PM", taken: false },
{ label: "Fri 10:00 AM", taken: false }
]
},
{
id: "theo",
name: "Theo Mwangi",
role: "Hypertrophy Coach",
initials: "TM",
color: "#60a5fa",
rating: 4.7,
reviews: 158,
specialties: ["Strength", "Nutrition"],
certs: "NASM-CPT · 6 yrs",
bio: "Bodybuilding-style coaching for members chasing serious muscle.",
full: "Theo lives for the pump. His detailed split programming and tempo work help intermediate lifters break plateaus, and he pairs training with simple nutrition nudges to keep gains coming.",
achievements: [
"Prepped 8 members for first-time physique shows",
"Built Ironforge's 12-week hypertrophy block",
"Top-rated coach for progress-photo transformations"
],
slots: [
{ label: "Tue 5:30 AM", taken: false },
{ label: "Thu 5:30 PM", taken: true },
{ label: "Sat 8:00 AM", taken: false },
{ label: "Sun 4:00 PM", taken: false }
]
}
];
/* ---------- Helpers ---------- */
var $ = function (sel, root) { return (root || document).querySelector(sel); };
var $$ = function (sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); };
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("is-show"); }, 2800);
}
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
/* ---------- Render grid ---------- */
var grid = $("#trainer-grid");
function cardHTML(t) {
var tags = t.specialties.map(function (s) { return '<span class="tag">' + esc(s) + "</span>"; }).join("");
return (
'<button class="card" type="button" data-id="' + t.id +
'" data-specialties="' + esc(t.specialties.join(",")) + '" aria-label="View profile for ' + esc(t.name) + '">' +
'<div class="card__top">' +
'<span class="avatar" style="background:' + t.color + '" aria-hidden="true">' + esc(t.initials) + "</span>" +
"<div>" +
'<div class="card__name">' + esc(t.name) + "</div>" +
'<div class="card__role">' + esc(t.role) + "</div>" +
'<div class="rating">★ ' + t.rating.toFixed(1) + " <span>(" + t.reviews + ")</span></div>" +
"</div>" +
"</div>" +
'<div class="tags">' + tags + "</div>" +
'<p class="card__bio">' + esc(t.bio) + "</p>" +
'<div class="card__cert">' + esc(t.certs) + "</div>" +
'<div class="card__foot"><span class="btn btn--neon btn--sm btn--block">Book session</span></div>' +
"</button>"
);
}
grid.innerHTML = TRAINERS.map(cardHTML).join("");
/* ---------- Filtering ---------- */
var countEl = $("#result-count");
var emptyEl = $("#empty-state");
var activeFilter = "all";
function applyFilter(filter) {
activeFilter = filter;
var visible = 0;
$$(".card", grid).forEach(function (card) {
var specs = card.getAttribute("data-specialties").split(",");
var match = filter === "all" || specs.indexOf(filter) !== -1;
card.classList.toggle("is-hidden", !match);
if (match) visible++;
});
emptyEl.hidden = visible !== 0;
countEl.textContent = visible + (visible === 1 ? " coach" : " coaches") +
(filter === "all" ? "" : " in " + filter);
}
$$(".chip").forEach(function (chip) {
chip.addEventListener("click", function () {
$$(".chip").forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
applyFilter("all");
/* ---------- Modal ---------- */
var modal = $("#modal");
var modalBody = $("#modal-body");
var lastFocused = null;
function modalHTML(t) {
var tags = t.specialties.map(function (s) { return '<span class="tag">' + esc(s) + "</span>"; }).join("");
var ach = t.achievements.map(function (a) { return "<li>" + esc(a) + "</li>"; }).join("");
var slots = t.slots.map(function (s) {
return '<button class="slot' + (s.taken ? " is-taken" : "") + '" type="button"' +
(s.taken ? " disabled aria-disabled=\"true\"" : "") +
' data-slot="' + esc(s.label) + '">' + esc(s.label) + "</button>";
}).join("");
return (
'<div class="m-hero">' +
'<span class="avatar" style="background:' + t.color + '" aria-hidden="true">' + esc(t.initials) + "</span>" +
"<div>" +
'<h3 id="modal-name">' + esc(t.name) + "</h3>" +
'<div class="card__role">' + esc(t.role) + "</div>" +
'<div class="rating">★ ' + t.rating.toFixed(1) + " <span>(" + t.reviews + " reviews)</span></div>" +
'<div class="tags">' + tags + "</div>" +
"</div>" +
"</div>" +
'<div class="m-section"><h4>About</h4><p>' + esc(t.full) + "</p>" +
'<div class="card__cert">' + esc(t.certs) + "</div></div>" +
'<div class="m-section"><h4>Achievements</h4><ul class="ach-list">' + ach + "</ul></div>" +
'<div class="m-section"><h4>Available slots</h4><div class="slots" id="slot-group">' + slots + "</div></div>" +
'<div class="m-foot"><button class="btn btn--neon btn--block" type="button" id="confirm-book" disabled>' +
"Select a slot to book</button></div>"
);
}
function openModal(id) {
var t = TRAINERS.filter(function (x) { return x.id === id; })[0];
if (!t) return;
lastFocused = document.activeElement;
modalBody.innerHTML = modalHTML(t);
modal.hidden = false;
document.body.style.overflow = "hidden";
var selected = null;
var confirmBtn = $("#confirm-book");
$$(".slot:not(.is-taken)", modalBody).forEach(function (slot) {
slot.addEventListener("click", function () {
$$(".slot", modalBody).forEach(function (s) { s.classList.remove("is-selected"); });
slot.classList.add("is-selected");
selected = slot.getAttribute("data-slot");
confirmBtn.disabled = false;
confirmBtn.textContent = "Book " + t.name.split(" ")[0] + " · " + selected;
});
});
confirmBtn.addEventListener("click", function () {
if (!selected) return;
closeModal();
toast("Session booked with " + t.name + " — " + selected + ". Check your email.");
});
var closeBtn = $(".modal__close", modal);
if (closeBtn) closeBtn.focus();
}
function closeModal() {
modal.hidden = true;
document.body.style.overflow = "";
if (lastFocused && lastFocused.focus) lastFocused.focus();
}
grid.addEventListener("click", function (e) {
var card = e.target.closest(".card");
if (card) openModal(card.getAttribute("data-id"));
});
modal.addEventListener("click", function (e) {
if (e.target.hasAttribute("data-close")) closeModal();
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !modal.hidden) closeModal();
});
/* ---------- Generic toast buttons ---------- */
$$("[data-toast]").forEach(function (btn) {
btn.addEventListener("click", function () { toast(btn.getAttribute("data-toast")); });
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ironforge — Meet the Coaches</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;900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#trainers">Skip to trainers</a>
<header class="topbar">
<div class="wrap topbar__inner">
<a class="brand" href="#" aria-label="Ironforge home">
<span class="brand__mark" aria-hidden="true">▲</span>
<span class="brand__name">IRONFORGE</span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#trainers">Coaches</a>
<a href="#">Programs</a>
<a href="#">Pricing</a>
</nav>
<button class="btn btn--neon btn--sm" type="button" data-toast="Free trial pass reserved — see you on the floor.">Free trial</button>
</div>
</header>
<main>
<section class="hero" aria-labelledby="hero-title">
<div class="wrap hero__inner">
<p class="eyebrow">The Coaching Roster</p>
<h1 id="hero-title">Coaches who<br /><span class="hl">push your limits.</span></h1>
<p class="hero__lead">
Every Ironforge coach is certified, obsessed with progress, and built to get you results.
Filter by what you want to train, then book a 1-on-1 session in seconds.
</p>
<div class="hero__stats" role="list">
<div class="stat" role="listitem"><strong>12</strong><span>Elite coaches</span></div>
<div class="stat" role="listitem"><strong>40k+</strong><span>Sessions run</span></div>
<div class="stat" role="listitem"><strong>4.9</strong><span>Avg rating</span></div>
</div>
</div>
</section>
<section id="trainers" class="roster" aria-labelledby="roster-title">
<div class="wrap">
<div class="roster__head">
<h2 id="roster-title">Find your coach</h2>
<p class="roster__sub" id="result-count" aria-live="polite"></p>
</div>
<div class="filters" role="group" aria-label="Filter trainers by specialty">
<button class="chip is-active" type="button" data-filter="all" aria-pressed="true">All</button>
<button class="chip" type="button" data-filter="Strength" aria-pressed="false">Strength</button>
<button class="chip" type="button" data-filter="Mobility" aria-pressed="false">Mobility</button>
<button class="chip" type="button" data-filter="Boxing" aria-pressed="false">Boxing</button>
<button class="chip" type="button" data-filter="Yoga" aria-pressed="false">Yoga</button>
<button class="chip" type="button" data-filter="Nutrition" aria-pressed="false">Nutrition</button>
</div>
<div class="grid" id="trainer-grid"></div>
<p class="empty" id="empty-state" hidden>No coaches match that specialty yet — try another filter.</p>
</div>
</section>
</main>
<footer class="footer">
<div class="wrap footer__inner">
<span class="brand__name">IRONFORGE</span>
<p>Fictional demo gym. All coaches, certs and slots are made up.</p>
</div>
</footer>
<!-- Trainer detail modal -->
<div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-name" hidden>
<div class="modal__backdrop" data-close></div>
<div class="modal__panel" role="document">
<button class="modal__close" type="button" data-close aria-label="Close coach profile">✕</button>
<div class="modal__body" id="modal-body"></div>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Trainers Page
A bold, athletic marketing page for a fictional gym’s coaching roster, built on the high-energy dark theme with neon-lime accents and orange highlights. The hero pairs an oversized headline with quick credibility stats, and a row of pill-shaped filter chips lets visitors narrow the team by specialty — Strength, Mobility, Boxing, Yoga or Nutrition. The grid below is fully responsive, collapsing to a single column on small screens.
Each trainer card shows a colored avatar, name and role, a star rating, specialty tags, a short bio, their certifications and a prominent neon Book session button. Selecting a chip filters the grid in place with a smooth fade-and-scale animation and a live result count, and an empty state appears if nothing matches.
Clicking a card opens an accessible detail modal — focus moves into the dialog, Escape and the backdrop close it, and focus returns to the originating card. Inside, members get the full bio, a list of achievements and a set of availability slots; taken slots are disabled, selecting an open slot enables the confirm button, and booking fires a toast confirmation. Everything runs on a single dependency-free vanilla JS file.
Illustrative UI only — coaches, certifications and time slots are fictional.