Gym — Classes Overview
A bold, dark-themed marketing page for a performance gym timetable, featuring a high-energy hero, a featured-class spotlight banner, category filter tabs across Strength, Cardio, Cycle, Yoga, HIIT and Mobility, and a responsive grid of class cards with gradient headers, intensity meters, trainer credits and schedule links. Filtering smoothly fades and staggers cards, and a free-trial CTA closes the page. Built with vanilla HTML, CSS and JavaScript — no frameworks or build step.
MCP
Código
: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 0 rgba(255, 255, 255, 0.04) inset,
0 8px 24px rgba(0, 0, 0, 0.45);
--shadow-2: 0 20px 60px rgba(0, 0, 0, 0.55);
}
* {
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;
text-rendering: optimizeLegibility;
}
h1,
h2,
h3 {
margin: 0;
letter-spacing: -0.02em;
line-height: 1.05;
}
a {
color: inherit;
text-decoration: none;
}
button {
font-family: inherit;
}
:focus-visible {
outline: 3px solid var(--neon);
outline-offset: 2px;
border-radius: var(--r-sm);
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
z-index: 100;
background: var(--neon);
color: #0a0c0e;
padding: 10px 16px;
font-weight: 700;
border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus {
left: 0;
}
.eyebrow {
margin: 0 0 14px;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--neon);
}
.eyebrow--orange {
color: var(--orange);
}
.eyebrow--dark {
color: rgba(13, 15, 18, 0.66);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid transparent;
border-radius: 999px;
padding: 14px 26px;
font-size: 0.95rem;
font-weight: 800;
letter-spacing: 0.01em;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.18s ease, background 0.18s ease,
border-color 0.18s ease;
}
.btn--sm {
padding: 10px 18px;
font-size: 0.85rem;
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn--neon {
background: var(--neon);
color: #0a0c0e;
box-shadow: 0 8px 24px rgba(198, 255, 58, 0.22);
}
.btn--neon:hover {
background: var(--neon-d);
box-shadow: 0 10px 30px rgba(198, 255, 58, 0.3);
}
.btn--orange {
background: var(--orange);
color: #160a04;
box-shadow: 0 8px 24px rgba(255, 106, 43, 0.28);
}
.btn--orange:hover {
filter: brightness(1.08);
}
.btn--ghost {
background: transparent;
color: var(--ink);
border-color: var(--line-2);
}
.btn--ghost:hover {
background: rgba(255, 255, 255, 0.05);
border-color: var(--neon);
}
.btn--dark {
background: #0d0f12;
color: var(--neon);
box-shadow: var(--shadow-1);
}
.btn--dark:hover {
background: #07080a;
}
/* ---------- Topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 40;
background: rgba(13, 15, 18, 0.82);
backdrop-filter: blur(14px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
max-width: 1180px;
margin: 0 auto;
padding: 14px 24px;
display: flex;
align-items: center;
gap: 24px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 900;
letter-spacing: 0.04em;
}
.brand__mark {
color: var(--neon);
font-weight: 900;
font-size: 1.1rem;
}
.topnav {
display: flex;
gap: 22px;
margin-left: auto;
font-size: 0.9rem;
font-weight: 600;
color: var(--ink-2);
}
.topnav a:hover {
color: var(--neon);
}
.topbar .btn--neon {
margin-left: 4px;
}
/* ---------- Hero ---------- */
.hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--line);
}
.hero__glow {
position: absolute;
inset: -40% 30% auto -10%;
height: 560px;
background: radial-gradient(
600px 360px at 20% 20%,
var(--neon-50),
transparent 70%
),
radial-gradient(500px 320px at 90% 10%, var(--orange-soft), transparent 70%);
filter: blur(10px);
pointer-events: none;
}
.hero__inner {
position: relative;
max-width: 1180px;
margin: 0 auto;
padding: 84px 24px 72px;
}
.hero__title {
font-size: clamp(2.6rem, 7vw, 5rem);
font-weight: 900;
}
.hero__accent {
color: var(--neon);
}
.hero__sub {
max-width: 540px;
margin: 22px 0 30px;
color: var(--ink-2);
font-size: 1.08rem;
}
.hero__actions {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.hero__stats {
display: flex;
gap: 38px;
flex-wrap: wrap;
list-style: none;
margin: 48px 0 0;
padding: 0;
}
.hero__stats li {
display: flex;
flex-direction: column;
}
.hero__stats strong {
font-size: 1.8rem;
font-weight: 900;
}
.hero__stats span {
font-size: 0.75rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
margin-top: 4px;
}
/* ---------- Spotlight ---------- */
.spotlight {
max-width: 1180px;
margin: 64px auto;
padding: 0 24px;
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 0;
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
background: var(--surface);
box-shadow: var(--shadow-2);
}
.spotlight__media {
position: relative;
min-height: 320px;
background: linear-gradient(135deg, #2a1206, #ff6a2b 55%, #ffb37a);
}
.spotlight__media::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(
-45deg,
rgba(0, 0, 0, 0.16) 0 14px,
transparent 14px 28px
);
}
.spotlight__badge {
position: absolute;
top: 18px;
left: 18px;
z-index: 2;
background: #0d0f12;
color: var(--orange);
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 8px 14px;
border-radius: 999px;
}
.spotlight__body {
padding: 40px 38px;
}
.spotlight__body h2 {
font-size: clamp(1.5rem, 3.4vw, 2.1rem);
font-weight: 800;
}
.spotlight__text {
color: var(--ink-2);
margin: 16px 0 22px;
}
.spotlight__meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 26px;
}
.chip {
font-size: 0.8rem;
font-weight: 700;
color: var(--ink-2);
background: var(--surface-2);
border: 1px solid var(--line);
padding: 7px 13px;
border-radius: 999px;
}
.chip--hot {
color: var(--orange);
border-color: rgba(255, 106, 43, 0.4);
background: var(--orange-soft);
}
/* ---------- Classes section ---------- */
.classes {
max-width: 1180px;
margin: 0 auto;
padding: 24px 24px 72px;
}
.section-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 26px;
}
.section-head h2 {
font-size: clamp(1.7rem, 4vw, 2.6rem);
font-weight: 900;
}
.section-head__count {
margin: 0;
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
white-space: nowrap;
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 32px;
}
.filter {
background: var(--surface);
border: 1px solid var(--line);
color: var(--ink-2);
font-size: 0.88rem;
font-weight: 700;
letter-spacing: 0.02em;
padding: 10px 18px;
border-radius: 999px;
cursor: pointer;
transition: all 0.16s ease;
}
.filter:hover {
border-color: var(--line-2);
color: var(--ink);
}
.filter.is-active {
background: var(--neon);
color: #0a0c0e;
border-color: var(--neon);
box-shadow: 0 6px 18px rgba(198, 255, 58, 0.22);
}
/* ---------- Grid + cards ---------- */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
gap: 20px;
}
.card {
display: flex;
flex-direction: column;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-1);
transition: transform 0.18s ease, border-color 0.18s ease,
box-shadow 0.18s ease, opacity 0.32s ease;
}
.card:hover {
transform: translateY(-4px);
border-color: var(--line-2);
box-shadow: var(--shadow-2);
}
/* fade/reveal states driven by JS */
.card.is-hidden {
display: none;
}
.card.is-enter {
opacity: 0;
transform: translateY(10px);
}
.card.is-in {
opacity: 1;
transform: translateY(0);
}
.card__header {
position: relative;
height: 132px;
display: flex;
align-items: flex-end;
padding: 14px;
}
.card__header::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
transparent 30%,
rgba(13, 15, 18, 0.55)
);
}
.card__cat {
position: relative;
z-index: 2;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #0a0c0e;
background: rgba(244, 246, 248, 0.92);
padding: 6px 11px;
border-radius: 999px;
}
.card__time {
position: absolute;
z-index: 2;
top: 14px;
right: 14px;
font-size: 0.74rem;
font-weight: 800;
color: var(--ink);
background: rgba(13, 15, 18, 0.7);
border: 1px solid var(--line-2);
padding: 6px 11px;
border-radius: 999px;
}
/* gradient header variants per category */
.h-strength {
background: linear-gradient(135deg, #1b1500, #c6ff3a 120%);
}
.h-cardio {
background: linear-gradient(135deg, #2a0a04, #ff6a2b);
}
.h-cycle {
background: linear-gradient(135deg, #04122a, #2b8cff);
}
.h-yoga {
background: linear-gradient(135deg, #1a0420, #b86bff);
}
.h-hiit {
background: linear-gradient(135deg, #2a0410, #f8417a);
}
.h-mobility {
background: linear-gradient(135deg, #04221c, #34d399);
}
.card__body {
padding: 18px 18px 20px;
display: flex;
flex-direction: column;
flex: 1;
}
.card__name {
font-size: 1.18rem;
font-weight: 800;
}
.card__blurb {
margin: 8px 0 16px;
color: var(--muted);
font-size: 0.9rem;
flex: 1;
}
.intensity {
margin: 0 0 14px;
}
.intensity__label {
display: flex;
justify-content: space-between;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 7px;
}
.intensity__bars {
display: flex;
gap: 5px;
}
.intensity__bar {
flex: 1;
height: 6px;
border-radius: 3px;
background: var(--elevated);
}
.intensity__bar.on {
background: var(--neon);
}
.card__foot {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 14px;
border-top: 1px solid var(--line);
}
.trainer {
display: flex;
align-items: center;
gap: 10px;
}
.trainer__avatar {
width: 34px;
height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 800;
font-size: 0.8rem;
color: #0a0c0e;
background: var(--neon);
}
.trainer__name {
font-size: 0.82rem;
font-weight: 700;
}
.trainer__role {
font-size: 0.7rem;
color: var(--muted);
}
.card__link {
font-size: 0.84rem;
font-weight: 800;
color: var(--neon);
display: inline-flex;
align-items: center;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 6px 4px;
}
.card__link:hover {
color: var(--neon-d);
}
.card__link span {
transition: transform 0.16s ease;
}
.card__link:hover span {
transform: translateX(3px);
}
.empty {
text-align: center;
color: var(--muted);
font-weight: 600;
padding: 48px 0;
}
/* ---------- CTA ---------- */
.cta {
max-width: 1180px;
margin: 0 auto 72px;
padding: 0 24px;
}
.cta__inner {
background: linear-gradient(120deg, var(--neon), var(--neon-d));
color: #0a0c0e;
border-radius: var(--r-lg);
padding: 48px 44px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
flex-wrap: wrap;
box-shadow: 0 24px 60px rgba(198, 255, 58, 0.18);
}
.cta__inner h2 {
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 900;
color: #0a0c0e;
}
.cta__text {
margin: 10px 0 0;
color: rgba(13, 15, 18, 0.74);
font-weight: 600;
}
/* ---------- Footer ---------- */
.footer {
border-top: 1px solid var(--line);
padding: 32px 24px;
text-align: center;
color: var(--muted);
font-size: 0.85rem;
}
.footer__sub {
margin: 6px 0 0;
font-size: 0.78rem;
color: rgba(139, 146, 156, 0.7);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 24px);
background: var(--elevated);
color: var(--ink);
border: 1px solid var(--line-2);
padding: 14px 22px;
border-radius: var(--r-md);
font-weight: 700;
font-size: 0.92rem;
box-shadow: var(--shadow-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 80;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 860px) {
.spotlight {
grid-template-columns: 1fr;
}
.spotlight__media {
min-height: 200px;
}
}
@media (max-width: 520px) {
.topnav {
display: none;
}
.topbar__inner {
gap: 12px;
}
.hero__inner {
padding: 56px 18px 48px;
}
.hero__stats {
gap: 24px;
margin-top: 36px;
}
.hero__actions .btn {
flex: 1;
}
.spotlight,
.classes,
.cta {
padding-left: 18px;
padding-right: 18px;
}
.spotlight {
margin: 40px auto;
}
.spotlight__body {
padding: 28px 22px;
}
.section-head {
flex-direction: column;
align-items: flex-start;
}
.grid {
grid-template-columns: 1fr;
}
.cta__inner {
padding: 32px 24px;
}
.cta__inner .btn {
width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
* {
scroll-behavior: auto !important;
transition: none !important;
}
}// ---------------------------------------------------------------------------
// Ironpulse — Classes Overview
// Vanilla JS: render class grid, category filtering with fade reveal, toasts.
// ---------------------------------------------------------------------------
(function () {
"use strict";
/** Class catalog — fictional but realistic data. */
const CLASSES = [
{
cat: "strength",
label: "Strength",
name: "Barbell Foundations",
blurb: "Squat, hinge, press. Build raw force with progressive barbell work and clean technique.",
duration: "60 min",
intensity: 4,
trainer: "Marcus Vega",
role: "Head S&C Coach",
},
{
cat: "strength",
label: "Strength",
name: "Iron Hour",
blurb: "Hypertrophy-focused supersets across push, pull, and legs. Leave with a real pump.",
duration: "55 min",
intensity: 3,
trainer: "Dana Okafor",
role: "Strength Coach",
},
{
cat: "cardio",
label: "Cardio",
name: "Tread & Shred",
blurb: "Treadmill intervals mixed with floor circuits to torch calories and lift your engine.",
duration: "45 min",
intensity: 4,
trainer: "Priya Anand",
role: "Conditioning Coach",
},
{
cat: "cardio",
label: "Cardio",
name: "Row Republic",
blurb: "Low-impact, high-output rowing intervals. Big aerobic gains, easy on the joints.",
duration: "40 min",
intensity: 3,
trainer: "Leo Marsh",
role: "Endurance Coach",
},
{
cat: "cycle",
label: "Cycle",
name: "Night Ride",
blurb: "Beat-matched indoor cycling in the dark with neon visuals and relentless climbs.",
duration: "45 min",
intensity: 4,
trainer: "Sofia Reyes",
role: "Ride Captain",
},
{
cat: "cycle",
label: "Cycle",
name: "Power Sprint",
blurb: "Short, savage cycle sprints built around wattage targets and recovery blocks.",
duration: "30 min",
intensity: 5,
trainer: "Sofia Reyes",
role: "Ride Captain",
},
{
cat: "yoga",
label: "Yoga",
name: "Flow & Restore",
blurb: "A slow vinyasa flow into deep restorative holds. Reset your nervous system.",
duration: "60 min",
intensity: 2,
trainer: "Hannah Lim",
role: "Yoga Instructor",
},
{
cat: "yoga",
label: "Yoga",
name: "Power Vinyasa",
blurb: "Heated, breath-led flow that builds heat, balance, and serious core control.",
duration: "55 min",
intensity: 3,
trainer: "Hannah Lim",
role: "Yoga Instructor",
},
{
cat: "hiit",
label: "HIIT",
name: "Engine Room",
blurb: "Rowers, bikes, and barbell complexes in timed intervals. Featured 6-week series.",
duration: "45 min",
intensity: 5,
trainer: "Marcus Vega",
role: "Head S&C Coach",
},
{
cat: "hiit",
label: "HIIT",
name: "Tabata 20",
blurb: "Twenty minutes, eight rounds, zero excuses. Bodyweight intervals at max effort.",
duration: "30 min",
intensity: 5,
trainer: "Priya Anand",
role: "Conditioning Coach",
},
{
cat: "mobility",
label: "Mobility",
name: "Joints & Jelly",
blurb: "Controlled articular rotations and loaded stretching to bulletproof your joints.",
duration: "40 min",
intensity: 1,
trainer: "Leo Marsh",
role: "Endurance Coach",
},
{
cat: "mobility",
label: "Mobility",
name: "Deep Reset",
blurb: "Foam rolling, breathwork, and mobility drills to unwind after a heavy training week.",
duration: "35 min",
intensity: 2,
trainer: "Dana Okafor",
role: "Strength Coach",
},
];
const grid = document.getElementById("grid");
const empty = document.getElementById("empty");
const countEl = document.querySelector("[data-count]");
const filterBtns = Array.from(document.querySelectorAll(".filter"));
const toastEl = document.getElementById("toast");
let activeFilter = "all";
let toastTimer = null;
/** Initials for the trainer avatar. */
function initials(name) {
return name
.split(" ")
.map((p) => p[0])
.join("")
.slice(0, 2)
.toUpperCase();
}
/** Build the intensity meter (5 bars). */
function intensityBars(level) {
let bars = "";
for (let i = 1; i <= 5; i++) {
bars += `<span class="intensity__bar${i <= level ? " on" : ""}"></span>`;
}
return bars;
}
/** Render one card element from a class record. */
function makeCard(c) {
const el = document.createElement("article");
el.className = "card";
el.dataset.cat = c.cat;
el.innerHTML = `
<div class="card__header h-${c.cat}">
<span class="card__cat">${c.label}</span>
<span class="card__time">${c.duration}</span>
</div>
<div class="card__body">
<h3 class="card__name">${c.name}</h3>
<p class="card__blurb">${c.blurb}</p>
<div class="intensity">
<div class="intensity__label">
<span>Intensity</span><span>${c.intensity}/5</span>
</div>
<div class="intensity__bars" role="img" aria-label="Intensity ${c.intensity} out of 5">
${intensityBars(c.intensity)}
</div>
</div>
<div class="card__foot">
<div class="trainer">
<span class="trainer__avatar" aria-hidden="true">${initials(c.trainer)}</span>
<span>
<span class="trainer__name">${c.trainer}</span><br />
<span class="trainer__role">${c.role}</span>
</span>
</div>
<button class="card__link" data-schedule="${c.name}">
See schedule <span aria-hidden="true">→</span>
</button>
</div>
</div>`;
return el;
}
/** Initial render of all cards. */
function render() {
const frag = document.createDocumentFragment();
CLASSES.forEach((c) => frag.appendChild(makeCard(c)));
grid.appendChild(frag);
}
/** Apply the active filter with a staggered fade-in reveal. */
function applyFilter(filter) {
activeFilter = filter;
const cards = Array.from(grid.children);
let shown = 0;
let stagger = 0;
cards.forEach((card) => {
const match = filter === "all" || card.dataset.cat === filter;
if (match) {
shown++;
card.classList.remove("is-hidden");
card.classList.add("is-enter");
// stagger the reveal for a smooth cascade
const delay = stagger;
stagger += 45;
requestAnimationFrame(() => {
card.style.transitionDelay = delay + "ms";
requestAnimationFrame(() => {
card.classList.remove("is-enter");
card.classList.add("is-in");
});
});
// clear the delay after the reveal so hover stays snappy
window.setTimeout(() => {
card.style.transitionDelay = "";
}, delay + 400);
} else {
card.classList.add("is-hidden");
card.classList.remove("is-enter", "is-in");
}
});
empty.hidden = shown > 0;
updateCount(shown);
}
function updateCount(n) {
const word = n === 1 ? "class" : "classes";
const scope = activeFilter === "all" ? "all" : activeFilter;
countEl.textContent = `${n} ${word} · ${scope}`;
}
function setActiveButton(btn) {
filterBtns.forEach((b) => {
const on = b === btn;
b.classList.toggle("is-active", on);
b.setAttribute("aria-selected", on ? "true" : "false");
});
}
/** Lightweight toast helper. */
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
if (toastTimer) clearTimeout(toastTimer);
toastTimer = window.setTimeout(() => {
toastEl.classList.remove("show");
}, 2600);
}
// ----- Wire up events -----
filterBtns.forEach((btn) => {
btn.addEventListener("click", () => {
setActiveButton(btn);
applyFilter(btn.dataset.filter);
});
});
// Delegated clicks for "See schedule" + booking CTAs.
document.addEventListener("click", (e) => {
const schedule = e.target.closest("[data-schedule]");
if (schedule) {
toast(`Opening the schedule for “${schedule.dataset.schedule}”…`);
return;
}
const book = e.target.closest("[data-book]");
if (book) {
toast(`Nice — we saved you a ${book.dataset.book}. Check your email!`);
}
});
// ----- Boot -----
render();
applyFilter("all");
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ironpulse — Classes Overview</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="#classes">Skip to classes</a>
<header class="topbar">
<div class="topbar__inner">
<a class="brand" href="#" aria-label="Ironpulse home">
<span class="brand__mark" aria-hidden="true">/\</span>
<span class="brand__name">IRONPULSE</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#classes">Classes</a>
<a href="#spotlight">Spotlight</a>
<a href="#trial">Trial</a>
</nav>
<button class="btn btn--neon btn--sm" data-book="Free Trial Pass">
Join now
</button>
</div>
</header>
<main>
<!-- HERO -->
<section class="hero" aria-labelledby="hero-title">
<div class="hero__glow" aria-hidden="true"></div>
<div class="hero__inner">
<p class="eyebrow">42 classes / week · 9 coaches</p>
<h1 id="hero-title" class="hero__title">
Train loud.<br />
<span class="hero__accent">Move with purpose.</span>
</h1>
<p class="hero__sub">
Strength, sweat, and stillness under one roof. Filter the timetable,
find your coach, and lock in a session that actually fits your week.
</p>
<div class="hero__actions">
<button class="btn btn--neon" data-book="Free Trial Pass">
Book a free trial
</button>
<a class="btn btn--ghost" href="#classes">Browse classes</a>
</div>
<ul class="hero__stats" aria-label="Studio highlights">
<li><strong>6</strong><span>Class styles</span></li>
<li><strong>30–60</strong><span>Min sessions</span></li>
<li><strong>4.9</strong><span>Member rating</span></li>
</ul>
</div>
</section>
<!-- SPOTLIGHT -->
<section id="spotlight" class="spotlight" aria-labelledby="spotlight-title">
<div class="spotlight__media" aria-hidden="true">
<span class="spotlight__badge">Featured this month</span>
</div>
<div class="spotlight__body">
<p class="eyebrow eyebrow--orange">Limited series · HIIT</p>
<h2 id="spotlight-title">Engine Room: 6-Week Conditioning</h2>
<p class="spotlight__text">
A progressive interval block built to spike your VO₂ max. Rowers,
assault bikes, and barbell complexes — coached live by Marcus Vega.
Caps at 14 athletes so every rep gets eyes on it.
</p>
<div class="spotlight__meta">
<span class="chip">Tue / Thu · 6:15 AM</span>
<span class="chip">45 min</span>
<span class="chip chip--hot">3 spots left</span>
</div>
<button class="btn btn--orange" data-book="Engine Room series">
Reserve your spot
</button>
</div>
</section>
<!-- CLASSES -->
<section id="classes" class="classes" aria-labelledby="classes-title">
<div class="section-head">
<div>
<p class="eyebrow">The timetable</p>
<h2 id="classes-title">Find your class</h2>
</div>
<p class="section-head__count" data-count aria-live="polite"></p>
</div>
<div class="filters" role="tablist" aria-label="Filter classes by category">
<button class="filter is-active" role="tab" aria-selected="true" data-filter="all">All</button>
<button class="filter" role="tab" aria-selected="false" data-filter="strength">Strength</button>
<button class="filter" role="tab" aria-selected="false" data-filter="cardio">Cardio</button>
<button class="filter" role="tab" aria-selected="false" data-filter="cycle">Cycle</button>
<button class="filter" role="tab" aria-selected="false" data-filter="yoga">Yoga</button>
<button class="filter" role="tab" aria-selected="false" data-filter="hiit">HIIT</button>
<button class="filter" role="tab" aria-selected="false" data-filter="mobility">Mobility</button>
</div>
<div class="grid" id="grid">
<!-- cards injected here -->
</div>
<p class="empty" id="empty" hidden>
No classes in this category yet — try another filter.
</p>
</section>
<!-- TRIAL CTA -->
<section id="trial" class="cta" aria-labelledby="cta-title">
<div class="cta__inner">
<div>
<p class="eyebrow eyebrow--dark">No contract · No catch</p>
<h2 id="cta-title">Your first session is on us.</h2>
<p class="cta__text">
Walk in, get a coach-led intro, and try any class on the board.
</p>
</div>
<button class="btn btn--dark" data-book="Free Trial Pass">
Claim free trial
</button>
</div>
</section>
</main>
<footer class="footer">
<p>© 2026 Ironpulse Strength & Conditioning · 14 Forge Lane</p>
<p class="footer__sub">Coaches: real people. Data: very fictional.</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Classes Overview
A self-contained marketing page for Ironpulse, a fictional strength-and-conditioning gym. It opens with a high-energy hero (headline, stats, and a free-trial CTA), then a featured-class spotlight banner for a limited HIIT series. The core of the page is a filterable timetable: a row of category tabs (All, Strength, Cardio, Cycle, Yoga, HIIT, Mobility) sits above a responsive grid of class cards.
Each card pairs a category-colored gradient header with the class name, a short blurb, duration, a five-bar intensity meter, the assigned trainer (with initials avatar and role), and a “See schedule” link. The grid uses auto-fill so it reflows from three columns down to a single column at ~360px.
The vanilla JS renders the cards from a data array, then handles filtering: selecting a tab marks the active button (aria-selected), shows the matching cards, and reveals them with a staggered fade so the transition reads as intentional rather than a hard cut. A live count announces how many classes match, an empty state appears when a category has none, and a small toast() helper confirms schedule views and trial bookings. The whole page respects prefers-reduced-motion and keeps WCAG AA contrast on the dark theme.