Music — Streaming / Modern Pop Landing
A sleek, glossy landing page for the fictional Pulsewave streaming service, built in HTML, CSS, and vanilla JS. A dynamic hero pairs animated gradient glow blobs with a floating phone mockup whose now-playing screen cycles tracks behind a live equalizer and progress scrubber. Below sit glassy feature cards for offline, lossless, and personalized listening, a count-up catalog stat band, a Free, Premium, and Family pricing grid with a monthly to annual toggle, plus scroll-reveal motion and toasts throughout.
MCP
代码
:root {
--bg: #08080c;
--bg-2: #0e0e16;
--surface: #15151f;
--surface-2: #1c1c2a;
--text: #ffffff;
--muted: #9a9ab0;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--accent: #22e1ff;
--accent-2: #a855f7;
--accent-3: #ff2d95;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--r-full: 999px;
--shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
--glow: 0 0 40px rgba(34, 225, 255, 0.35);
--display: "Space Grotesk", "Sora", system-ui, sans-serif;
--body: "Inter", system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--body);
background: var(--bg);
color: var(--text);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
position: relative;
}
a { color: inherit; text-decoration: none; }
ul { list-style: none; }
em { font-style: normal; }
/* ---------- Ambient gradient blobs ---------- */
.blobs {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.blob {
position: absolute;
width: 46vmax;
height: 46vmax;
border-radius: 50%;
filter: blur(90px);
opacity: 0.5;
will-change: transform;
}
.blob--cyan { background: radial-gradient(circle, var(--accent), transparent 65%); top: -14vmax; left: -10vmax; }
.blob--violet { background: radial-gradient(circle, var(--accent-2), transparent 65%); top: 18vmax; right: -16vmax; }
.blob--magenta { background: radial-gradient(circle, var(--accent-3), transparent 65%); bottom: -20vmax; left: 22vmax; opacity: 0.4; }
@media (prefers-reduced-motion: no-preference) {
.blob--cyan { animation: float1 18s ease-in-out infinite; }
.blob--violet { animation: float2 22s ease-in-out infinite; }
.blob--magenta { animation: float1 26s ease-in-out infinite reverse; }
}
@keyframes float1 { 50% { transform: translate(6vmax, 5vmax) scale(1.12); } }
@keyframes float2 { 50% { transform: translate(-7vmax, 4vmax) scale(1.08); } }
main, .nav, .footer { position: relative; z-index: 1; }
/* ---------- Buttons ---------- */
.btn {
font-family: var(--body);
font-weight: 700;
font-size: 0.95rem;
border: 1px solid transparent;
border-radius: var(--r-full);
padding: 0.72rem 1.4rem;
cursor: pointer;
transition: transform 0.16s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease;
color: var(--text);
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.btn--solid {
background: linear-gradient(120deg, var(--accent), var(--accent-2) 55%, var(--accent-3));
color: #07070b;
box-shadow: 0 10px 28px rgba(34, 225, 255, 0.32);
}
.btn--solid:hover { transform: translateY(-2px); box-shadow: 0 14px 34px rgba(168, 85, 247, 0.45); }
.btn--ghost { background: rgba(255, 255, 255, 0.05); border-color: var(--line-2); }
.btn--ghost:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-2px); }
.btn--lg { padding: 0.95rem 1.9rem; font-size: 1.05rem; }
.btn--block { width: 100%; }
.btn:active { transform: translateY(0); }
/* ---------- Nav ---------- */
.nav {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1.1rem clamp(1rem, 4vw, 3rem);
max-width: 1240px;
margin: 0 auto;
}
.brand { display: inline-flex; align-items: center; gap: 0.6rem; }
.brand__name { font-family: var(--display); font-weight: 700; font-size: 1.3rem; letter-spacing: -0.02em; }
.brand__mark { display: inline-flex; align-items: flex-end; gap: 3px; height: 22px; }
.brand__mark i {
width: 4px; height: 100%;
background: linear-gradient(var(--accent), var(--accent-2));
border-radius: 2px;
transform-origin: bottom;
}
@media (prefers-reduced-motion: no-preference) {
.brand__mark i { animation: eq 1.1s ease-in-out infinite; }
.brand__mark i:nth-child(1) { animation-delay: -0.2s; }
.brand__mark i:nth-child(2) { animation-delay: -0.5s; }
.brand__mark i:nth-child(3) { animation-delay: -0.1s; }
.brand__mark i:nth-child(4) { animation-delay: -0.7s; }
}
@keyframes eq { 0%, 100% { transform: scaleY(0.3); } 50% { transform: scaleY(1); } }
.nav__links { display: flex; gap: 1.6rem; margin-left: auto; font-weight: 600; color: var(--muted); }
.nav__links a { transition: color 0.18s ease; }
.nav__links a:hover { color: var(--text); }
.nav__cta { display: flex; gap: 0.6rem; }
/* ---------- Hero ---------- */
.hero {
max-width: 1240px;
margin: 0 auto;
padding: clamp(2rem, 6vw, 5rem) clamp(1rem, 4vw, 3rem) 4rem;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: clamp(2rem, 5vw, 4rem);
align-items: center;
}
.eyebrow {
display: inline-block;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 0.4rem 0.85rem;
border-radius: var(--r-full);
border: 1px solid var(--line-2);
background: rgba(255, 255, 255, 0.04);
color: var(--accent);
margin-bottom: 1.4rem;
}
.hero h1 {
font-family: var(--display);
font-weight: 700;
font-size: clamp(2.6rem, 7vw, 4.4rem);
line-height: 1.02;
letter-spacing: -0.03em;
}
.hero h1 em {
background: linear-gradient(110deg, var(--accent), var(--accent-2) 50%, var(--accent-3));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.lede { color: var(--muted); font-size: 1.1rem; max-width: 30rem; margin: 1.3rem 0 1.8rem; }
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.stores { display: flex; gap: 0.6rem; }
.store {
display: inline-flex;
align-items: center;
gap: 0.55rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--line-2);
color: var(--text);
border-radius: var(--r-md);
padding: 0.5rem 0.85rem;
cursor: pointer;
transition: background 0.18s ease, transform 0.16s ease;
}
.store:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-2px); }
.store__glyph { width: 20px; height: 22px; background: var(--text); border-radius: 5px 5px 5px 2px; flex: none; clip-path: polygon(50% 0, 62% 18%, 100% 30%, 100% 100%, 0 100%, 0 30%, 38% 18%); }
.store__glyph--play { clip-path: polygon(15% 8%, 85% 50%, 15% 92%); background: linear-gradient(var(--accent), var(--accent-3)); }
.store__txt { display: flex; flex-direction: column; line-height: 1.1; text-align: left; }
.store__txt small { font-size: 0.62rem; color: var(--muted); }
.store__txt strong { font-size: 0.92rem; }
.hero__trust { display: flex; gap: 2rem; margin-top: 2.4rem; flex-wrap: wrap; }
.hero__trust li { display: flex; flex-direction: column; }
.hero__trust strong { font-family: var(--display); font-size: 1.5rem; }
.hero__trust span { color: var(--muted); font-size: 0.82rem; }
/* ---------- Phone mockup ---------- */
.hero__device { position: relative; display: grid; place-items: center; }
.device__glow {
position: absolute;
width: 110%; height: 110%;
background: radial-gradient(circle, rgba(168, 85, 247, 0.4), transparent 60%);
filter: blur(50px);
z-index: -1;
}
.phone {
width: min(300px, 78vw);
aspect-ratio: 9 / 19;
background: linear-gradient(160deg, #1d1d2c, #0c0c14);
border: 1px solid var(--line-2);
border-radius: 38px;
padding: 12px;
position: relative;
box-shadow: var(--shadow), inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
@media (prefers-reduced-motion: no-preference) {
.phone { animation: phoneFloat 6s ease-in-out infinite; }
}
@keyframes phoneFloat { 50% { transform: translateY(-12px); } }
.phone__notch {
position: absolute;
top: 14px; left: 50%;
transform: translateX(-50%);
width: 38%; height: 18px;
background: #050509;
border-radius: var(--r-full);
z-index: 2;
}
.phone__screen {
width: 100%; height: 100%;
border-radius: 28px;
background: linear-gradient(180deg, var(--bg-2), #07070d);
overflow: hidden;
position: relative;
}
.np { padding: 2.2rem 1.1rem 1.1rem; display: flex; flex-direction: column; height: 100%; gap: 0.85rem; }
.np__top { display: flex; align-items: center; justify-content: space-between; }
.np__chip { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); }
.np__menu { color: var(--muted); letter-spacing: 2px; }
.np__art {
width: 100%;
aspect-ratio: 1;
border-radius: var(--r-md);
background:
radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.25), transparent 45%),
linear-gradient(135deg, var(--accent), var(--accent-2) 55%, var(--accent-3));
position: relative;
display: grid;
place-items: end center;
overflow: hidden;
transition: background 0.5s ease;
box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.3);
}
.np__eq { display: flex; align-items: flex-end; gap: 4px; height: 34%; padding-bottom: 12px; }
.np__eq i {
width: 6px; height: 100%;
background: rgba(255, 255, 255, 0.85);
border-radius: 3px;
transform-origin: bottom;
transform: scaleY(0.25);
}
.np__eq.is-playing i { animation: eq 0.9s ease-in-out infinite; }
.np__eq i:nth-child(2) { animation-delay: -0.3s; }
.np__eq i:nth-child(3) { animation-delay: -0.55s; }
.np__eq i:nth-child(4) { animation-delay: -0.15s; }
.np__eq i:nth-child(5) { animation-delay: -0.7s; }
.np__meta { text-align: left; }
.np__title { font-family: var(--display); font-size: 1.15rem; font-weight: 700; }
.np__artist { color: var(--muted); font-size: 0.85rem; }
.np__scrub { margin-top: -0.2rem; }
.np__bar { height: 5px; background: rgba(255, 255, 255, 0.14); border-radius: var(--r-full); overflow: hidden; }
.np__bar span { display: block; height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent), var(--accent-3)); border-radius: var(--r-full); }
.np__time { display: flex; justify-content: space-between; font-size: 0.68rem; color: var(--muted); margin-top: 5px; }
.np__controls { display: flex; align-items: center; justify-content: center; gap: 1rem; margin: 0.2rem 0; }
.np__btn {
background: none; border: none; color: var(--text);
font-size: 1.1rem; cursor: pointer; padding: 0.3rem;
border-radius: var(--r-full);
transition: transform 0.15s ease, color 0.15s ease;
}
.np__btn:hover { color: var(--accent); }
.np__btn:active { transform: scale(0.9); }
.np__btn--play {
width: 52px; height: 52px;
display: grid; place-items: center;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: #07070b;
box-shadow: 0 6px 18px rgba(34, 225, 255, 0.4);
}
.np__btn--play svg { width: 22px; height: 22px; fill: currentColor; }
.np__queue { margin-top: auto; }
.np__queue-label { font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
.np__queue ul { margin-top: 0.4rem; display: flex; flex-direction: column; gap: 0.35rem; }
.np__queue li { display: flex; justify-content: space-between; font-size: 0.74rem; color: var(--muted); }
.np__queue li strong { color: var(--text); font-weight: 600; }
/* ---------- Section shared ---------- */
.section-title {
font-family: var(--display);
font-weight: 700;
font-size: clamp(1.8rem, 4vw, 2.6rem);
letter-spacing: -0.02em;
text-align: center;
max-width: 22ch;
margin: 0 auto 2.4rem;
}
/* ---------- Features ---------- */
.features { max-width: 1240px; margin: 0 auto; padding: 4rem clamp(1rem, 4vw, 3rem); }
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem; }
.card {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.8rem;
backdrop-filter: blur(12px);
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.card:hover { transform: translateY(-6px); border-color: var(--line-2); box-shadow: var(--shadow); }
.card__icon {
width: 54px; height: 54px;
border-radius: var(--r-md);
display: grid; place-items: center;
margin-bottom: 1.1rem;
position: relative;
}
.card__icon--offline { background: linear-gradient(135deg, var(--accent), #0a8ea0); }
.card__icon--offline::after { content: ""; width: 20px; height: 20px; border: 3px solid #06121a; border-bottom: none; border-radius: 20px 20px 0 0; transform: translateY(2px); box-shadow: 0 8px 0 -3px #06121a; }
.card__icon--lossless { background: linear-gradient(135deg, var(--accent-2), var(--accent-3)); display: flex; align-items: flex-end; gap: 3px; padding: 12px; }
.card__icon--lossless i { width: 5px; background: #160a1e; border-radius: 3px; }
.card__icon--lossless i:nth-child(1) { height: 40%; }
.card__icon--lossless i:nth-child(2) { height: 80%; }
.card__icon--lossless i:nth-child(3) { height: 55%; }
.card__icon--lossless i:nth-child(4) { height: 95%; }
.card__icon--personal { background: linear-gradient(135deg, var(--accent-3), var(--accent-2)); }
.card__icon--personal::after { content: ""; width: 22px; height: 22px; background: #1a0816; clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); }
.card h3 { font-family: var(--display); font-size: 1.25rem; margin-bottom: 0.5rem; }
.card p { color: var(--muted); }
/* ---------- Stats ---------- */
.stats { max-width: 1240px; margin: 0 auto; padding: 3rem clamp(1rem, 4vw, 3rem); }
.stat-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 2rem 1rem;
background: linear-gradient(120deg, rgba(34, 225, 255, 0.06), rgba(168, 85, 247, 0.06), rgba(255, 45, 149, 0.06));
backdrop-filter: blur(10px);
}
.stat { text-align: center; }
.stat__num {
display: block;
font-family: var(--display);
font-size: clamp(1.8rem, 4vw, 2.8rem);
font-weight: 700;
background: linear-gradient(110deg, var(--accent), var(--accent-2), var(--accent-3));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat__label { color: var(--muted); font-size: 0.85rem; }
/* ---------- Pricing ---------- */
.pricing { max-width: 1240px; margin: 0 auto; padding: 4rem clamp(1rem, 4vw, 3rem); }
.toggle { display: flex; align-items: center; justify-content: center; gap: 0.9rem; margin-bottom: 2.4rem; font-weight: 600; }
.toggle__label { color: var(--muted); transition: color 0.2s ease; }
.toggle.is-annual .toggle__label[data-on="annual"],
.toggle:not(.is-annual) .toggle__label[data-on="monthly"] { color: var(--text); }
.toggle__save { font-size: 0.72rem; color: var(--accent); background: rgba(34, 225, 255, 0.12); padding: 0.15rem 0.5rem; border-radius: var(--r-full); margin-left: 0.3rem; }
.toggle__switch {
width: 52px; height: 28px;
border-radius: var(--r-full);
border: 1px solid var(--line-2);
background: var(--surface-2);
position: relative;
cursor: pointer;
transition: background 0.2s ease;
}
.toggle__switch[aria-checked="true"] { background: linear-gradient(120deg, var(--accent), var(--accent-2)); }
.toggle__knob {
position: absolute;
top: 2px; left: 2px;
width: 22px; height: 22px;
border-radius: 50%;
background: #fff;
transition: transform 0.2s ease;
}
.toggle__switch[aria-checked="true"] .toggle__knob { transform: translateX(24px); }
.toggle__switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem; align-items: stretch; }
.plan {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 1.9rem;
display: flex;
flex-direction: column;
position: relative;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.plan:hover { transform: translateY(-4px); }
.plan--featured {
border-color: transparent;
background:
linear-gradient(var(--surface), var(--surface)) padding-box,
linear-gradient(135deg, var(--accent), var(--accent-2), var(--accent-3)) border-box;
box-shadow: 0 18px 50px rgba(168, 85, 247, 0.25);
}
.plan__badge {
position: absolute;
top: -0.8rem; left: 50%;
transform: translateX(-50%);
font-size: 0.7rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.06em;
padding: 0.3rem 0.8rem;
border-radius: var(--r-full);
background: linear-gradient(120deg, var(--accent), var(--accent-3));
color: #07070b;
}
.plan__head h3 { font-family: var(--display); font-size: 1.4rem; }
.plan__tag { color: var(--muted); font-size: 0.85rem; }
.plan__price { margin: 1.1rem 0 1.3rem; display: flex; align-items: baseline; gap: 0.3rem; }
.plan__amt { font-family: var(--display); font-size: 2.2rem; font-weight: 700; }
.plan__per { color: var(--muted); font-size: 0.9rem; }
.plan__feats { display: flex; flex-direction: column; gap: 0.7rem; margin-bottom: 1.6rem; flex: 1; }
.plan__feats li { position: relative; padding-left: 1.6rem; color: var(--muted); font-size: 0.92rem; }
.plan__feats li::before {
content: "";
position: absolute; left: 0; top: 4px;
width: 16px; height: 16px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.plan__feats li::after {
content: "";
position: absolute; left: 5px; top: 8px;
width: 5px; height: 8px;
border: solid #07070b; border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* ---------- CTA ---------- */
.cta { max-width: 1240px; margin: 2rem auto; padding: 0 clamp(1rem, 4vw, 3rem); }
.cta__inner {
border-radius: var(--r-lg);
padding: clamp(2.4rem, 6vw, 4rem);
text-align: center;
background:
radial-gradient(circle at 20% 0%, rgba(34, 225, 255, 0.3), transparent 50%),
radial-gradient(circle at 90% 100%, rgba(255, 45, 149, 0.3), transparent 50%),
var(--surface);
border: 1px solid var(--line-2);
}
.cta__inner h2 { font-family: var(--display); font-size: clamp(1.6rem, 4vw, 2.4rem); margin-bottom: 1.6rem; letter-spacing: -0.02em; }
/* ---------- Footer ---------- */
.footer { max-width: 1240px; margin: 4rem auto 0; padding: 3rem clamp(1rem, 4vw, 3rem); border-top: 1px solid var(--line); }
.footer__cols { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 2rem; }
.footer__brand p { color: var(--muted); margin-top: 0.6rem; max-width: 24rem; }
.footer nav { display: flex; flex-direction: column; gap: 0.55rem; }
.footer h4 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 0.4rem; }
.footer nav a { color: var(--text); font-size: 0.9rem; transition: color 0.15s ease; }
.footer nav a:hover { color: var(--accent); }
.footer__bar { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.8rem; margin-top: 2.4rem; padding-top: 1.4rem; border-top: 1px solid var(--line); color: var(--muted); font-size: 0.82rem; }
.footer__legal { display: flex; gap: 1rem; }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%; bottom: 24px;
transform: translate(-50%, 140%);
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--text);
padding: 0.75rem 1.2rem;
border-radius: var(--r-full);
box-shadow: var(--shadow);
font-size: 0.9rem;
font-weight: 500;
z-index: 50;
transition: transform 0.32s cubic-bezier(0.2, 0.9, 0.3, 1);
max-width: 90vw;
}
.toast.is-show { transform: translate(-50%, 0); }
/* ---------- Scroll reveal ---------- */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.6s ease, transform 0.6s ease; }
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
* { animation: none !important; }
}
/* ---------- Responsive ---------- */
@media (max-width: 920px) {
.hero { grid-template-columns: 1fr; text-align: center; }
.hero__copy { order: 1; }
.hero__device { order: 2; }
.lede { margin-left: auto; margin-right: auto; }
.hero__actions, .hero__trust { justify-content: center; }
.feature-grid, .plans { grid-template-columns: 1fr; }
.stat-row { grid-template-columns: repeat(2, 1fr); gap: 1.6rem; }
.footer__cols { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 520px) {
.nav { flex-wrap: wrap; gap: 0.8rem; }
.nav__links { display: none; }
.nav__cta { margin-left: auto; }
.hero { padding-top: 1.5rem; }
.hero h1 { font-size: clamp(2.2rem, 11vw, 3rem); }
.stores { flex-direction: column; width: 100%; }
.store { justify-content: center; }
.hero__trust { gap: 1.2rem; }
.stat-row { grid-template-columns: 1fr; }
.footer__cols { grid-template-columns: 1fr; }
.footer__bar { flex-direction: column; align-items: flex-start; }
.toggle { flex-wrap: wrap; }
}(function () {
"use strict";
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("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");
}, 2600);
}
document.querySelectorAll("[data-toast]").forEach(function (el) {
el.addEventListener("click", function () {
toast(el.getAttribute("data-toast"));
});
});
/* ---------- Now-playing mockup ---------- */
var tracks = [
{ title: "Paper Lanterns", artist: "Neon Tides", dur: 222, art: "linear-gradient(135deg,#22e1ff,#a855f7 55%,#ff2d95)" },
{ title: "Velvet Static", artist: "Mara Vale", dur: 198, art: "linear-gradient(135deg,#ff2d95,#a855f7 55%,#22e1ff)" },
{ title: "Midnight Reservoir", artist: "Glass Atlas", dur: 254, art: "linear-gradient(135deg,#a855f7,#22e1ff 60%,#0a8ea0)" },
{ title: "Slow Rivers", artist: "Hollow Coast", dur: 176, art: "linear-gradient(135deg,#22e1ff,#0a8ea0 50%,#a855f7)" }
];
var idx = 0;
var elapsed = 0;
var playing = true;
var tick = null;
var artEl = document.getElementById("npArt");
var eqEl = document.getElementById("npEq");
var titleEl = document.getElementById("npTitle");
var artistEl = document.getElementById("npArtist");
var progEl = document.getElementById("npProgress");
var curEl = document.getElementById("npCur");
var durEl = document.getElementById("npDur");
var queueEl = document.getElementById("npQueue");
var playBtn = document.getElementById("npPlay");
var playPath = document.getElementById("npPath");
var PLAY_ICON = "M8 5v14l11-7z";
var PAUSE_ICON = "M6 5h4v14H6zM14 5h4v14h-4z";
function fmt(sec) {
var m = Math.floor(sec / 60);
var s = Math.floor(sec % 60);
return m + ":" + (s < 10 ? "0" : "") + s;
}
function renderQueue() {
if (!queueEl) return;
queueEl.innerHTML = "";
for (var i = 1; i <= 2; i++) {
var t = tracks[(idx + i) % tracks.length];
var li = document.createElement("li");
var s = document.createElement("strong");
s.textContent = t.title;
var span = document.createElement("span");
span.textContent = fmt(t.dur);
li.appendChild(s);
li.appendChild(span);
queueEl.appendChild(li);
}
}
function loadTrack(reset) {
var t = tracks[idx];
titleEl.textContent = t.title;
artistEl.textContent = t.artist;
durEl.textContent = fmt(t.dur);
artEl.style.background =
"radial-gradient(circle at 30% 25%,rgba(255,255,255,0.25),transparent 45%)," + t.art;
if (reset) {
elapsed = 0;
}
updateProgress();
renderQueue();
}
function updateProgress() {
var t = tracks[idx];
var pct = Math.min(100, (elapsed / t.dur) * 100);
progEl.style.width = pct + "%";
curEl.textContent = fmt(elapsed);
}
function setPlaying(state) {
playing = state;
playBtn.setAttribute("aria-pressed", String(state));
playBtn.setAttribute("aria-label", state ? "Pause" : "Play");
playPath.setAttribute("d", state ? PAUSE_ICON : PLAY_ICON);
eqEl.classList.toggle("is-playing", state && !reduceMotion);
if (state) startTick();
else stopTick();
}
function startTick() {
stopTick();
tick = setInterval(function () {
elapsed += 1;
var t = tracks[idx];
if (elapsed >= t.dur) {
next(true);
return;
}
updateProgress();
}, 1000);
}
function stopTick() {
if (tick) {
clearInterval(tick);
tick = null;
}
}
function next(auto) {
idx = (idx + 1) % tracks.length;
loadTrack(true);
if (auto) {
// keep playing
if (playing) startTick();
} else {
setPlaying(true);
}
}
function prev() {
if (elapsed > 3) {
elapsed = 0;
updateProgress();
return;
}
idx = (idx - 1 + tracks.length) % tracks.length;
loadTrack(true);
setPlaying(true);
}
if (playBtn) {
playBtn.addEventListener("click", function () {
setPlaying(!playing);
});
document.getElementById("npNext").addEventListener("click", function () { next(false); });
document.getElementById("npPrev").addEventListener("click", prev);
// Scrub by clicking the progress bar
var barEl = progEl.parentElement;
barEl.addEventListener("click", function (e) {
var rect = barEl.getBoundingClientRect();
var ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
elapsed = Math.floor(tracks[idx].dur * ratio);
updateProgress();
});
loadTrack(true);
setPlaying(true);
}
/* ---------- Pricing: monthly / annual toggle ---------- */
var toggleWrap = document.querySelector(".toggle");
var billToggle = document.getElementById("billToggle");
var amts = document.querySelectorAll(".plan__amt[data-m]");
var perPremium = document.getElementById("perPremium");
var perFamily = document.getElementById("perFamily");
function setBilling(annual) {
billToggle.setAttribute("aria-checked", String(annual));
toggleWrap.classList.toggle("is-annual", annual);
amts.forEach(function (el) {
var val = annual ? el.getAttribute("data-a") : el.getAttribute("data-m");
el.textContent = "$" + val;
});
var per = annual ? "/mo, billed yearly" : "/month";
if (perPremium) perPremium.textContent = per;
if (perFamily) perFamily.textContent = per;
}
if (billToggle) {
billToggle.addEventListener("click", function () {
setBilling(billToggle.getAttribute("aria-checked") !== "true");
});
}
/* ---------- Count-up stats ---------- */
function formatStat(n, suffix) {
var out;
if (n >= 1000000) out = (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + "M";
else if (n >= 1000) out = Math.round(n / 1000) + "K";
else out = String(n);
return out + (suffix || "");
}
function countUp(el) {
var target = parseInt(el.getAttribute("data-count"), 10);
var suffix = el.getAttribute("data-suffix") || "";
var numEl = el.querySelector(".stat__num");
if (reduceMotion) {
numEl.textContent = formatStat(target, suffix);
return;
}
var dur = 1400;
var start = null;
function step(ts) {
if (!start) start = ts;
var p = Math.min(1, (ts - start) / dur);
var eased = 1 - Math.pow(1 - p, 3);
numEl.textContent = formatStat(Math.round(target * eased), suffix);
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
/* ---------- Scroll reveal + lazy count-up ---------- */
var revealEls = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window && !reduceMotion) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("is-in");
if (entry.target.classList.contains("stat")) countUp(entry.target);
io.unobserve(entry.target);
}
});
}, { threshold: 0.18 });
revealEls.forEach(function (el) { io.observe(el); });
} else {
revealEls.forEach(function (el) {
el.classList.add("is-in");
if (el.classList.contains("stat")) countUp(el);
});
}
/* ---------- Parallax-ish blob drift on pointer move ---------- */
if (!reduceMotion) {
var blobs = document.querySelectorAll(".blob");
window.addEventListener("pointermove", function (e) {
var x = (e.clientX / window.innerWidth - 0.5) * 2;
var y = (e.clientY / window.innerHeight - 0.5) * 2;
blobs.forEach(function (b, i) {
var depth = (i + 1) * 8;
b.style.marginLeft = x * depth + "px";
b.style.marginTop = y * depth + "px";
});
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pulsewave — Music for every moment</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=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Ambient gradient blobs -->
<div class="blobs" aria-hidden="true">
<span class="blob blob--cyan"></span>
<span class="blob blob--violet"></span>
<span class="blob blob--magenta"></span>
</div>
<header class="nav" id="top">
<a class="brand" href="#top" aria-label="Pulsewave home">
<span class="brand__mark" aria-hidden="true">
<i></i><i></i><i></i><i></i>
</span>
<span class="brand__name">Pulsewave</span>
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#features">Features</a>
<a href="#stats">Catalog</a>
<a href="#pricing">Plans</a>
</nav>
<div class="nav__cta">
<button class="btn btn--ghost" type="button" data-toast="Welcome back — sign in flow is illustrative.">Log in</button>
<button class="btn btn--solid" type="button" data-toast="Free plan started. Enjoy the music!">Get started</button>
</div>
</header>
<main>
<!-- HERO -->
<section class="hero" aria-labelledby="hero-title">
<div class="hero__copy reveal">
<span class="eyebrow">New · Spatial audio rolling out</span>
<h1 id="hero-title">Music for<br /><em>every</em> moment.</h1>
<p class="lede">
Stream over 90 million tracks in lossless quality. Download for offline,
get a feed tuned to your taste, and press play anywhere — phone, desktop, car.
</p>
<div class="hero__actions">
<button class="btn btn--solid btn--lg" type="button" data-toast="Free plan started — no card required.">
Start listening free
</button>
<div class="stores">
<button class="store" type="button" data-toast="App Store link is illustrative.">
<span class="store__glyph"></span>
<span class="store__txt"><small>Download on the</small><strong>App Store</strong></span>
</button>
<button class="store" type="button" data-toast="Google Play link is illustrative.">
<span class="store__glyph store__glyph--play"></span>
<span class="store__txt"><small>Get it on</small><strong>Google Play</strong></span>
</button>
</div>
</div>
<ul class="hero__trust">
<li><strong>4.9</strong><span>App rating</span></li>
<li><strong>120M+</strong><span>Listeners</span></li>
<li><strong>30 days</strong><span>Free Premium</span></li>
</ul>
</div>
<!-- Phone mockup -->
<div class="hero__device reveal">
<div class="phone" role="group" aria-label="Now playing preview">
<div class="phone__notch" aria-hidden="true"></div>
<div class="phone__screen">
<div class="np">
<div class="np__top">
<span class="np__chip">Now playing</span>
<span class="np__menu" aria-hidden="true">···</span>
</div>
<div class="np__art" id="npArt" aria-hidden="true">
<span class="np__eq" id="npEq">
<i></i><i></i><i></i><i></i><i></i>
</span>
</div>
<div class="np__meta">
<h3 class="np__title" id="npTitle">Paper Lanterns</h3>
<p class="np__artist" id="npArtist">Neon Tides</p>
</div>
<div class="np__scrub">
<div class="np__bar"><span id="npProgress"></span></div>
<div class="np__time">
<span id="npCur">0:00</span>
<span id="npDur">3:42</span>
</div>
</div>
<div class="np__controls">
<button class="np__btn" type="button" id="npPrev" aria-label="Previous track">⏮</button>
<button class="np__btn np__btn--play" type="button" id="npPlay" aria-label="Play" aria-pressed="true">
<svg viewBox="0 0 24 24" id="npIcon" aria-hidden="true"><path id="npPath" d="M8 5v14l11-7z"/></svg>
</button>
<button class="np__btn" type="button" id="npNext" aria-label="Next track">⏭</button>
</div>
<div class="np__queue">
<span class="np__queue-label">Up next</span>
<ul id="npQueue"></ul>
</div>
</div>
</div>
</div>
<div class="device__glow" aria-hidden="true"></div>
</div>
</section>
<!-- FEATURES -->
<section class="features" id="features" aria-labelledby="feat-title">
<h2 id="feat-title" class="section-title reveal">Built for the way you listen</h2>
<div class="feature-grid">
<article class="card reveal">
<span class="card__icon card__icon--offline" aria-hidden="true"></span>
<h3>Listen offline</h3>
<p>Download albums and playlists, then play with zero signal — on a flight, underground, anywhere.</p>
</article>
<article class="card reveal">
<span class="card__icon card__icon--lossless" aria-hidden="true">
<i></i><i></i><i></i><i></i>
</span>
<h3>Lossless & spatial</h3>
<p>Hear every detail in studio-quality lossless, with immersive spatial mixes on supported tracks.</p>
</article>
<article class="card reveal">
<span class="card__icon card__icon--personal" aria-hidden="true"></span>
<h3>Tuned to you</h3>
<p>A feed that learns. Fresh mixes every morning and discovery built from what you actually love.</p>
</article>
</div>
</section>
<!-- STAT BAND -->
<section class="stats" id="stats" aria-labelledby="stats-title">
<h2 id="stats-title" class="section-title reveal">Millions of tracks. One tap away.</h2>
<div class="stat-row">
<div class="stat reveal" data-count="90000000" data-suffix="+">
<strong class="stat__num">0</strong>
<span class="stat__label">Tracks streaming</span>
</div>
<div class="stat reveal" data-count="8000000" data-suffix="+">
<strong class="stat__num">0</strong>
<span class="stat__label">Artists</span>
</div>
<div class="stat reveal" data-count="120" data-suffix="M+">
<strong class="stat__num">0</strong>
<span class="stat__label">Listeners worldwide</span>
</div>
<div class="stat reveal" data-count="184" data-suffix="">
<strong class="stat__num">0</strong>
<span class="stat__label">Countries</span>
</div>
</div>
</section>
<!-- PRICING -->
<section class="pricing" id="pricing" aria-labelledby="pricing-title">
<h2 id="pricing-title" class="section-title reveal">Pick a plan, press play</h2>
<div class="toggle reveal" role="group" aria-label="Billing period">
<span class="toggle__label" data-on="monthly">Monthly</span>
<button class="toggle__switch" type="button" id="billToggle" role="switch" aria-checked="false" aria-label="Annual billing">
<span class="toggle__knob"></span>
</button>
<span class="toggle__label" data-on="annual">Annual <em class="toggle__save">save 20%</em></span>
</div>
<div class="plans">
<article class="plan reveal">
<header class="plan__head">
<h3>Free</h3>
<p class="plan__tag">Get going, on us</p>
</header>
<p class="plan__price"><span class="plan__amt" data-m="0" data-a="0">$0</span><span class="plan__per">/forever</span></p>
<ul class="plan__feats">
<li>Shuffle play across the catalog</li>
<li>Ad-supported listening</li>
<li>Standard audio quality</li>
<li>Listen on mobile & web</li>
</ul>
<button class="btn btn--ghost btn--block" type="button" data-toast="Free plan started.">Start free</button>
</article>
<article class="plan plan--featured reveal">
<span class="plan__badge">Most popular</span>
<header class="plan__head">
<h3>Premium</h3>
<p class="plan__tag">For the serious listener</p>
</header>
<p class="plan__price"><span class="plan__amt" data-m="10.99" data-a="8.79">$10.99</span><span class="plan__per" id="perPremium">/month</span></p>
<ul class="plan__feats">
<li>Ad-free, on-demand playback</li>
<li>Lossless & spatial audio</li>
<li>Unlimited offline downloads</li>
<li>Skip as much as you want</li>
</ul>
<button class="btn btn--solid btn--block" type="button" data-toast="Premium trial activated — 30 days free.">Try 30 days free</button>
</article>
<article class="plan reveal">
<header class="plan__head">
<h3>Family</h3>
<p class="plan__tag">Up to 6 accounts</p>
</header>
<p class="plan__price"><span class="plan__amt" data-m="16.99" data-a="13.59">$16.99</span><span class="plan__per" id="perFamily">/month</span></p>
<ul class="plan__feats">
<li>Six individual Premium logins</li>
<li>Parental controls & safe mode</li>
<li>Shared family playlists</li>
<li>One bill, everyone covered</li>
</ul>
<button class="btn btn--ghost btn--block" type="button" data-toast="Family plan selected.">Choose Family</button>
</article>
</div>
</section>
<!-- CTA STRIP -->
<section class="cta reveal">
<div class="cta__inner">
<h2>Your next favorite song is one tap away.</h2>
<button class="btn btn--solid btn--lg" type="button" data-toast="Free plan started — no card required.">Start listening free</button>
</div>
</section>
</main>
<footer class="footer">
<div class="footer__cols">
<div class="footer__brand">
<span class="brand__name">Pulsewave</span>
<p>Music for every moment. Stream, download, discover.</p>
</div>
<nav aria-label="Company"><h4>Company</h4><a href="#">About</a><a href="#">Jobs</a><a href="#">Press</a><a href="#">Newsroom</a></nav>
<nav aria-label="Communities"><h4>Communities</h4><a href="#">For Artists</a><a href="#">Developers</a><a href="#">Advertising</a><a href="#">Investors</a></nav>
<nav aria-label="Useful links"><h4>Useful links</h4><a href="#">Support</a><a href="#">Mobile app</a><a href="#">Gift cards</a><a href="#">Status</a></nav>
</div>
<div class="footer__bar">
<span>© 2026 Pulsewave Audio (fictional). Illustrative UI.</span>
<span class="footer__legal"><a href="#">Privacy</a><a href="#">Terms</a><a href="#">Cookies</a></span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Streaming / Modern Pop Landing
A premium marketing landing for Pulsewave, a fictional modern-pop streaming service. The hero leads with a bold Space Grotesk headline and a gradient-clipped accent word, a “Start listening free” call to action, and App Store / Google Play buttons, all set against three slowly drifting neon glow blobs that also react to the pointer. Beside the copy, a floating phone mockup renders a glassy now-playing screen: a CSS-drawn album cover with an animated equalizer, a scrubbable progress bar with live timestamps, a morphing play/pause button, and an “Up next” queue that updates as tracks change.
The page then steps through a glassy feature trio — offline downloads, lossless and spatial audio, and a personalized feed — a catalog stat band that counts up to 90M+ tracks and 120M+ listeners as it scrolls into view, and a three-tier pricing grid (Free, Premium, Family) with a monthly/annual switch that re-prices every plan and applies a 20% annual discount. A closing CTA strip and a full multi-column footer round it out.
Everything is vanilla JS: the mockup auto-advances tracks on a timer and can be paused, skipped, or scrubbed; the equalizer animates only while playing; the billing toggle is a real role="switch"; stats count up via requestAnimationFrame; and a shared toast() helper confirms every illustrative click. Sections reveal on scroll, controls are keyboard-focusable with visible focus rings, and all motion (blobs, float, equalizer, reveals) is disabled under prefers-reduced-motion. The layout is responsive down to ~360px.
Illustrative UI only — fictional artists, albums, tracks, and data. No real audio playback.