Pages Hard
FREQ — Music & Podcast Platform
Neon cyberpunk music platform with magenta/cyan palette, Space Mono monospace typography, animated Canvas 2D waveform background, scrolling artist marquee, chart bars with glitch hover, and typing terminal CTA.
Open in Lab
MCP
gsap scrolltrigger lenis canvas-2d space-mono
Targets: JS HTML
Code
/* ── Demo 47: Music Platform — Neon Cyberpunk ── */
/* Palette: Black / Neon Magenta / Cyan / Lime */
/* Fonts: Space Mono (display/code) + Space Grotesk (body) */
:root {
--black: #080810;
--dark: #0d0d1a;
--panel: #111120;
--border: #1e1e38;
--text: #e8e8f8;
--muted: #6b6b90;
--faint: #2a2a48;
--magenta: #ff2d78;
--cyan: #00e5ff;
--lime: #a8ff3e;
--purple: #9b4dff;
--gold: #ffcc00;
--neon-glow-m: 0 0 8px rgba(255, 45, 120, 0.6), 0 0 25px rgba(255, 45, 120, 0.25);
--neon-glow-c: 0 0 8px rgba(0, 229, 255, 0.6), 0 0 25px rgba(0, 229, 255, 0.25);
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Space Grotesk", -apple-system, sans-serif;
background: var(--black);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
/* ── Scan lines overlay ── */
.scanlines {
position: fixed;
inset: 0;
z-index: 200;
pointer-events: none;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.06) 2px,
rgba(0, 0, 0, 0.06) 4px
);
}
/* ── Navigation ── */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 2.5rem;
background: rgba(8, 8, 16, 0.85);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
}
.nav-brand {
display: flex;
align-items: center;
gap: 0.5rem;
}
.brand-mark {
font-size: 1.2rem;
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
.brand-name {
font-family: "Space Mono", monospace;
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--text);
}
.nav-links {
display: flex;
gap: 2.5rem;
list-style: none;
}
.nav-links a {
text-decoration: none;
font-family: "Space Mono", monospace;
font-size: 0.72rem;
font-weight: 400;
letter-spacing: 0.12em;
color: var(--muted);
text-transform: uppercase;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--cyan);
text-shadow: var(--neon-glow-c);
}
.nav-right {
display: flex;
align-items: center;
gap: 1.5rem;
}
.nav-live {
display: flex;
align-items: center;
gap: 0.4rem;
font-family: "Space Mono", monospace;
font-size: 0.68rem;
letter-spacing: 0.15em;
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
.live-dot {
width: 6px;
height: 6px;
background: var(--magenta);
border-radius: 50%;
animation: pulse-dot 1.2s ease-in-out infinite;
box-shadow: var(--neon-glow-m);
}
@keyframes pulse-dot {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.4;
transform: scale(0.7);
}
}
/* ── Buttons ── */
.btn-neon {
display: inline-block;
padding: 0.7rem 1.5rem;
background: transparent;
color: var(--magenta);
border: 1px solid var(--magenta);
font-family: "Space Mono", monospace;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: inset 0 0 0 0 var(--magenta);
}
.btn-neon:hover {
background: var(--magenta);
color: var(--black);
box-shadow: var(--neon-glow-m);
}
.btn-neon--lg {
padding: 1rem 2.5rem;
font-size: 0.82rem;
}
.btn-ghost {
display: inline-flex;
align-items: center;
gap: 0.6rem;
padding: 0.9rem 1.8rem;
background: transparent;
color: var(--text);
border: 1px solid var(--faint);
font-family: "Space Grotesk", sans-serif;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
}
.btn-ghost:hover {
border-color: var(--cyan);
color: var(--cyan);
}
.play-icon {
font-size: 0.7rem;
}
.btn-outline {
display: inline-block;
padding: 0.75rem 1.5rem;
background: transparent;
color: var(--text);
border: 1px solid var(--border);
font-family: "Space Mono", monospace;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
text-decoration: none;
transition: border-color 0.2s, color 0.2s;
}
.btn-outline:hover {
border-color: var(--cyan);
color: var(--cyan);
}
/* ── Hero ── */
.hero {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
padding: 7rem 2.5rem 5rem;
overflow: hidden;
}
#waveform-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 1;
max-width: 720px;
}
.hero-tag {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 2rem;
font-family: "Space Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.1em;
color: var(--muted);
}
.tag-prefix {
color: var(--magenta);
text-shadow: var(--neon-glow-m);
font-size: 0.9rem;
}
.hero-h1 {
font-family: "Space Mono", monospace;
font-size: clamp(3.5rem, 9vw, 8rem);
font-weight: 700;
line-height: 0.95;
letter-spacing: -0.03em;
color: var(--text);
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.h1-line--neon {
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
.hero-sub {
font-size: 1rem;
color: var(--muted);
line-height: 1.8;
max-width: 520px;
margin-bottom: 2.5rem;
}
.hero-actions {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 3rem;
}
/* EQ bars */
.eq-bars {
display: flex;
align-items: flex-end;
gap: 3px;
height: 32px;
}
.eq-bars span {
width: 4px;
background: var(--magenta);
border-radius: 1px;
animation: eq-bounce 0.8s ease-in-out infinite alternate;
box-shadow: 0 0 4px rgba(255, 45, 120, 0.5);
}
.eq-bars span:nth-child(1) {
height: 60%;
animation-delay: 0.0s;
}
.eq-bars span:nth-child(2) {
height: 90%;
animation-delay: 0.1s;
}
.eq-bars span:nth-child(3) {
height: 40%;
animation-delay: 0.2s;
}
.eq-bars span:nth-child(4) {
height: 100%;
animation-delay: 0.15s;
}
.eq-bars span:nth-child(5) {
height: 70%;
animation-delay: 0.3s;
}
.eq-bars span:nth-child(6) {
height: 55%;
animation-delay: 0.25s;
}
.eq-bars span:nth-child(7) {
height: 85%;
animation-delay: 0.05s;
}
.eq-bars span:nth-child(8) {
height: 45%;
animation-delay: 0.35s;
}
.eq-bars span:nth-child(9) {
height: 75%;
animation-delay: 0.1s;
}
.eq-bars span:nth-child(10) {
height: 95%;
animation-delay: 0.2s;
}
.eq-bars span:nth-child(11) {
height: 50%;
animation-delay: 0.4s;
}
.eq-bars span:nth-child(12) {
height: 80%;
animation-delay: 0.15s;
}
.eq-bars span:nth-child(13) {
height: 65%;
animation-delay: 0.05s;
}
.eq-bars span:nth-child(14) {
height: 90%;
animation-delay: 0.3s;
}
.eq-bars span:nth-child(15) {
height: 40%;
animation-delay: 0.0s;
}
@keyframes eq-bounce {
from {
transform: scaleY(0.35);
}
to {
transform: scaleY(1);
}
}
/* Now playing card */
.now-playing {
position: absolute;
right: 3rem;
bottom: 4rem;
width: 280px;
padding: 1.25rem;
background: rgba(13, 13, 26, 0.9);
border: 1px solid var(--magenta);
box-shadow: var(--neon-glow-m);
z-index: 2;
}
.np-label {
font-family: "Space Mono", monospace;
font-size: 0.62rem;
letter-spacing: 0.18em;
color: var(--magenta);
margin-bottom: 0.5rem;
}
.np-title {
font-family: "Space Mono", monospace;
font-size: 0.95rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.2rem;
}
.np-artist {
font-size: 0.78rem;
color: var(--muted);
margin-bottom: 1rem;
}
.np-bar {
height: 2px;
background: var(--faint);
margin-bottom: 0.5rem;
overflow: hidden;
}
.np-progress {
height: 100%;
width: 50%;
background: var(--magenta);
box-shadow: 0 0 6px var(--magenta);
}
.np-time {
display: flex;
justify-content: space-between;
font-family: "Space Mono", monospace;
font-size: 0.68rem;
color: var(--muted);
}
/* ── Section tag ── */
.section-tag {
display: block;
font-family: "Space Mono", monospace;
font-size: 0.68rem;
letter-spacing: 0.15em;
color: var(--cyan);
text-shadow: var(--neon-glow-c);
margin-bottom: 1rem;
}
.neon-text {
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
/* ── Shows Section ── */
.shows-section {
padding: 5rem 2.5rem;
border-top: 1px solid var(--border);
}
.shows-header {
margin-bottom: 2.5rem;
}
.shows-header h2 {
font-family: "Space Mono", monospace;
font-size: clamp(2rem, 5vw, 4rem);
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text);
line-height: 1;
}
.shows-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto auto;
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
}
.show-card--featured {
grid-row: 1 / 3;
}
.show-card {
background: var(--dark);
overflow: hidden;
transition: background 0.3s;
cursor: pointer;
}
.show-card:hover {
background: var(--panel);
}
.sc-art {
height: 200px;
position: relative;
}
.show-card--featured .sc-art {
height: 300px;
}
.sc-art--1 {
background: radial-gradient(ellipse at 30% 50%, rgba(255, 45, 120, 0.4) 0%, transparent 60%),
radial-gradient(ellipse at 70% 30%, rgba(155, 77, 255, 0.35) 0%, transparent 50%), var(--dark);
}
.sc-art--2 {
background: radial-gradient(ellipse at 50% 70%, rgba(0, 229, 255, 0.3) 0%, transparent 60%),
radial-gradient(ellipse at 20% 20%, rgba(168, 255, 62, 0.2) 0%, transparent 50%), var(--dark);
}
.sc-art--3 {
background: radial-gradient(ellipse at 80% 50%, rgba(155, 77, 255, 0.4) 0%, transparent 60%),
radial-gradient(ellipse at 30% 80%, rgba(0, 229, 255, 0.2) 0%, transparent 50%), var(--dark);
}
.sc-art--4 {
background: radial-gradient(ellipse at 50% 30%, rgba(255, 204, 0, 0.25) 0%, transparent 55%),
radial-gradient(ellipse at 70% 70%, rgba(255, 45, 120, 0.25) 0%, transparent 50%), var(--dark);
}
.sc-info {
padding: 1.5rem;
border-top: 1px solid var(--border);
}
.sc-info--sm {
padding: 1.2rem;
}
.sc-genre {
font-family: "Space Mono", monospace;
font-size: 0.62rem;
letter-spacing: 0.18em;
color: var(--cyan);
display: block;
margin-bottom: 0.4rem;
}
.sc-info h3 {
font-family: "Space Mono", monospace;
font-size: 1.2rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.6rem;
}
.sc-info--sm h3 {
font-size: 0.95rem;
}
.sc-info p {
font-size: 0.85rem;
color: var(--muted);
line-height: 1.65;
margin-bottom: 0.8rem;
}
.sc-meta {
font-family: "Space Mono", monospace;
font-size: 0.65rem;
color: var(--muted);
margin-bottom: 1rem;
display: flex;
gap: 0.4rem;
}
.dot {
color: var(--faint);
}
.sc-stats {
display: flex;
gap: 1.5rem;
font-family: "Space Mono", monospace;
font-size: 0.75rem;
color: var(--text);
}
.sc-stats span {
display: flex;
gap: 0.4rem;
}
.sc-stats em {
font-style: normal;
color: var(--muted);
font-size: 0.65rem;
align-self: flex-end;
}
/* ── Charts Section ── */
.charts-section {
padding: 5rem 2.5rem;
background: var(--dark);
border-top: 1px solid var(--border);
}
.charts-inner {
max-width: 900px;
}
.charts-header {
margin-bottom: 3rem;
}
.charts-header h2 {
font-family: "Space Mono", monospace;
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 700;
line-height: 1.1;
color: var(--text);
}
.chart-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 0;
border: 1px solid var(--border);
}
.chart-item {
display: grid;
grid-template-columns: 50px 1fr 200px 70px 50px;
align-items: center;
gap: 1rem;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
transition: background 0.2s;
cursor: pointer;
}
.chart-item:last-child {
border-bottom: none;
}
.chart-item:hover {
background: var(--panel);
}
.ci-rank {
font-family: "Space Mono", monospace;
font-size: 1.2rem;
font-weight: 700;
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
.ci-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.ci-info strong {
font-family: "Space Mono", monospace;
font-size: 0.85rem;
font-weight: 700;
color: var(--text);
letter-spacing: 0.02em;
}
.ci-info span {
font-size: 0.75rem;
color: var(--muted);
}
.ci-bar-wrap {
height: 2px;
background: var(--faint);
overflow: hidden;
}
.ci-bar {
height: 100%;
width: var(--w, 0%);
background: linear-gradient(90deg, var(--magenta), var(--purple));
transform-origin: left;
transform: scaleX(0);
box-shadow: 0 0 4px var(--magenta);
}
.ci-plays {
font-family: "Space Mono", monospace;
font-size: 0.72rem;
color: var(--muted);
text-align: right;
}
.ci-delta {
font-family: "Space Mono", monospace;
font-size: 0.65rem;
font-weight: 700;
text-align: center;
}
.ci-delta.up {
color: var(--lime);
}
.ci-delta.down {
color: var(--magenta);
}
.ci-delta.new {
color: var(--gold);
}
/* ── Artists Section ── */
.artists-section {
padding: 5rem 2.5rem;
border-top: 1px solid var(--border);
}
.artists-header {
margin-bottom: 3rem;
}
.artists-header h2 {
font-family: "Space Mono", monospace;
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 700;
line-height: 1.1;
color: var(--text);
}
/* Marquee */
.artists-marquee-wrap {
overflow: hidden;
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
margin-bottom: 4rem;
}
.marquee-track {
display: flex;
gap: 1rem;
width: max-content;
animation: marquee-scroll 28s linear infinite;
}
@keyframes marquee-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.artist-chip {
flex-shrink: 0;
padding: 0.5rem 1.25rem;
border: 1px solid var(--border);
font-family: "Space Mono", monospace;
font-size: 0.72rem;
letter-spacing: 0.08em;
color: var(--muted);
white-space: nowrap;
transition: border-color 0.2s, color 0.2s;
}
.chip--accent {
border-color: rgba(255, 45, 120, 0.4);
color: var(--magenta);
}
/* Platform stats */
.platform-stats {
display: flex;
align-items: center;
gap: 3rem;
padding: 2.5rem 2rem;
border: 1px solid var(--border);
background: var(--panel);
}
.ps-item {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.ps-num {
font-family: "Space Mono", monospace;
font-size: 2.2rem;
font-weight: 700;
color: var(--cyan);
text-shadow: var(--neon-glow-c);
line-height: 1;
}
.ps-label {
font-size: 0.75rem;
color: var(--muted);
letter-spacing: 0.05em;
}
.ps-div {
width: 1px;
height: 50px;
background: var(--border);
}
/* ── Studio / CTA ── */
.studio-section {
padding: 5rem 2.5rem;
background: var(--dark);
border-top: 1px solid var(--border);
}
.studio-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.studio-left h2 {
font-family: "Space Mono", monospace;
font-size: clamp(2rem, 4vw, 3.2rem);
font-weight: 700;
line-height: 1.1;
color: var(--text);
margin-bottom: 1.2rem;
}
.studio-left p {
font-size: 0.95rem;
color: var(--muted);
line-height: 1.8;
max-width: 400px;
margin-bottom: 2rem;
}
.studio-links {
display: flex;
gap: 1rem;
align-items: center;
}
/* Terminal */
.studio-terminal {
background: #0a0a14;
border: 1px solid var(--border);
border-top: 1px solid var(--cyan);
box-shadow: var(--neon-glow-c);
font-family: "Space Mono", monospace;
font-size: 0.82rem;
overflow: hidden;
}
.terminal-bar {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 1rem;
background: var(--panel);
border-bottom: 1px solid var(--border);
}
.tb-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.tb-dot--red {
background: #ff5f56;
}
.tb-dot--yellow {
background: #ffbd2e;
}
.tb-dot--green {
background: #27c93f;
}
.tb-title {
margin-left: 0.5rem;
font-size: 0.7rem;
color: var(--muted);
}
.terminal-body {
padding: 1.2rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
min-height: 180px;
}
.terminal-body p {
line-height: 1.5;
}
.t-prompt {
color: var(--lime);
margin-right: 0.5rem;
}
.t-output {
color: var(--muted);
padding-left: 1.2rem;
}
.t-success {
color: var(--lime);
}
.t-val {
color: var(--text);
}
.t-neon {
color: var(--magenta);
text-shadow: var(--neon-glow-m);
}
.t-cursor {
color: var(--cyan);
animation: cursor-blink 1s step-end infinite;
}
@keyframes cursor-blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* ── Footer ── */
.footer {
padding: 1.5rem 2.5rem;
background: var(--black);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.footer-brand {
display: flex;
align-items: center;
gap: 0.5rem;
font-family: "Space Mono", monospace;
font-size: 0.78rem;
color: var(--muted);
}
.footer-brand .brand-mark {
font-size: 0.9rem;
}
.footer p {
font-family: "Space Mono", monospace;
font-size: 0.65rem;
color: var(--muted);
letter-spacing: 0.05em;
}
/* ── Responsive ── */
@media (max-width: 1024px) {
.shows-grid {
grid-template-columns: 1fr 1fr;
}
.show-card--featured {
grid-column: 1 / 3;
grid-row: auto;
}
.studio-inner {
grid-template-columns: 1fr;
}
.chart-item {
grid-template-columns: 40px 1fr 100px 60px 40px;
}
}
@media (max-width: 768px) {
.nav-links {
display: none;
}
.now-playing {
display: none;
}
.shows-grid {
grid-template-columns: 1fr;
}
.show-card--featured {
grid-column: auto;
}
.chart-item {
grid-template-columns: 40px 1fr 50px 40px;
}
.ci-bar-wrap {
display: none;
}
.platform-stats {
flex-wrap: wrap;
gap: 1.5rem;
}
}
/* ── Reduced Motion ── */
html.reduced-motion .eq-bars span,
html.reduced-motion .live-dot,
html.reduced-motion .t-cursor,
html.reduced-motion .marquee-track {
animation: none !important;
}
html.reduced-motion .eq-bars span {
height: 60% !important;
transform: none !important;
}
html.reduced-motion * {
transition-duration: 0.01ms !important;
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import Lenis from "lenis";
gsap.registerPlugin(ScrollTrigger);
initDemoShell({
title: "FREQ — Music Platform",
category: "pages",
tech: ["gsap", "scroll-trigger", "lenis", "canvas-2d", "space-mono"],
});
const reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add("reduced-motion");
// ── Lenis smooth scroll ───────────────────────────────────────────────────────
const lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
// ── Canvas Waveform Background ────────────────────────────────────────────────
const canvas = document.getElementById("waveform-canvas");
const ctx = canvas.getContext("2d");
let w, h;
let scrollY = 0;
function resizeCanvas() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
lenis.on("scroll", (e) => {
scrollY = e.scroll;
});
function drawWaveform(time) {
ctx.clearRect(0, 0, w, h);
const numWaves = 4;
const waveConfigs = [
{ color: "rgba(255,45,120,", freq: 0.003, amp: 60, speed: 0.4, y: h * 0.3 },
{ color: "rgba(155,77,255,", freq: 0.004, amp: 40, speed: 0.6, y: h * 0.5 },
{ color: "rgba(0,229,255,", freq: 0.0025, amp: 50, speed: 0.3, y: h * 0.7 },
{ color: "rgba(168,255,62,", freq: 0.005, amp: 25, speed: 0.8, y: h * 0.85 },
];
const scrollOffset = scrollY * 0.3;
waveConfigs.forEach((cfg, idx) => {
ctx.beginPath();
const opacity = 0.08 - idx * 0.015;
for (let x = 0; x <= w; x += 2) {
const y =
cfg.y +
Math.sin((x + scrollOffset) * cfg.freq + time * cfg.speed) * cfg.amp +
Math.sin((x * 0.7 + scrollOffset * 0.8) * cfg.freq * 1.6 + time * cfg.speed * 1.3) *
(cfg.amp * 0.4);
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = `${cfg.color}${opacity})`;
ctx.lineWidth = 1.5;
ctx.stroke();
});
// Subtle horizontal grid lines
ctx.strokeStyle = "rgba(30, 30, 56, 0.6)";
ctx.lineWidth = 1;
for (let y = 0; y < h; y += 60) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
// Vertical grid lines
for (let x = 0; x < w; x += 80) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.stroke();
}
}
// ── Main animation loop ───────────────────────────────────────────────────────
if (!reduced) {
let startTime = null;
function tick(ts) {
if (!startTime) startTime = ts;
const elapsed = (ts - startTime) / 1000;
drawWaveform(elapsed);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
// ── Hero entrance ─────────────────────────────────────────────────────────────
if (!reduced) {
gsap.set([".hero-tag", ".hero-h1 .h1-line", ".hero-sub", ".hero-actions", ".eq-bars"], {
opacity: 0,
y: 30,
});
gsap.set(".now-playing", { opacity: 0, x: 30 });
gsap
.timeline({ delay: 0.4, defaults: { ease: "expo.out" } })
.to(".hero-tag", { opacity: 1, y: 0, duration: 0.7 })
.to(".h1-line:nth-child(1)", { opacity: 1, y: 0, duration: 0.8 }, "-=0.3")
.to(".h1-line:nth-child(2)", { opacity: 1, y: 0, duration: 0.8 }, "-=0.5")
.to(".hero-sub", { opacity: 1, y: 0, duration: 0.7 }, "-=0.4")
.to(".hero-actions", { opacity: 1, y: 0, duration: 0.6 }, "-=0.3")
.to(".eq-bars", { opacity: 1, y: 0, duration: 0.5 }, "-=0.2")
.to(".now-playing", { opacity: 1, x: 0, duration: 0.8, ease: "back.out(1.2)" }, "-=0.6");
}
// ── Now Playing progress animation ───────────────────────────────────────────
gsap.to(".np-progress", {
width: "75%",
duration: 30,
ease: "none",
repeat: -1,
yoyo: true,
});
// ── Shows section reveal ──────────────────────────────────────────────────────
if (!reduced) {
gsap.set(".shows-header > *", { opacity: 0, y: 20 });
gsap.to(".shows-header > *", {
opacity: 1,
y: 0,
duration: 0.8,
stagger: 0.1,
ease: "expo.out",
scrollTrigger: {
trigger: ".shows-header",
start: "top 80%",
toggleActions: "play none none reverse",
},
});
document.querySelectorAll(".show-card").forEach((card, i) => {
gsap.set(card, { opacity: 0 });
gsap.to(card, {
opacity: 1,
duration: 0.6,
delay: i * 0.08,
ease: "power2.out",
scrollTrigger: {
trigger: ".shows-grid",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
});
}
// ── Charts — bar reveal on scroll ────────────────────────────────────────────
document.querySelectorAll(".ci-bar").forEach((bar, i) => {
if (!reduced) {
gsap.to(bar, {
scaleX: 1,
duration: 1,
ease: "expo.out",
delay: i * 0.1,
scrollTrigger: {
trigger: ".chart-list",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
} else {
bar.style.transform = "scaleX(1)";
}
});
if (!reduced) {
document.querySelectorAll(".chart-item").forEach((item, i) => {
gsap.set(item, { opacity: 0, x: -20 });
gsap.to(item, {
opacity: 1,
x: 0,
duration: 0.6,
ease: "expo.out",
delay: i * 0.08,
scrollTrigger: {
trigger: ".chart-list",
start: "top 80%",
toggleActions: "play none none reverse",
},
});
});
// Hover glitch on chart items
document.querySelectorAll(".chart-item").forEach((item) => {
item.addEventListener("mouseenter", () => {
if (reduced) return;
gsap.to(item, { x: 4, duration: 0.05, yoyo: true, repeat: 2, ease: "power1.inOut" });
});
});
}
// ── Platform stats counters ───────────────────────────────────────────────────
document.querySelectorAll(".ps-num").forEach((el) => {
const target = parseFloat(el.dataset.target);
const suffix = el.dataset.suffix || "";
const isDecimal = !Number.isInteger(target);
if (!reduced) {
ScrollTrigger.create({
trigger: ".platform-stats",
start: "top 80%",
end: "top 30%",
onUpdate: (self) => {
const val = target * self.progress;
el.textContent = (isDecimal ? val.toFixed(1) : Math.round(val)) + suffix;
},
});
} else {
el.textContent = target + suffix;
}
});
// ── Terminal typing effect ────────────────────────────────────────────────────
if (!reduced) {
const terminal = document.querySelector(".terminal-body");
const lines = terminal.querySelectorAll("p");
lines.forEach((line, i) => {
gsap.set(line, { opacity: 0 });
gsap.to(line, {
opacity: 1,
duration: 0.01,
delay: i * 0.25,
scrollTrigger: {
trigger: ".studio-terminal",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
});
}
// ── Studio section reveal ─────────────────────────────────────────────────────
if (!reduced) {
gsap.set(".studio-left > *", { opacity: 0, y: 20 });
gsap.to(".studio-left > *", {
opacity: 1,
y: 0,
duration: 0.8,
stagger: 0.1,
ease: "expo.out",
scrollTrigger: {
trigger: ".studio-section",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
gsap.set(".studio-terminal", { opacity: 0, y: 25 });
gsap.to(".studio-terminal", {
opacity: 1,
y: 0,
duration: 0.9,
ease: "expo.out",
delay: 0.2,
scrollTrigger: {
trigger: ".studio-section",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
}
// ── Marquee pause on hover ────────────────────────────────────────────────────
const marqueeTrack = document.getElementById("marquee-track");
if (marqueeTrack) {
marqueeTrack.addEventListener("mouseenter", () => {
marqueeTrack.style.animationPlayState = "paused";
});
marqueeTrack.addEventListener("mouseleave", () => {
marqueeTrack.style.animationPlayState = "running";
});
}
// ── Motion preference toggle ──────────────────────────────────────────────────
window.addEventListener("motion-preference", (e) => {
if (e.detail.reduced) {
gsap.globalTimeline.paused(true);
} else {
gsap.globalTimeline.paused(false);
}
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FREQ — Music & Podcast Platform</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+Mono:ital,wght@0,400;0,700;1,400&family=Space+Grotesk:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"https://esm.sh/gsap@3.13.0","gsap/ScrollTrigger":"https://esm.sh/gsap@3.13.0/ScrollTrigger","gsap/SplitText":"https://esm.sh/gsap@3.13.0/SplitText","gsap/Flip":"https://esm.sh/gsap@3.13.0/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/gsap@3.13.0/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/gsap@3.13.0/TextPlugin","gsap/all":"https://esm.sh/gsap@3.13.0/all","gsap/":"https://esm.sh/gsap@3.13.0/","lenis":"https://esm.sh/lenis@1.1.13/dist/lenis.mjs","three":"https://esm.sh/three@0.171.0","three/addons/":"https://esm.sh/three@0.171.0/examples/jsm/"}}</script>
<style>html.lenis,
html.lenis body {
height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
overflow: clip;
}
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] {
overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}
.lenis.lenis-autoToggle {
transition-property: overflow;
transition-duration: 1ms;
transition-behavior: allow-discrete;
}</style>
</head>
<body>
<!-- Scan line overlay -->
<div class="scanlines" aria-hidden="true"></div>
<!-- Nav -->
<nav class="nav" id="nav">
<div class="nav-brand">
<span class="brand-mark">◈</span>
<span class="brand-name">FREQ</span>
</div>
<ul class="nav-links">
<li><a href="#shows">Shows</a></li>
<li><a href="#charts">Charts</a></li>
<li><a href="#artists">Artists</a></li>
<li><a href="#contact">Studio</a></li>
</ul>
<div class="nav-right">
<span class="nav-live"><span class="live-dot"></span>LIVE</span>
<a href="#contact" class="btn-neon">Listen Now</a>
</div>
</nav>
<!-- Hero -->
<section class="hero" id="hero">
<!-- Waveform background -->
<canvas id="waveform-canvas" aria-hidden="true"></canvas>
<div class="hero-content">
<div class="hero-tag">
<span class="tag-prefix">///</span>
<span>New Episodes Daily</span>
</div>
<h1 class="hero-h1">
<span class="h1-line">THE SOUND</span>
<span class="h1-line h1-line--neon">OF NOW.</span>
</h1>
<p class="hero-sub">Underground beats. Cutting-edge podcasts. Live sessions from artists who refuse to be categorized.</p>
<div class="hero-actions">
<a href="#shows" class="btn-neon btn-neon--lg">Browse Shows</a>
<button class="btn-ghost" id="hero-play-btn">
<span class="play-icon">▶</span>
<span>Play Latest</span>
</button>
</div>
<!-- Equalizer bars -->
<div class="eq-bars" id="eq-bars" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
</div>
</div>
<!-- Now playing card -->
<div class="now-playing" id="now-playing">
<div class="np-label">NOW PLAYING</div>
<div class="np-title">Frequencies Vol. 12</div>
<div class="np-artist">DJ Novacore × CTRL</div>
<div class="np-bar">
<div class="np-progress"></div>
</div>
<div class="np-time"><span>27:14</span><span>54:00</span></div>
</div>
</section>
<!-- Shows Grid -->
<section class="shows-section" id="shows">
<div class="shows-header">
<span class="section-tag">// Featured Shows</span>
<h2>ON THE GRID</h2>
</div>
<div class="shows-grid">
<!-- Card 1 — Featured large -->
<article class="show-card show-card--featured" data-genre="ELECTRONIC">
<div class="sc-art sc-art--1"></div>
<div class="sc-info">
<span class="sc-genre">Electronic</span>
<h3>FREQUENCIES</h3>
<p>Deep techno and experimental electronics from the world's leading underground producers.</p>
<div class="sc-meta">
<span>124 episodes</span>
<span class="dot">·</span>
<span>New every Friday</span>
</div>
<div class="sc-stats">
<span>2.4M<em>listeners</em></span>
<span>★ 4.9</span>
</div>
</div>
</article>
<!-- Card 2 -->
<article class="show-card" data-genre="HIP-HOP">
<div class="sc-art sc-art--2"></div>
<div class="sc-info sc-info--sm">
<span class="sc-genre">Hip-Hop</span>
<h3>CIPHERS</h3>
<p>Live freestyle sessions with emerging MCs.</p>
<div class="sc-meta"><span>88 episodes</span></div>
</div>
</article>
<!-- Card 3 -->
<article class="show-card" data-genre="AMBIENT">
<div class="sc-art sc-art--3"></div>
<div class="sc-info sc-info--sm">
<span class="sc-genre">Ambient</span>
<h3>VOID SPACE</h3>
<p>Drone music and sonic experiments for deep focus.</p>
<div class="sc-meta"><span>62 episodes</span></div>
</div>
</article>
<!-- Card 4 -->
<article class="show-card" data-genre="INTERVIEWS">
<div class="sc-art sc-art--4"></div>
<div class="sc-info sc-info--sm">
<span class="sc-genre">Interviews</span>
<h3>SIGNAL/NOISE</h3>
<p>Long-form conversations about music, tech, and culture.</p>
<div class="sc-meta"><span>47 episodes</span></div>
</div>
</article>
</div>
</section>
<!-- Charts Section -->
<section class="charts-section" id="charts">
<div class="charts-inner">
<div class="charts-header">
<span class="section-tag">// Weekly Rankings</span>
<h2>TRANSMISSION<br><span class="neon-text">CHARTS</span></h2>
</div>
<ol class="chart-list">
<li class="chart-item" data-rank="01">
<span class="ci-rank">01</span>
<div class="ci-info">
<strong>AETHER PROTOCOL</strong>
<span>Machine Girl</span>
</div>
<div class="ci-bar-wrap"><div class="ci-bar" style="--w: 94%"></div></div>
<span class="ci-plays">1.2M</span>
<span class="ci-delta up">+3</span>
</li>
<li class="chart-item" data-rank="02">
<span class="ci-rank">02</span>
<div class="ci-info">
<strong>GHOST PROTOCOL (feat. ERIS)</strong>
<span>Burial × Novacore</span>
</div>
<div class="ci-bar-wrap"><div class="ci-bar" style="--w: 88%"></div></div>
<span class="ci-plays">980K</span>
<span class="ci-delta up">+1</span>
</li>
<li class="chart-item" data-rank="03">
<span class="ci-rank">03</span>
<div class="ci-info">
<strong>FRACTAL DECAY</strong>
<span>CLOAK</span>
</div>
<div class="ci-bar-wrap"><div class="ci-bar" style="--w: 81%"></div></div>
<span class="ci-plays">854K</span>
<span class="ci-delta new">NEW</span>
</li>
<li class="chart-item" data-rank="04">
<span class="ci-rank">04</span>
<div class="ci-info">
<strong>SUBTERRANEAN</strong>
<span>Vatican Shadow</span>
</div>
<div class="ci-bar-wrap"><div class="ci-bar" style="--w: 74%"></div></div>
<span class="ci-plays">712K</span>
<span class="ci-delta down">-2</span>
</li>
<li class="chart-item" data-rank="05">
<span class="ci-rank">05</span>
<div class="ci-info">
<strong>TRANSMISSION LOST</strong>
<span>Shapednoise</span>
</div>
<div class="ci-bar-wrap"><div class="ci-bar" style="--w: 67%"></div></div>
<span class="ci-plays">640K</span>
<span class="ci-delta up">+4</span>
</li>
</ol>
</div>
</section>
<!-- Artists Marquee -->
<section class="artists-section" id="artists">
<div class="artists-header">
<span class="section-tag">// Artists</span>
<h2>FEATURED<br><span class="neon-text">CREATORS</span></h2>
</div>
<div class="artists-marquee-wrap">
<div class="marquee-track" id="marquee-track">
<div class="artist-chip">Machine Girl</div>
<div class="artist-chip">Burial</div>
<div class="artist-chip chip--accent">DJ Novacore</div>
<div class="artist-chip">CLOAK</div>
<div class="artist-chip">Vatican Shadow</div>
<div class="artist-chip chip--accent">Shapednoise</div>
<div class="artist-chip">CTRL</div>
<div class="artist-chip">Arca</div>
<div class="artist-chip chip--accent">Eris Drew</div>
<div class="artist-chip">Container</div>
<div class="artist-chip">Actor</div>
<div class="artist-chip chip--accent">Paula Temple</div>
<!-- Duplicate for seamless loop -->
<div class="artist-chip">Machine Girl</div>
<div class="artist-chip">Burial</div>
<div class="artist-chip chip--accent">DJ Novacore</div>
<div class="artist-chip">CLOAK</div>
<div class="artist-chip">Vatican Shadow</div>
<div class="artist-chip chip--accent">Shapednoise</div>
<div class="artist-chip">CTRL</div>
<div class="artist-chip">Arca</div>
<div class="artist-chip chip--accent">Eris Drew</div>
<div class="artist-chip">Container</div>
<div class="artist-chip">Actor</div>
<div class="artist-chip chip--accent">Paula Temple</div>
</div>
</div>
<!-- Stats row -->
<div class="platform-stats">
<div class="ps-item">
<span class="ps-num" data-target="4200" data-suffix="+">0</span>
<span class="ps-label">Artists</span>
</div>
<div class="ps-div"></div>
<div class="ps-item">
<span class="ps-num" data-target="18000" data-suffix="+">0</span>
<span class="ps-label">Episodes</span>
</div>
<div class="ps-div"></div>
<div class="ps-item">
<span class="ps-num" data-target="9.2" data-suffix="M">0</span>
<span class="ps-label">Monthly Listeners</span>
</div>
<div class="ps-div"></div>
<div class="ps-item">
<span class="ps-num" data-target="140" data-suffix="">0</span>
<span class="ps-label">Countries</span>
</div>
</div>
</section>
<!-- CTA / Studio -->
<section class="studio-section" id="contact">
<div class="studio-inner">
<div class="studio-left">
<span class="section-tag">// Submit Your Show</span>
<h2>BROADCAST<br><span class="neon-text">YOUR SIGNAL.</span></h2>
<p>FREQ is open to independent creators. Apply to join our network and reach millions of listeners worldwide.</p>
<div class="studio-links">
<a href="#" class="btn-neon">Apply to Join</a>
<a href="#" class="btn-outline">View Criteria →</a>
</div>
</div>
<div class="studio-terminal">
<div class="terminal-bar">
<span class="tb-dot tb-dot--red"></span>
<span class="tb-dot tb-dot--yellow"></span>
<span class="tb-dot tb-dot--green"></span>
<span class="tb-title">freq_broadcast.sh</span>
</div>
<div class="terminal-body">
<p><span class="t-prompt">$</span> freq connect --studio</p>
<p class="t-output">Initializing signal uplink...</p>
<p class="t-output t-success">✓ Uplink established</p>
<p><span class="t-prompt">$</span> freq status</p>
<p class="t-output">Network: <span class="t-val">ONLINE</span></p>
<p class="t-output">Listeners: <span class="t-val">9,241,882</span></p>
<p class="t-output">Live now: <span class="t-val t-neon">3 shows</span></p>
<p class="t-cursor">█</p>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-brand">
<span class="brand-mark">◈</span>
<span>FREQ // Music & Podcast Network</span>
</div>
<p>© 2025 FREQ. All transmissions reserved.</p>
</footer>
<script type="module" src="script.js"></script>
</body>
</html>FREQ — Music & Podcast Platform
Neon cyberpunk music platform with magenta/cyan palette, Space Mono monospace typography, animated Canvas 2D waveform background, scrolling artist marquee, chart bars with glitch hover, and typing terminal CTA.
Source
- Repository:
libs-genclaude - Original demo id:
47-music-platform
Notes
Neon cyberpunk music platform with magenta/cyan palette, Space Mono monospace typography, animated Canvas 2D waveform background, scrolling artist marquee, chart bars with glitch hover, and typing terminal CTA.