Gym — Boutique Cycling / HIIT Landing
A club-energy landing page for a boutique cycling and HIIT studio, built in vanilla HTML, CSS and JS. Features a neon-glow hero with a free-ride CTA, a live class-time rotator with dynamic spot counts, vivid signature-class cards, an instructor and DJ-energy crew row, an experience grid, a packs pricing teaser, rider testimonials and a validated claim form. Deep-purple palette with neon-pink gradients, reactive lighting vibes, scroll reveals and toast feedback.
MCP
Código
:root {
--bg: #140a24;
--surface: #1e1036;
--surface-2: #271447;
--elevated: #2f1a55;
--ink: #fdf7ff;
--ink-2: #d3c4e8;
--muted: #9a8bb5;
--pink: #ff2fb0;
--pink-d: #e0179a;
--violet: #8b5cf6;
--cyan: #22d3ee;
--glow: rgba(255, 47, 176, 0.4);
--line: rgba(255, 255, 255, 0.12);
--line-2: rgba(255, 255, 255, 0.2);
--ok: #34d399;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
--shadow-glow: 0 0 40px -8px var(--glow);
}
*,
*::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;
text-rendering: optimizeLegibility;
overflow-x: hidden;
}
/* subtle club ambience */
body::before {
content: "";
position: fixed;
inset: 0;
background:
radial-gradient(60% 50% at 80% -5%, rgba(139, 92, 246, 0.18), transparent 70%),
radial-gradient(50% 40% at 0% 100%, rgba(255, 47, 176, 0.14), transparent 70%);
pointer-events: none;
z-index: 0;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; line-height: 1.05; }
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
.grad {
background: linear-gradient(100deg, var(--pink), var(--violet) 55%, var(--cyan));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.22em;
font-size: 0.72rem;
font-weight: 700;
color: var(--pink);
margin: 0 0 0.9rem;
}
/* ===== BUTTONS ===== */
.btn {
--b-bg: transparent;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font: inherit;
font-weight: 700;
cursor: pointer;
border: 1px solid transparent;
border-radius: 999px;
padding: 0.62rem 1.15rem;
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease,
border-color 0.2s ease, color 0.2s ease;
white-space: nowrap;
}
.btn:focus-visible {
outline: 3px solid var(--cyan);
outline-offset: 2px;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn-primary {
background: linear-gradient(100deg, var(--pink), var(--pink-d));
color: #190826;
box-shadow: 0 8px 24px -6px var(--glow);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 14px 34px -6px var(--glow), 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
}
.btn-outline {
border-color: var(--line-2);
color: var(--ink);
background: rgba(255, 255, 255, 0.03);
}
.btn-outline:hover {
border-color: var(--pink);
color: var(--ink);
box-shadow: 0 0 22px -10px var(--glow);
transform: translateY(-2px);
}
.btn-ghost { color: var(--ink-2); }
.btn-ghost:hover { color: var(--ink); }
.btn-lg { padding: 0.9rem 1.6rem; font-size: 1.02rem; }
/* ===== NAV ===== */
.nav {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1rem clamp(1rem, 4vw, 3rem);
background: rgba(20, 10, 36, 0.55);
backdrop-filter: blur(14px);
border-bottom: 1px solid transparent;
transition: background 0.25s ease, border-color 0.25s ease;
}
.nav.scrolled {
background: rgba(20, 10, 36, 0.88);
border-bottom-color: var(--line);
}
.brand {
font-family: "Anton", "Inter", sans-serif;
font-size: 1.5rem;
letter-spacing: 0.04em;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.brand-sub { color: var(--pink); }
.brand-dot {
width: 11px; height: 11px;
border-radius: 50%;
background: var(--pink);
box-shadow: 0 0 14px 2px var(--glow);
animation: pulse 1.8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.45; transform: scale(0.78); }
}
.nav-links {
display: flex;
gap: 1.4rem;
margin-left: auto;
font-weight: 600;
font-size: 0.95rem;
}
.nav-links a { color: var(--ink-2); position: relative; padding: 0.25rem 0; }
.nav-links a::after {
content: "";
position: absolute;
left: 0; bottom: -2px;
height: 2px; width: 0;
background: var(--pink);
transition: width 0.2s ease;
}
.nav-links a:hover { color: var(--ink); }
.nav-links a:hover::after { width: 100%; }
.nav-cta { display: flex; gap: 0.6rem; align-items: center; }
.nav-toggle {
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: 0;
cursor: pointer;
padding: 6px;
margin-left: auto;
}
.nav-toggle span {
width: 26px; height: 2px;
background: var(--ink);
border-radius: 2px;
transition: transform 0.25s ease, opacity 0.25s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.mobile-menu {
position: sticky;
top: 64px;
z-index: 49;
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 1rem clamp(1rem, 4vw, 3rem) 1.4rem;
background: rgba(30, 16, 54, 0.97);
border-bottom: 1px solid var(--line);
}
.mobile-menu[hidden] { display: none; }
.mobile-menu a {
padding: 0.7rem 0.2rem;
font-weight: 600;
color: var(--ink-2);
border-bottom: 1px solid var(--line);
}
.mobile-menu a.btn { border-bottom: 0; margin-top: 0.6rem; }
/* ===== HERO ===== */
.hero {
position: relative;
z-index: 1;
padding: clamp(3.5rem, 9vw, 7rem) clamp(1rem, 4vw, 3rem) clamp(3rem, 7vw, 5rem);
max-width: 1180px;
margin: 0 auto;
overflow: hidden;
}
.hero-glow {
position: absolute;
border-radius: 50%;
filter: blur(90px);
pointer-events: none;
z-index: -1;
animation: drift 9s ease-in-out infinite alternate;
}
.hero-glow-a {
width: 460px; height: 460px;
background: radial-gradient(circle, rgba(255, 47, 176, 0.55), transparent 65%);
top: -120px; right: -60px;
}
.hero-glow-b {
width: 420px; height: 420px;
background: radial-gradient(circle, rgba(139, 92, 246, 0.5), transparent 65%);
bottom: -160px; left: -80px;
animation-delay: -3s;
}
@keyframes drift {
from { transform: translate(0, 0) scale(1); }
to { transform: translate(28px, -24px) scale(1.08); }
}
.hero-inner { max-width: 720px; }
.hero-title {
font-family: "Anton", "Inter", sans-serif;
font-weight: 400;
font-size: clamp(2.9rem, 11vw, 6.2rem);
letter-spacing: 0.01em;
text-transform: uppercase;
margin-bottom: 1.2rem;
text-shadow: 0 0 60px rgba(255, 47, 176, 0.25);
}
.hero-lead {
font-size: clamp(1.02rem, 2.4vw, 1.22rem);
color: var(--ink-2);
max-width: 560px;
margin: 0 0 1.8rem;
}
.hero-actions { display: flex; gap: 0.8rem; flex-wrap: wrap; margin-bottom: 2.6rem; }
.hero-stats {
list-style: none;
margin: 0; padding: 0;
display: flex;
gap: clamp(1.4rem, 5vw, 3rem);
}
.hero-stats li { display: flex; flex-direction: column; }
.hero-stats strong {
font-family: "Anton", "Inter", sans-serif;
font-size: clamp(1.5rem, 4vw, 2.1rem);
background: linear-gradient(120deg, var(--pink), var(--violet));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero-stats span {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
}
.next-ride {
margin-top: 2.6rem;
display: flex;
align-items: center;
gap: 1.1rem;
padding: 0.9rem 1.1rem;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: linear-gradient(120deg, rgba(255, 47, 176, 0.08), rgba(139, 92, 246, 0.06));
max-width: 560px;
box-shadow: var(--shadow-glow);
}
.next-ride-label {
font-size: 0.66rem;
text-transform: uppercase;
letter-spacing: 0.16em;
font-weight: 700;
color: var(--pink);
white-space: nowrap;
}
.next-ride-body { display: flex; flex-direction: column; flex: 1; transition: opacity 0.3s ease; }
.next-ride-body.swap { opacity: 0; }
.next-ride-name { font-size: 1.02rem; }
.next-ride-meta { font-size: 0.85rem; color: var(--ink-2); }
.next-ride-spots {
font-size: 0.8rem;
font-weight: 700;
color: var(--bg);
background: var(--ok);
padding: 0.3rem 0.6rem;
border-radius: 999px;
white-space: nowrap;
}
/* ===== SECTIONS ===== */
.section {
position: relative;
z-index: 1;
max-width: 1180px;
margin: 0 auto;
padding: clamp(3.5rem, 8vw, 6rem) clamp(1rem, 4vw, 3rem);
}
.section--tight { padding-top: clamp(2rem, 5vw, 3rem); }
.section-head { max-width: 640px; margin: 0 auto clamp(2.2rem, 5vw, 3.4rem); text-align: center; }
.section-title {
font-family: "Anton", "Inter", sans-serif;
font-weight: 400;
font-size: clamp(2rem, 6vw, 3.4rem);
text-transform: uppercase;
letter-spacing: 0.01em;
margin-bottom: 0.7rem;
}
.section-sub { color: var(--ink-2); font-size: 1.02rem; margin: 0; }
/* ===== CLASS CARDS ===== */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.2rem;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.6rem 1.5rem;
overflow: hidden;
transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}
.card::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(160deg, var(--card-tint, rgba(255, 47, 176, 0.14)), transparent 55%);
opacity: 0.9;
pointer-events: none;
}
.card--ride { --card-tint: rgba(255, 47, 176, 0.18); }
.card--rhythm { --card-tint: rgba(139, 92, 246, 0.18); }
.card--hiit { --card-tint: rgba(34, 211, 238, 0.16); }
.card:hover {
transform: translateY(-6px);
border-color: var(--line-2);
box-shadow: var(--shadow), var(--shadow-glow);
}
.card > * { position: relative; }
.card-badge {
display: inline-block;
font-size: 0.68rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #190826;
background: var(--pink);
padding: 0.28rem 0.6rem;
border-radius: 999px;
margin-bottom: 1rem;
}
.card-badge--alt { background: var(--violet); color: var(--ink); }
.card-name {
font-family: "Anton", "Inter", sans-serif;
font-weight: 400;
font-size: 2rem;
text-transform: uppercase;
}
.card-tag {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.74rem;
font-weight: 700;
color: var(--muted);
margin: 0.2rem 0 0.9rem;
}
.card-desc { color: var(--ink-2); margin: 0 0 1.1rem; font-size: 0.96rem; }
.card-meta {
list-style: none;
margin: 0 0 1.4rem;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.45rem;
font-size: 0.9rem;
color: var(--ink);
}
.card-btn { width: 100%; }
/* ===== CREW ===== */
.crew-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 1.1rem;
}
.crew {
text-align: center;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.6rem 1.1rem;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.crew:hover { transform: translateY(-5px); border-color: var(--line-2); }
.crew-photo {
width: 84px; height: 84px;
margin: 0 auto 1rem;
border-radius: 50%;
display: grid;
place-items: center;
font-family: "Anton", "Inter", sans-serif;
font-size: 1.6rem;
color: var(--ink);
background: linear-gradient(135deg, var(--c1), var(--c2));
box-shadow: 0 0 26px -6px var(--c1);
}
.crew h3 { font-size: 1.1rem; }
.crew-role {
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.72rem;
font-weight: 700;
color: var(--pink);
margin: 0.3rem 0 0.5rem;
}
.crew-note { color: var(--ink-2); font-size: 0.88rem; margin: 0; }
/* ===== EXPERIENCE ===== */
.experience { position: relative; overflow: hidden; }
.exp-glow {
position: absolute;
width: 540px; height: 540px;
border-radius: 50%;
background: radial-gradient(circle, rgba(139, 92, 246, 0.35), transparent 65%);
filter: blur(100px);
top: 50%; left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
pointer-events: none;
}
.exp-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.1rem;
}
.exp-item {
background: rgba(30, 16, 54, 0.6);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.5rem;
backdrop-filter: blur(6px);
transition: transform 0.2s ease, border-color 0.2s ease;
}
.exp-item:hover { transform: translateY(-5px); border-color: var(--pink); }
.exp-icon {
font-size: 1.8rem;
width: 52px; height: 52px;
display: grid;
place-items: center;
border-radius: var(--r-md);
background: linear-gradient(135deg, rgba(255, 47, 176, 0.18), rgba(139, 92, 246, 0.18));
margin-bottom: 1rem;
}
.exp-item h3 { font-size: 1.15rem; margin-bottom: 0.4rem; }
.exp-item p { color: var(--ink-2); font-size: 0.92rem; margin: 0; }
/* ===== PACKS ===== */
.packs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.2rem;
align-items: stretch;
}
.pack {
position: relative;
display: flex;
flex-direction: column;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.8rem 1.6rem;
transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}
.pack:hover { transform: translateY(-5px); border-color: var(--line-2); }
.pack--featured {
border-color: var(--pink);
background: linear-gradient(165deg, rgba(255, 47, 176, 0.12), var(--surface) 60%);
box-shadow: var(--shadow-glow);
}
.pack-flag {
position: absolute;
top: -0.7rem; left: 50%;
transform: translateX(-50%);
font-size: 0.66rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #190826;
background: var(--pink);
padding: 0.3rem 0.8rem;
border-radius: 999px;
}
.pack-name {
font-family: "Anton", "Inter", sans-serif;
font-weight: 400;
font-size: 1.5rem;
text-transform: uppercase;
margin-bottom: 0.6rem;
}
.pack-price {
font-family: "Anton", "Inter", sans-serif;
font-size: 2.8rem;
margin: 0 0 1.2rem;
display: flex;
align-items: baseline;
gap: 0.1rem;
}
.pack-cur { font-size: 1.3rem; color: var(--pink); }
.pack-per { font-size: 0.85rem; font-family: "Inter", sans-serif; color: var(--muted); margin-left: 0.3rem; }
.pack-list {
list-style: none;
margin: 0 0 1.6rem;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.6rem;
flex: 1;
font-size: 0.94rem;
color: var(--ink-2);
}
.pack-list li { position: relative; padding-left: 1.5rem; }
.pack-list li::before {
content: "✓";
position: absolute;
left: 0;
color: var(--pink);
font-weight: 800;
}
.pack-btn { width: 100%; }
/* ===== QUOTES ===== */
.quotes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.2rem;
}
.quote {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.6rem;
margin: 0;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.quote:hover { transform: translateY(-4px); border-color: var(--violet); }
.quote blockquote {
margin: 0 0 1rem;
font-size: 1.02rem;
color: var(--ink);
position: relative;
padding-top: 1.4rem;
}
.quote blockquote::before {
content: "“";
position: absolute;
top: -0.5rem; left: -0.2rem;
font-family: "Anton", sans-serif;
font-size: 3rem;
color: var(--pink);
opacity: 0.5;
}
.quote figcaption { font-size: 0.88rem; color: var(--muted); }
.quote figcaption strong { color: var(--ink); }
/* ===== CTA ===== */
.cta {
position: relative;
z-index: 1;
overflow: hidden;
margin: clamp(2rem, 6vw, 4rem) clamp(1rem, 4vw, 3rem) 0;
border-radius: var(--r-lg);
max-width: 1180px;
margin-inline: auto;
background: linear-gradient(150deg, rgba(255, 47, 176, 0.16), rgba(139, 92, 246, 0.14));
border: 1px solid var(--line);
}
.cta-glow {
position: absolute;
width: 600px; height: 600px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 47, 176, 0.4), transparent 65%);
filter: blur(90px);
top: -200px; right: -120px;
z-index: -1;
}
.cta-inner {
text-align: center;
max-width: 580px;
margin: 0 auto;
padding: clamp(2.6rem, 7vw, 4.5rem) clamp(1.2rem, 4vw, 2rem);
}
.cta-title {
font-family: "Anton", "Inter", sans-serif;
font-weight: 400;
font-size: clamp(2.2rem, 7vw, 4rem);
text-transform: uppercase;
margin-bottom: 0.7rem;
}
.cta-sub { color: var(--ink-2); margin: 0 0 1.8rem; }
.cta-form {
display: flex;
gap: 0.6rem;
max-width: 460px;
margin: 0 auto 1rem;
}
.cta-form input {
flex: 1;
font: inherit;
padding: 0.9rem 1rem;
border-radius: 999px;
border: 1px solid var(--line-2);
background: rgba(0, 0, 0, 0.25);
color: var(--ink);
}
.cta-form input::placeholder { color: var(--muted); }
.cta-form input:focus-visible {
outline: none;
border-color: var(--pink);
box-shadow: 0 0 0 3px var(--glow);
}
.cta-form input.invalid {
border-color: var(--pink);
animation: shake 0.32s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-6px); }
75% { transform: translateX(6px); }
}
.cta-fine { font-size: 0.8rem; color: var(--muted); margin: 0; }
/* ===== FOOTER ===== */
.footer {
position: relative;
z-index: 1;
text-align: center;
padding: clamp(2.5rem, 6vw, 4rem) 1rem 3rem;
color: var(--ink-2);
font-size: 0.9rem;
}
.footer-brand {
font-family: "Anton", "Inter", sans-serif;
font-size: 1.3rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.6rem;
}
.footer p { margin: 0.2rem 0; }
.footer-fine { color: var(--muted); font-size: 0.8rem; margin-top: 0.8rem; }
/* ===== TOAST ===== */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 130%);
background: var(--elevated);
color: var(--ink);
border: 1px solid var(--pink);
border-radius: var(--r-md);
padding: 0.85rem 1.3rem;
font-weight: 600;
font-size: 0.94rem;
box-shadow: var(--shadow), 0 0 26px -10px var(--glow);
z-index: 100;
transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.2);
pointer-events: none;
max-width: calc(100vw - 32px);
}
.toast.show { transform: translate(-50%, 0); }
/* ===== REVEAL ===== */
.reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.in { opacity: 1; transform: none; }
/* ===== RESPONSIVE ===== */
@media (max-width: 880px) {
.nav-links, .nav-cta { display: none; }
.nav-toggle { display: flex; }
}
@media (max-width: 520px) {
.hero-stats { gap: 1.2rem; flex-wrap: wrap; }
.hero-actions .btn { flex: 1; }
.next-ride { flex-wrap: wrap; }
.next-ride-spots { order: 3; }
.cta-form { flex-direction: column; }
.cta-form .btn { width: 100%; }
.card, .pack { padding: 1.4rem 1.2rem; }
.section-head { text-align: left; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
.reveal { opacity: 1; transform: none; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2800);
}
/* ---------- Sticky nav shadow ---------- */
var nav = document.getElementById("nav");
function onScroll() {
if (!nav) return;
nav.classList.toggle("scrolled", window.scrollY > 20);
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- Mobile menu ---------- */
var toggle = document.getElementById("navToggle");
var menu = document.getElementById("mobileMenu");
function closeMenu() {
if (!menu || !toggle) return;
menu.hidden = true;
toggle.setAttribute("aria-expanded", "false");
}
if (toggle && menu) {
toggle.addEventListener("click", function () {
var open = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!open));
menu.hidden = open;
});
menu.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", closeMenu);
});
}
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeMenu();
});
/* ---------- Smooth-scroll for in-page anchors ---------- */
document.querySelectorAll('a[href^="#"]').forEach(function (link) {
link.addEventListener("click", function (e) {
var id = link.getAttribute("href");
if (id === "#" || id.length < 2) return;
var target = document.querySelector(id);
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
});
});
/* ---------- Reveal on scroll ---------- */
var reveals = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("in");
io.unobserve(entry.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
);
reveals.forEach(function (el) {
io.observe(el);
});
} else {
reveals.forEach(function (el) {
el.classList.add("in");
});
}
/* ---------- Class-time rotator ---------- */
var rides = [
{ name: "Ride45 · Neon Hour", meta: "6:30 PM · Studio A · with Mara V.", spots: 7 },
{ name: "Rhythm · Sunset Set", meta: "7:15 PM · Studio B · with Deon K.", spots: 12 },
{ name: "HIIT Sculpt · Burn", meta: "8:00 PM · Floor 2 · with Sasha L.", spots: 4 },
{ name: "Ride45 · Bass Drop", meta: "8:45 PM · Studio A · with Rio O.", spots: 9 },
];
var rideName = document.getElementById("rideName");
var rideMeta = document.getElementById("rideMeta");
var rideSpots = document.getElementById("rideSpots");
var rideBody = rideName ? rideName.closest(".next-ride-body") : null;
var ri = 0;
function spotColor(n) {
if (n <= 4) return "var(--danger, #f87171)";
if (n <= 8) return "var(--warn, #fbbf24)";
return "var(--ok, #34d399)";
}
function renderRide() {
var r = rides[ri];
if (rideName) rideName.textContent = r.name;
if (rideMeta) rideMeta.textContent = r.meta;
if (rideSpots) {
rideSpots.textContent = r.spots + " bikes left";
rideSpots.style.background = spotColor(r.spots);
}
}
renderRide();
if (rideBody) {
setInterval(function () {
rideBody.classList.add("swap");
setTimeout(function () {
ri = (ri + 1) % rides.length;
renderRide();
rideBody.classList.remove("swap");
}, 320);
}, 3600);
}
/* ---------- Book buttons ---------- */
document.querySelectorAll(".card-btn, .pack-btn").forEach(function (btn) {
btn.addEventListener("click", function (e) {
e.preventDefault();
var card = btn.closest(".card, .pack");
var label = card ? card.querySelector(".card-name, .pack-name") : null;
toast(label ? "🎟️ " + label.textContent.trim() + " added — see you on the floor!" : "Added!");
});
});
/* ---------- Claim form ---------- */
var form = document.getElementById("claimForm");
if (form) {
var input = document.getElementById("email");
form.addEventListener("submit", function (e) {
e.preventDefault();
var val = (input.value || "").trim();
var ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
if (!ok) {
input.classList.add("invalid");
input.focus();
toast("Enter a valid email to claim your ride.");
setTimeout(function () {
input.classList.remove("invalid");
}, 500);
return;
}
input.value = "";
toast("🔥 Free ride locked in — check your inbox for the code!");
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PULSE — Boutique Cycling & HIIT Club</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=Anton&family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- ===== NAV ===== -->
<header class="nav" id="nav">
<a href="#top" class="brand" aria-label="PULSE home">
<span class="brand-dot" aria-hidden="true"></span>
PULSE<span class="brand-sub">CLUB</span>
</a>
<nav class="nav-links" aria-label="Primary">
<a href="#classes">Classes</a>
<a href="#crew">The Crew</a>
<a href="#experience">Experience</a>
<a href="#packs">Packs</a>
</nav>
<div class="nav-cta">
<a href="#claim" class="btn btn-ghost">Sign in</a>
<a href="#claim" class="btn btn-primary">Claim first ride</a>
</div>
<button class="nav-toggle" id="navToggle" aria-label="Toggle menu" aria-expanded="false" aria-controls="mobileMenu">
<span></span><span></span><span></span>
</button>
</header>
<div class="mobile-menu" id="mobileMenu" hidden>
<a href="#classes">Classes</a>
<a href="#crew">The Crew</a>
<a href="#experience">Experience</a>
<a href="#packs">Packs</a>
<a href="#claim" class="btn btn-primary">Claim first ride</a>
</div>
<main id="top">
<!-- ===== HERO ===== -->
<section class="hero">
<div class="hero-glow hero-glow-a" aria-hidden="true"></div>
<div class="hero-glow hero-glow-b" aria-hidden="true"></div>
<div class="hero-inner">
<p class="eyebrow reveal">Downtown · 45-min rides · Live DJ sets</p>
<h1 class="hero-title reveal">
RIDE LOUD.<br /><span class="grad">SWEAT BRIGHT.</span>
</h1>
<p class="hero-lead reveal">
A neon-lit cycling & HIIT club where the lights move with the beat and the
leaderboard never sleeps. Forty-five minutes. One room. Zero excuses.
</p>
<div class="hero-actions reveal">
<a href="#claim" class="btn btn-primary btn-lg">Claim your first ride free</a>
<a href="#classes" class="btn btn-outline btn-lg">See the schedule</a>
</div>
<ul class="hero-stats reveal" aria-label="Club highlights">
<li><strong>12k+</strong><span>riders moved</span></li>
<li><strong>4.9★</strong><span>average rating</span></li>
<li><strong>38</strong><span>rides / week</span></li>
</ul>
</div>
<!-- live rotator -->
<div class="next-ride reveal" id="nextRide" aria-live="polite">
<span class="next-ride-label">Starting next</span>
<div class="next-ride-body">
<strong class="next-ride-name" id="rideName">Ride45 · Neon Hour</strong>
<span class="next-ride-meta" id="rideMeta">6:30 PM · Studio A · with Mara V.</span>
</div>
<span class="next-ride-spots" id="rideSpots">7 bikes left</span>
</div>
</section>
<!-- ===== SIGNATURE CLASSES ===== -->
<section class="section" id="classes">
<header class="section-head reveal">
<p class="eyebrow">Signature classes</p>
<h2 class="section-title">Pick your <span class="grad">vibe</span></h2>
<p class="section-sub">Three formats, one philosophy: move hard, move together.</p>
</header>
<div class="cards">
<article class="card card--ride reveal">
<span class="card-badge">Most booked</span>
<h3 class="card-name">Ride45</h3>
<p class="card-tag">Endurance cycling</p>
<p class="card-desc">A 45-minute climb-and-sprint journey synced to a live DJ set. Tap-back, hills, full send.</p>
<ul class="card-meta">
<li>🔥 520–700 kcal</li>
<li>📊 Live leaderboard</li>
<li>🎧 DJ-curated set</li>
</ul>
<a href="#claim" class="btn btn-primary card-btn">Book Ride45</a>
</article>
<article class="card card--rhythm reveal">
<span class="card-badge card-badge--alt">Beginner-friendly</span>
<h3 class="card-name">Rhythm</h3>
<p class="card-tag">Beat-based ride</p>
<p class="card-desc">Ride the music, not the metrics. Choreographed tap-backs and chest-presses in club lighting.</p>
<ul class="card-meta">
<li>🔥 400–550 kcal</li>
<li>💡 Full light show</li>
<li>🕺 No experience needed</li>
</ul>
<a href="#claim" class="btn btn-outline card-btn">Book Rhythm</a>
</article>
<article class="card card--hiit reveal">
<span class="card-badge card-badge--alt">High intensity</span>
<h3 class="card-name">HIIT Sculpt</h3>
<p class="card-tag">Off-the-bike strength</p>
<p class="card-desc">Floor-based intervals: dumbbells, sliders & bodyweight. Build the engine, sculpt the frame.</p>
<ul class="card-meta">
<li>🔥 450–600 kcal</li>
<li>🏋️ Strength + cardio</li>
<li>⏱️ 40-min circuit</li>
</ul>
<a href="#claim" class="btn btn-outline card-btn">Book HIIT Sculpt</a>
</article>
</div>
</section>
<!-- ===== CREW ===== -->
<section class="section section--tight" id="crew">
<header class="section-head reveal">
<p class="eyebrow">The crew</p>
<h2 class="section-title">Part coach, <span class="grad">part DJ</span></h2>
<p class="section-sub">Our instructors build the playlist, set the pace and learn your name.</p>
</header>
<div class="crew-row">
<article class="crew reveal">
<div class="crew-photo" style="--c1:#ff2fb0;--c2:#8b5cf6"><span>MV</span></div>
<h3>Mara Velez</h3>
<p class="crew-role">Lead · Ride45</p>
<p class="crew-note">House & techno. Will out-sprint you.</p>
</article>
<article class="crew reveal">
<div class="crew-photo" style="--c1:#8b5cf6;--c2:#22d3ee"><span>DK</span></div>
<h3>Deon King</h3>
<p class="crew-role">Rhythm specialist</p>
<p class="crew-note">Old-school hip-hop, perfect counts.</p>
</article>
<article class="crew reveal">
<div class="crew-photo" style="--c1:#ffb627;--c2:#ff2fb0"><span>SL</span></div>
<h3>Sasha Lin</h3>
<p class="crew-role">HIIT Sculpt coach</p>
<p class="crew-note">Form fanatic. Loud encouragement.</p>
</article>
<article class="crew reveal">
<div class="crew-photo" style="--c1:#22d3ee;--c2:#8b5cf6"><span>RO</span></div>
<h3>Rio Okafor</h3>
<p class="crew-role">Ride45 · weekends</p>
<p class="crew-note">Drum & bass, relentless climbs.</p>
</article>
</div>
</section>
<!-- ===== EXPERIENCE ===== -->
<section class="section experience" id="experience">
<div class="exp-glow" aria-hidden="true"></div>
<header class="section-head reveal">
<p class="eyebrow">The experience</p>
<h2 class="section-title">It hits <span class="grad">different</span></h2>
</header>
<div class="exp-grid">
<div class="exp-item reveal">
<div class="exp-icon" aria-hidden="true">💡</div>
<h3>Reactive lighting</h3>
<p>500+ LEDs choreographed to every drop. The room breathes with the beat.</p>
</div>
<div class="exp-item reveal">
<div class="exp-icon" aria-hidden="true">🔊</div>
<h3>Concert-grade sound</h3>
<p>A tuned 12-speaker rig so the bass lives in your chest, not your ears.</p>
</div>
<div class="exp-item reveal">
<div class="exp-icon" aria-hidden="true">📈</div>
<h3>Live metrics</h3>
<p>Output, RPM and rank streamed to the wall — chase the leaderboard or just ride.</p>
</div>
<div class="exp-item reveal">
<div class="exp-icon" aria-hidden="true">🚿</div>
<h3>Full reset</h3>
<p>Rainfall showers, fresh towels, dry shampoo bar. Walk in, glow out.</p>
</div>
</div>
</section>
<!-- ===== PACKS / PRICING ===== -->
<section class="section" id="packs">
<header class="section-head reveal">
<p class="eyebrow">Ride packs</p>
<h2 class="section-title">No lock-in. <span class="grad">Just rides.</span></h2>
<p class="section-sub">Buy a pack, book any format. Credits never expire on memberships.</p>
</header>
<div class="packs">
<article class="pack reveal">
<h3 class="pack-name">Drop-in</h3>
<p class="pack-price"><span class="pack-cur">$</span>26<span class="pack-per">/ ride</span></p>
<ul class="pack-list">
<li>Single class credit</li>
<li>Shoes & towel included</li>
<li>Any format, any studio</li>
</ul>
<a href="#claim" class="btn btn-outline pack-btn">Choose Drop-in</a>
</article>
<article class="pack pack--featured reveal">
<span class="pack-flag">Best value</span>
<h3 class="pack-name">10-Pack</h3>
<p class="pack-price"><span class="pack-cur">$</span>200<span class="pack-per">/ 10 rides</span></p>
<ul class="pack-list">
<li>Save $60 vs drop-in</li>
<li>Credits valid 90 days</li>
<li>Priority bike booking</li>
<li>Bring-a-friend pass ×2</li>
</ul>
<a href="#claim" class="btn btn-primary pack-btn">Choose 10-Pack</a>
</article>
<article class="pack reveal">
<h3 class="pack-name">Unlimited</h3>
<p class="pack-price"><span class="pack-cur">$</span>179<span class="pack-per">/ month</span></p>
<ul class="pack-list">
<li>Unlimited monthly rides</li>
<li>Reserve your bike first</li>
<li>Member events & merch drops</li>
</ul>
<a href="#claim" class="btn btn-outline pack-btn">Go Unlimited</a>
</article>
</div>
</section>
<!-- ===== TESTIMONIALS ===== -->
<section class="section section--tight">
<header class="section-head reveal">
<p class="eyebrow">From the floor</p>
<h2 class="section-title">Riders <span class="grad">talk</span></h2>
</header>
<div class="quotes">
<figure class="quote reveal">
<blockquote>The lighting alone is worth it — I forget I'm working out until I can't feel my legs.</blockquote>
<figcaption><strong>Priya N.</strong> · 84 rides</figcaption>
</figure>
<figure class="quote reveal">
<blockquote>Mara's Friday set is the best 45 minutes of my week. Booked out three weeks ahead.</blockquote>
<figcaption><strong>Tomás R.</strong> · 210 rides</figcaption>
</figure>
<figure class="quote reveal">
<blockquote>HIIT Sculpt humbled me and then made me strong. Sasha actually fixes your form.</blockquote>
<figcaption><strong>Bex O.</strong> · 56 rides</figcaption>
</figure>
</div>
</section>
<!-- ===== FINAL CTA ===== -->
<section class="cta" id="claim">
<div class="cta-glow" aria-hidden="true"></div>
<div class="cta-inner reveal">
<p class="eyebrow">Your first ride is on us</p>
<h2 class="cta-title">Claim your <span class="grad">free ride</span></h2>
<p class="cta-sub">Drop your email and we'll text you a code for any class this week.</p>
<form class="cta-form" id="claimForm" novalidate>
<label class="sr-only" for="email">Email address</label>
<input type="email" id="email" name="email" placeholder="[email protected]" required autocomplete="email" />
<button type="submit" class="btn btn-primary btn-lg">Send my free ride</button>
</form>
<p class="cta-fine">No card needed · 18+ · One free ride per new rider.</p>
</div>
</section>
</main>
<footer class="footer">
<div class="footer-brand">
<span class="brand-dot" aria-hidden="true"></span> PULSE<span class="brand-sub">CLUB</span>
</div>
<p>118 Lumen St · Open 5:30 AM – 9 PM</p>
<p class="footer-fine">© 2026 PULSE Club. Fictional studio for demo purposes.</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Boutique Cycling / HIIT Landing
A high-energy, club-like landing page for a fictional boutique cycling and HIIT studio (PULSE Club). The immersive hero sits over drifting neon glows and gradient display type, anchored by a “Claim your first ride free” call to action and a strip of social-proof stats. Below it, a live next ride widget cycles through the evening schedule, swapping the class name, time, instructor and remaining-bike count — with the spot badge re-coloring from green to amber to red as the room fills up.
The page is organized as a full marketing flow: vivid signature-class cards (Ride45, Rhythm, HIIT Sculpt) each with their own tint and badges, an instructor / DJ-energy crew row, an “experience” grid (reactive lighting, concert-grade sound, live metrics), a three-tier ride-packs pricing teaser with a featured plan, rider testimonials, and a bold closing CTA with an inline email-capture form.
Everything is vanilla: a sticky nav that gains a backdrop on scroll, an accessible mobile
menu, smooth in-page anchor scrolling, IntersectionObserver-driven reveal animations, the
class-time rotator, book-button and pricing toasts, and email validation with a shake-on-error
state. A small toast() helper centralizes feedback, focus rings stay visible for keyboard
users, and a prefers-reduced-motion block disables animation for those who opt out.