페이지 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.
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/[email protected]","gsap/ScrollTrigger":"https://esm.sh/[email protected]/ScrollTrigger","gsap/SplitText":"https://esm.sh/[email protected]/SplitText","gsap/Flip":"https://esm.sh/[email protected]/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/[email protected]/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/[email protected]/TextPlugin","gsap/all":"https://esm.sh/[email protected]/all","gsap/":"https://esm.sh/[email protected]/","lenis":"https://esm.sh/[email protected]/dist/lenis.mjs","three":"https://esm.sh/[email protected]","three/addons/":"https://esm.sh/[email protected]/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.