Music — Hip-Hop / Club Landing
A loud, street-styled hip-hop and club-night landing built with vanilla HTML, CSS and JavaScript. A high-contrast hero pairs a chrome-metallic condensed title with an acid-lime accent, a CSS-drawn boombox and spinning cassette, a thumping equalizer and a looping track marquee. Below sit a latest-drop album showcase with a shared simulated player and working scrubber, per-track play and like toggles, a tour-dates list with sold-out states, a hover-reactive merch grid, a visualizer block and a sticky stream CTA.
MCP
Kod
:root {
/* palette override — hip-hop / club: black + chrome + acid lime */
--bg: #050505;
--bg-2: #0c0c0e;
--surface: #121214;
--surface-2: #1a1a1f;
--text: #ffffff;
--muted: #8e8e96;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--accent: #caff00; /* acid lime */
--accent-2: #c0c0c8; /* chrome */
--accent-3: #ff3d71;
--chrome: linear-gradient(180deg, #fff 0%, #d6d6dc 18%, #8b8b94 48%, #f2f2f5 60%, #6c6c74 82%, #c9c9d0 100%);
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--r-full: 999px;
--display: "Archivo Black", "Oswald", system-ui, sans-serif;
--cond: "Oswald", "Archivo Black", system-ui, sans-serif;
--body: "Inter", system-ui, -apple-system, sans-serif;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--body);
font-weight: 400;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }
/* ---------- buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
gap: 0.55rem;
font-family: var(--cond);
font-weight: 700;
font-size: 0.92rem;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 0.72rem 1.25rem;
border: 1px solid var(--line-2);
border-radius: var(--r-full);
background: var(--surface-2);
color: var(--text);
cursor: pointer;
transition: transform 0.14s ease, box-shadow 0.18s ease, background 0.18s ease, filter 0.18s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.btn--acid {
background: var(--accent);
color: #060600;
border-color: var(--accent);
box-shadow: 0 0 0 rgba(202, 255, 0, 0);
}
.btn--acid:hover { box-shadow: 0 10px 30px rgba(202, 255, 0, 0.30); filter: brightness(1.05); }
.btn--chrome {
background: var(--chrome);
color: #0a0a0c;
border: 1px solid rgba(255, 255, 255, 0.45);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
.btn--chrome:hover { box-shadow: 0 10px 30px rgba(192, 192, 200, 0.25); }
.btn--ghost { background: transparent; }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn__icon {
width: 0; height: 0;
border-style: solid;
border-width: 6px 0 6px 10px;
border-color: transparent transparent transparent currentColor;
}
.btn.is-playing .btn__icon {
width: 10px; height: 11px;
border: 0;
border-left: 3px solid currentColor;
border-right: 3px solid currentColor;
}
/* ---------- ticker ---------- */
.ticker {
background: var(--accent);
color: #060600;
overflow: hidden;
border-bottom: 2px solid #060600;
white-space: nowrap;
}
.ticker__track {
display: inline-flex;
gap: 1.6rem;
align-items: center;
padding: 0.4rem 0;
font-family: var(--cond);
font-weight: 700;
font-size: 0.78rem;
letter-spacing: 0.12em;
will-change: transform;
}
.ticker__track span:nth-child(even) { opacity: 0.6; }
/* ---------- nav ---------- */
.nav {
position: sticky;
top: 0;
z-index: 40;
display: flex;
align-items: center;
gap: 1.5rem;
padding: 0.9rem clamp(1rem, 4vw, 3rem);
background: rgba(5, 5, 5, 0.72);
backdrop-filter: blur(14px);
border-bottom: 1px solid var(--line);
}
.brand {
display: inline-flex;
align-items: center;
gap: 0.6rem;
font-family: var(--display);
font-size: 1.1rem;
letter-spacing: 0.02em;
}
.brand__mark {
width: 18px; height: 18px;
background: var(--accent);
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}
.nav__links {
display: flex;
gap: 1.4rem;
margin-left: auto;
font-family: var(--cond);
font-weight: 600;
font-size: 0.86rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.nav__links a { color: var(--muted); transition: color 0.15s ease; }
.nav__links a:hover { color: var(--accent); }
.nav__cta { padding: 0.5rem 1rem; }
/* ---------- hero ---------- */
.hero {
position: relative;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 2rem;
align-items: center;
padding: clamp(2.5rem, 7vw, 5.5rem) clamp(1rem, 4vw, 3rem) clamp(2rem, 5vw, 4rem);
background:
radial-gradient(900px 500px at 85% 10%, rgba(202, 255, 0, 0.08), transparent 60%),
radial-gradient(700px 500px at 5% 90%, rgba(192, 192, 200, 0.06), transparent 60%),
var(--bg);
overflow: hidden;
}
.eyebrow {
font-family: var(--cond);
font-weight: 600;
letter-spacing: 0.22em;
font-size: 0.78rem;
color: var(--accent);
margin: 0 0 0.8rem;
}
.hero__title {
font-family: var(--display);
font-size: clamp(3.6rem, 13vw, 9rem);
line-height: 0.82;
margin: 0 0 1.1rem;
letter-spacing: -0.01em;
background: var(--chrome);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 2px 0 rgba(0, 0, 0, 0.4);
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.6));
}
.hero__sub {
max-width: 38ch;
color: var(--muted);
font-size: 1.02rem;
margin: 0 0 1.6rem;
}
.hero__sub strong { color: var(--text); }
.hero__cta { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-bottom: 1.8rem; }
.hero__player {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.8rem 1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
max-width: 420px;
}
.hero__now { display: flex; flex-direction: column; line-height: 1.25; }
.hero__nowlabel { font-family: var(--cond); font-weight: 600; letter-spacing: 0.04em; }
.hero__nowtime { color: var(--muted); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
/* equalizer */
.eq { display: flex; align-items: flex-end; gap: 3px; height: 34px; }
.eq i {
width: 5px;
height: 30%;
background: var(--accent);
border-radius: 2px;
}
.eq.is-active i { animation: bump 0.6s ease-in-out infinite; }
.eq i:nth-child(1) { animation-delay: -0.1s; }
.eq i:nth-child(2) { animation-delay: -0.4s; }
.eq i:nth-child(3) { animation-delay: -0.7s; }
.eq i:nth-child(4) { animation-delay: -0.2s; }
.eq i:nth-child(5) { animation-delay: -0.55s; }
.eq i:nth-child(6) { animation-delay: -0.3s; }
.eq i:nth-child(7) { animation-delay: -0.65s; }
.eq i:nth-child(8) { animation-delay: -0.15s; }
.eq i:nth-child(9) { animation-delay: -0.45s; }
.eq i:nth-child(10) { animation-delay: -0.25s; }
.eq i:nth-child(11) { animation-delay: -0.6s; }
.eq i:nth-child(12) { animation-delay: -0.35s; }
@keyframes bump {
0%, 100% { height: 22%; }
50% { height: 100%; }
}
/* boombox art */
.hero__art { position: relative; display: grid; place-items: center; min-height: 320px; }
.boombox {
position: relative;
width: min(420px, 86%);
z-index: 2;
transition: transform 0.2s ease;
}
.boombox.is-active { animation: shake 0.5s ease-in-out infinite; }
@keyframes shake {
0%, 100% { transform: translateY(0) rotate(0); }
25% { transform: translateY(-3px) rotate(-0.4deg); }
75% { transform: translateY(2px) rotate(0.4deg); }
}
.boombox__handle {
width: 50%;
height: 26px;
margin: 0 auto -8px;
border: 8px solid #2a2a30;
border-bottom: none;
border-radius: 26px 26px 0 0;
background: transparent;
}
.boombox__body {
display: grid;
grid-template-columns: 1fr 1.15fr 1fr;
gap: 0.6rem;
padding: 1rem;
background: linear-gradient(160deg, #26262c, #131317);
border: 2px solid #3a3a42;
border-radius: var(--r-md);
box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.boombox__speaker {
position: relative;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, #3b3b44, #0c0c10 70%);
border: 3px solid #15151a;
display: grid;
place-items: center;
}
.boombox__speaker .ring {
position: absolute;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.08);
}
.boombox__speaker .ring:nth-child(1) { inset: 18%; }
.boombox__speaker .ring:nth-child(2) { inset: 34%; }
.boombox__speaker .ring:nth-child(3) { inset: 46%; background: var(--accent); border: none; }
.boombox.is-active .boombox__speaker .ring:nth-child(3) { animation: pulse 0.5s ease-in-out infinite; }
@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.35); } }
.boombox__deck { display: flex; flex-direction: column; gap: 0.6rem; }
.cassette {
display: flex;
justify-content: space-around;
align-items: center;
background: linear-gradient(180deg, #1c1c22, #0e0e12);
border: 1px solid #34343c;
border-radius: 6px;
padding: 0.7rem 0.5rem;
}
.cassette .reel {
width: 30px; height: 30px;
border-radius: 50%;
background: repeating-conic-gradient(var(--accent-2) 0 18deg, #1a1a1f 18deg 36deg);
border: 2px solid #46464e;
}
.boombox.is-active .cassette .reel { animation: spin 1.4s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.boombox__bars { display: flex; align-items: flex-end; gap: 4px; height: 26px; justify-content: center; }
.boombox__bars i { width: 6px; height: 30%; background: var(--accent); border-radius: 2px; }
.boombox.is-active .boombox__bars i { animation: bump 0.55s ease-in-out infinite; }
.boombox__bars i:nth-child(2){animation-delay:-0.2s}
.boombox__bars i:nth-child(3){animation-delay:-0.45s}
.boombox__bars i:nth-child(4){animation-delay:-0.1s}
.boombox__bars i:nth-child(5){animation-delay:-0.3s}
.boombox__bars i:nth-child(6){animation-delay:-0.5s}
.boombox__bars i:nth-child(7){animation-delay:-0.15s}
.boombox__sticker {
position: absolute;
bottom: -14px; right: -10px;
background: #fff;
color: #000;
font-family: var(--display);
font-size: 0.55rem;
letter-spacing: 0.04em;
text-align: center;
line-height: 1.1;
padding: 0.3rem 0.5rem;
border-radius: 3px;
transform: rotate(-6deg);
z-index: 3;
}
.blob { position: absolute; border-radius: 50%; filter: blur(60px); z-index: 1; }
.blob--a { width: 280px; height: 280px; background: rgba(202, 255, 0, 0.18); top: -40px; right: -30px; }
.blob--b { width: 240px; height: 240px; background: rgba(192, 192, 200, 0.12); bottom: -50px; left: -20px; }
/* ---------- section heads ---------- */
.section__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.6rem;
padding-bottom: 0.8rem;
border-bottom: 1px solid var(--line);
}
.section__title {
font-family: var(--display);
font-size: clamp(1.8rem, 5vw, 3rem);
margin: 0;
letter-spacing: -0.01em;
}
.section__title span {
background: var(--chrome);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.section__tag {
font-family: var(--cond);
font-size: 0.82rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
main > section { padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1rem, 4vw, 3rem); }
/* ---------- drop ---------- */
.drop { background: var(--bg-2); }
.drop__grid {
display: grid;
grid-template-columns: 320px 1fr;
gap: 2rem;
align-items: start;
}
.cover { position: relative; }
.cover__art {
position: relative;
aspect-ratio: 1;
border-radius: var(--r-lg);
overflow: hidden;
background: linear-gradient(135deg, #0a0a0c 0%, #1a1a20 100%);
border: 1px solid var(--line);
display: grid;
place-content: center;
text-align: center;
}
.cover__shape {
position: absolute;
width: 150%; height: 60px;
background: var(--accent);
top: 30%; left: -25%;
transform: rotate(-18deg);
opacity: 0.92;
}
.cover__shape--2 { background: var(--accent-2); top: 58%; height: 26px; transform: rotate(-18deg); opacity: 0.5; }
.cover__title {
position: relative;
font-family: var(--display);
font-size: 1.7rem;
line-height: 0.95;
color: #060600;
mix-blend-mode: difference;
z-index: 2;
}
.cover__artist {
position: relative;
font-family: var(--cond);
letter-spacing: 0.3em;
font-size: 0.72rem;
color: var(--text);
margin-top: 0.5rem;
z-index: 2;
}
.cover__play {
position: absolute;
bottom: 14px; right: 14px;
width: 56px; height: 56px;
border-radius: 50%;
border: none;
background: var(--accent);
display: grid;
place-items: center;
cursor: pointer;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.5);
transition: transform 0.15s ease, box-shadow 0.18s ease;
}
.cover__play:hover { transform: scale(1.07); box-shadow: 0 12px 30px rgba(202,255,0,0.35); }
.cover__play:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.cover__playicon {
width: 0; height: 0;
border-style: solid;
border-width: 9px 0 9px 15px;
border-color: transparent transparent transparent #060600;
margin-left: 3px;
}
.cover__play[aria-pressed="true"] .cover__playicon {
width: 14px; height: 16px; border: 0; margin: 0;
border-left: 4px solid #060600;
border-right: 4px solid #060600;
}
/* tracks */
.tracks { list-style: none; margin: 0; padding: 0; }
.track {
display: grid;
grid-template-columns: 40px 28px 1fr auto 32px 52px;
align-items: center;
gap: 0.8rem;
padding: 0.55rem 0.7rem;
border-radius: var(--r-sm);
border-bottom: 1px solid var(--line);
transition: background 0.14s ease;
}
.track:hover { background: var(--surface); }
.track.is-playing { background: rgba(202, 255, 0, 0.08); }
.track__play {
width: 30px; height: 30px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--surface-2);
cursor: pointer;
display: grid;
place-items: center;
color: var(--text);
transition: background 0.14s ease, transform 0.12s ease;
}
.track__play:hover { background: var(--accent); transform: scale(1.05); }
.track__play:hover span { border-color: transparent transparent transparent #060600; }
.track__play:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.track__play span {
width: 0; height: 0;
border-style: solid;
border-width: 5px 0 5px 8px;
border-color: transparent transparent transparent currentColor;
margin-left: 2px;
}
.track__play[aria-pressed="true"] span {
width: 8px; height: 9px; border: 0; margin: 0;
border-left: 2px solid currentColor;
border-right: 2px solid currentColor;
}
.track.is-playing .track__play { background: var(--accent); color: #060600; }
.track__no { font-family: var(--cond); color: var(--muted); font-variant-numeric: tabular-nums; }
.track__name { font-weight: 600; }
.track__name em { color: var(--muted); font-style: normal; font-weight: 400; font-size: 0.9em; }
.track.is-playing .track__name { color: var(--accent); }
.track__plays { color: var(--muted); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.track__len { color: var(--muted); font-size: 0.85rem; text-align: right; font-variant-numeric: tabular-nums; }
.track__like {
width: 28px; height: 28px;
border: none;
background: transparent;
cursor: pointer;
position: relative;
border-radius: 50%;
}
.track__like::before {
content: "";
position: absolute;
inset: 0;
margin: auto;
width: 16px; height: 15px;
background: var(--muted);
-webkit-mask: var(--heart) center / contain no-repeat;
mask: var(--heart) center / contain no-repeat;
transition: background 0.15s ease, transform 0.15s ease;
}
.track__like:hover::before { background: var(--accent-3); transform: scale(1.12); }
.track__like[aria-pressed="true"]::before { background: var(--accent-3); }
.track__like:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
:root { --heart: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 22'%3E%3Cpath d='M12 21C5 16 1 12 1 7.5 1 4.4 3.4 2 6.5 2 8.4 2 10.2 3 12 5c1.8-2 3.6-3 5.5-3C20.6 2 23 4.4 23 7.5 23 12 19 16 12 21Z'/%3E%3C/svg%3E"); }
/* scrubber */
.scrub {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1.8rem;
padding: 0.9rem 1.1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
}
.scrub__time { font-family: var(--cond); font-size: 0.82rem; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 36px; }
.scrub__bar {
position: relative;
flex: 1;
height: 8px;
border-radius: var(--r-full);
background: var(--surface-2);
cursor: pointer;
}
.scrub__bar:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.scrub__fill {
position: absolute;
top: 0; left: 0; bottom: 0;
width: 0%;
border-radius: var(--r-full);
background: linear-gradient(90deg, var(--accent-2), var(--accent));
}
.scrub__knob {
position: absolute;
top: 50%;
left: 0%;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--accent);
transform: translate(-50%, -50%);
box-shadow: 0 0 0 4px rgba(202, 255, 0, 0.18);
}
/* ---------- tour ---------- */
.dates { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.date {
display: grid;
grid-template-columns: 70px 1fr 1fr auto;
align-items: center;
gap: 1.2rem;
padding: 1.05rem 0.6rem;
border-bottom: 1px solid var(--line);
transition: background 0.16s ease, padding-left 0.16s ease;
}
.date:hover { background: var(--surface); padding-left: 1.2rem; }
.date__day {
font-family: var(--display);
font-size: 1.6rem;
line-height: 1;
display: flex;
flex-direction: column;
}
.date__day b { font-family: var(--cond); font-size: 0.72rem; letter-spacing: 0.12em; color: var(--accent); font-weight: 600; }
.date__city { font-family: var(--cond); font-weight: 600; font-size: 1.05rem; letter-spacing: 0.03em; }
.date__venue { color: var(--muted); font-size: 0.92rem; }
.date--soldout { opacity: 0.6; }
.date--soldout .date__city { text-decoration: line-through; text-decoration-color: var(--accent-3); }
.date__btn { justify-self: end; }
/* ---------- merch ---------- */
.merch { background: var(--bg-2); }
.merch__grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.2rem;
}
.prod {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 0.9rem;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.2s ease;
}
.prod:hover {
transform: translateY(-6px);
border-color: var(--accent);
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
}
.prod__img {
aspect-ratio: 1;
border-radius: var(--r-sm);
display: grid;
place-items: center;
margin-bottom: 0.8rem;
overflow: hidden;
position: relative;
border: 1px solid var(--line);
}
.prod__glyph {
font-family: var(--display);
font-size: 2.4rem;
color: rgba(255, 255, 255, 0.85);
z-index: 2;
}
.prod__img--tee { background: linear-gradient(135deg, #1a1a20, #0c0c10); }
.prod__img--tee::after { content:""; position:absolute; inset:auto -20% -30% -20%; height:60%; background: var(--accent); transform: rotate(-10deg); opacity:0.85; }
.prod__img--hoodie { background: linear-gradient(135deg, #22222a, #101015); }
.prod__img--hoodie::after { content:""; position:absolute; inset:0; background: radial-gradient(circle at 70% 20%, rgba(192,192,200,0.25), transparent 55%); }
.prod__img--cap { background: linear-gradient(135deg, #14141a, #060608); }
.prod__img--cap::after { content:""; position:absolute; left:0; right:0; bottom:30%; height:14px; background: var(--accent); }
.prod__img--vinyl { background: repeating-radial-gradient(circle at 50% 50%, #18181d 0 4px, #0b0b0e 4px 8px); }
.prod__img--vinyl::after { content:""; position:absolute; inset:38%; border-radius:50%; background: var(--accent); }
.prod__img .prod__glyph { position: relative; }
.prod__row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.7rem; }
.prod__name { font-family: var(--cond); font-weight: 600; font-size: 0.96rem; }
.prod__price { font-family: var(--cond); font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.prod__btn { width: 100%; justify-content: center; }
.prod--soldout { opacity: 0.6; }
/* ---------- visualizer ---------- */
.visual__inner {
display: grid;
grid-template-columns: 0.9fr 1.1fr;
gap: 2rem;
align-items: center;
}
.visual__copy p { color: var(--muted); max-width: 40ch; }
.visual__copy strong { color: var(--text); }
.visual__screen {
position: relative;
aspect-ratio: 16 / 9;
border-radius: var(--r-lg);
border: 1px solid var(--line-2);
background:
radial-gradient(circle at 50% 50%, rgba(202, 255, 0, 0.08), transparent 60%),
linear-gradient(180deg, #0a0a0c, #131318);
overflow: hidden;
display: grid;
place-items: center;
}
.visual__wave {
width: 86%;
height: 60%;
background-image: repeating-linear-gradient(90deg, var(--accent) 0 3px, transparent 3px 9px);
-webkit-mask: var(--wavemask);
mask: var(--wavemask);
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
opacity: 0.85;
}
:root { --wavemask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none' viewBox='0 0 100 40'%3E%3Cpath d='M0 20 Q10 0 20 20 T40 20 T60 20 T80 20 T100 20' fill='none' stroke='black' stroke-width='40'/%3E%3C/svg%3E"); }
.visual__screen.is-playing .visual__wave { animation: wavemove 1.1s linear infinite; }
@keyframes wavemove { to { background-position-x: 18px; } }
.visual__time {
position: absolute;
bottom: 12px; right: 14px;
font-family: var(--cond);
font-size: 0.82rem;
color: var(--text);
background: rgba(0, 0, 0, 0.5);
padding: 0.15rem 0.5rem;
border-radius: var(--r-sm);
font-variant-numeric: tabular-nums;
}
/* ---------- footer ---------- */
.foot {
padding: 3rem clamp(1rem, 4vw, 3rem);
border-top: 1px solid var(--line);
display: flex;
align-items: center;
gap: 1.5rem;
flex-wrap: wrap;
}
.foot__brand { font-family: var(--display); font-size: 1.6rem; }
.foot__social { display: flex; gap: 0.6rem; margin-left: auto; }
.foot__social a {
width: 40px; height: 40px;
display: grid;
place-items: center;
border-radius: 50%;
border: 1px solid var(--line-2);
font-family: var(--cond);
font-weight: 700;
font-size: 0.82rem;
transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.foot__social a:hover { background: var(--accent); color: #060600; transform: translateY(-3px); }
.foot__legal { width: 100%; color: var(--muted); font-size: 0.8rem; margin: 0; }
/* ---------- sticky CTA ---------- */
.sticky {
position: fixed;
left: 50%;
bottom: 18px;
transform: translate(-50%, 140%);
display: flex;
align-items: center;
gap: 1rem;
padding: 0.6rem 0.6rem 0.6rem 1.2rem;
background: var(--surface-2);
border: 1px solid var(--line-2);
border-radius: var(--r-full);
box-shadow: var(--shadow);
z-index: 50;
transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
font-family: var(--cond);
font-weight: 600;
letter-spacing: 0.03em;
font-size: 0.9rem;
max-width: calc(100vw - 24px);
}
.sticky.is-visible { transform: translate(-50%, 0); }
/* ---------- toast ---------- */
.toast {
position: fixed;
bottom: 90px;
left: 50%;
transform: translate(-50%, 20px);
background: var(--accent);
color: #060600;
font-family: var(--cond);
font-weight: 700;
letter-spacing: 0.04em;
padding: 0.65rem 1.1rem;
border-radius: var(--r-full);
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 60;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }
/* ---------- responsive ---------- */
@media (max-width: 880px) {
.hero { grid-template-columns: 1fr; }
.hero__art { order: -1; min-height: 260px; }
.drop__grid { grid-template-columns: 1fr; }
.cover { max-width: 320px; }
.visual__inner { grid-template-columns: 1fr; }
.merch__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.nav__links { display: none; }
.hero__title { font-size: clamp(3rem, 22vw, 5rem); }
.hero__player { max-width: 100%; }
.section__head { flex-direction: column; align-items: flex-start; gap: 0.3rem; }
.track {
grid-template-columns: 32px 1fr auto 28px 48px;
gap: 0.5rem;
}
.track__no { display: none; }
.track__plays { display: none; }
.date {
grid-template-columns: 56px 1fr;
grid-template-areas:
"day city"
"day venue"
"btn btn";
row-gap: 0.3rem;
}
.date__day { grid-area: day; }
.date__city { grid-area: city; }
.date__venue { grid-area: venue; }
.date__btn { grid-area: btn; justify-self: stretch; margin-top: 0.4rem; }
.date__btn { width: 100%; justify-content: center; }
.foot { flex-direction: column; align-items: flex-start; }
.foot__social { margin-left: 0; }
.sticky span { display: none; }
.sticky { padding: 0.5rem; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}(function () {
"use strict";
/* ---------- helpers ---------- */
var prefersReduced =
window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var $ = function (sel, ctx) {
return (ctx || document).querySelector(sel);
};
var $$ = function (sel, ctx) {
return Array.prototype.slice.call((ctx || document).querySelectorAll(sel));
};
function fmt(sec) {
sec = Math.max(0, Math.floor(sec));
var m = Math.floor(sec / 60);
var s = sec % 60;
return m + ":" + (s < 10 ? "0" : "") + s;
}
function parseLen(str) {
var p = String(str).split(":");
return parseInt(p[0], 10) * 60 + parseInt(p[1], 10);
}
/* ---------- toast ---------- */
var toastEl = $("#toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2200);
}
/* ============================================================
SIMULATED PLAYER
One shared transport drives: hero button, cover button,
per-track buttons, equalizer, boombox, scrubber.
============================================================ */
var tracks = $$(".track");
var trackData = tracks.map(function (li) {
return {
el: li,
name: li.getAttribute("data-name"),
len: parseLen(li.getAttribute("data-len")),
playBtn: $(".track__play", li)
};
});
var player = {
index: 0,
pos: 0,
playing: false,
timer: null
};
// shared UI refs
var eq = $("#eq");
var boombox = $("#boombox");
var heroBtn = $("#playHero");
var heroLabel = $("#playLabel");
var coverBtn = $("#coverPlay");
var nowTrack = $("#nowTrack");
var curTime = $("#curTime");
var scrubFill = $("#scrubFill");
var scrubKnob = $("#scrubKnob");
var scrubBar = $("#scrubBar");
var scrubCur = $("#scrubCur");
var scrubTotal = $("#scrubTotal");
function currentLen() {
return trackData[player.index].len;
}
function renderTransport() {
var t = trackData[player.index];
var len = t.len;
var ratio = len ? player.pos / len : 0;
var pct = Math.min(100, ratio * 100);
if (scrubFill) scrubFill.style.width = pct + "%";
if (scrubKnob) scrubKnob.style.left = pct + "%";
if (scrubBar) scrubBar.setAttribute("aria-valuenow", Math.round(pct));
if (scrubCur) scrubCur.textContent = fmt(player.pos);
if (scrubTotal) scrubTotal.textContent = fmt(len);
if (curTime) curTime.textContent = fmt(player.pos);
if (nowTrack) nowTrack.textContent = t.name.replace("—", "—");
}
function setPlayingVisuals(on) {
if (eq) eq.classList.toggle("is-active", on);
if (boombox) boombox.classList.toggle("is-active", on);
if (heroBtn) {
heroBtn.setAttribute("aria-pressed", String(on));
heroBtn.classList.toggle("is-playing", on);
}
if (heroLabel) heroLabel.textContent = on ? "Pause" : "Play album";
if (coverBtn) {
coverBtn.setAttribute("aria-pressed", String(on));
}
trackData.forEach(function (t, i) {
var active = on && i === player.index;
t.el.classList.toggle("is-playing", active);
t.playBtn.setAttribute("aria-pressed", String(active));
});
}
function tick() {
player.pos += 1;
if (player.pos >= currentLen()) {
// advance to next track, loop at the end
player.pos = 0;
player.index = (player.index + 1) % trackData.length;
renderTransport();
toast("Up next — " + trackData[player.index].name);
}
renderTransport();
}
function play() {
if (player.playing) return;
player.playing = true;
setPlayingVisuals(true);
clearInterval(player.timer);
player.timer = setInterval(tick, 1000);
renderTransport();
}
function pause() {
player.playing = false;
clearInterval(player.timer);
setPlayingVisuals(false);
}
function toggle() {
player.playing ? pause() : play();
}
function selectTrack(i, autoplay) {
player.index = i;
player.pos = 0;
renderTransport();
if (autoplay) {
pause();
play();
}
}
// wire main buttons
if (heroBtn) heroBtn.addEventListener("click", toggle);
if (coverBtn) coverBtn.addEventListener("click", toggle);
// per-track play buttons
trackData.forEach(function (t, i) {
t.playBtn.addEventListener("click", function () {
if (player.playing && player.index === i) {
pause();
} else {
selectTrack(i, true);
}
});
});
// like toggles
$$(".track__like").forEach(function (btn) {
btn.addEventListener("click", function () {
var on = btn.getAttribute("aria-pressed") === "true";
btn.setAttribute("aria-pressed", String(!on));
var name = btn
.getAttribute("aria-label")
.replace("Like ", "");
toast(on ? "Removed " + name : "Added " + name + " to your likes");
});
});
/* ---------- scrubber (click, drag, keyboard) ---------- */
function seekFromEvent(clientX) {
if (!scrubBar) return;
var rect = scrubBar.getBoundingClientRect();
var ratio = (clientX - rect.left) / rect.width;
ratio = Math.min(1, Math.max(0, ratio));
player.pos = Math.round(ratio * currentLen());
renderTransport();
}
if (scrubBar) {
var dragging = false;
scrubBar.addEventListener("pointerdown", function (e) {
dragging = true;
scrubBar.setPointerCapture(e.pointerId);
seekFromEvent(e.clientX);
});
scrubBar.addEventListener("pointermove", function (e) {
if (dragging) seekFromEvent(e.clientX);
});
scrubBar.addEventListener("pointerup", function (e) {
dragging = false;
try { scrubBar.releasePointerCapture(e.pointerId); } catch (err) {}
});
scrubBar.addEventListener("keydown", function (e) {
var step = 0;
if (e.key === "ArrowRight" || e.key === "ArrowUp") step = 5;
else if (e.key === "ArrowLeft" || e.key === "ArrowDown") step = -5;
else if (e.key === "Home") { player.pos = 0; renderTransport(); e.preventDefault(); return; }
else if (e.key === "End") { player.pos = currentLen(); renderTransport(); e.preventDefault(); return; }
else return;
player.pos = Math.min(currentLen(), Math.max(0, player.pos + step));
renderTransport();
e.preventDefault();
});
}
// init transport display
renderTransport();
/* ============================================================
EQUALIZER "bump" — randomize bar heights when active so it
reads as reacting to the beat (on top of CSS keyframes).
============================================================ */
if (eq && !prefersReduced) {
var eqBars = $$("i", eq);
setInterval(function () {
if (!eq.classList.contains("is-active")) return;
eqBars.forEach(function (bar) {
bar.style.animationDuration = (0.35 + Math.random() * 0.4).toFixed(2) + "s";
});
}, 700);
}
/* ============================================================
MARQUEE TICKER — JS-driven for a continuous loop. We clone
the content so it scrolls seamlessly.
============================================================ */
var ticker = $("#ticker");
if (ticker && !prefersReduced) {
ticker.innerHTML += ticker.innerHTML; // duplicate for seamless loop
var tx = 0;
var halfWidth = ticker.scrollWidth / 2;
function marquee() {
tx -= 0.6;
if (Math.abs(tx) >= halfWidth) tx = 0;
ticker.style.transform = "translateX(" + tx + "px)";
requestAnimationFrame(marquee);
}
requestAnimationFrame(marquee);
}
/* ============================================================
TOUR DATES — tickets / sold out feedback
============================================================ */
$$(".date").forEach(function (row) {
var btn = $(".date__btn", row);
if (!btn || btn.disabled) return;
btn.addEventListener("click", function () {
var city = $(".date__city", row).textContent.trim();
var venue = $(".date__venue", row).textContent.trim();
toast("Tickets — " + city + " @ " + venue);
});
});
/* ============================================================
MERCH — add to cart
============================================================ */
var cart = 0;
$$(".prod").forEach(function (card) {
var btn = $(".prod__btn", card);
if (!btn || btn.disabled) return;
btn.addEventListener("click", function () {
cart += 1;
var name = $(".prod__name", card).textContent.trim();
toast(name + " added — cart (" + cart + ")");
});
});
/* ============================================================
VISUALIZER — simulated video playback
============================================================ */
var vBtn = $("#visualPlay");
var vLabel = $("#visualLabel");
var vScreen = $("#visualScreen");
var vTime = $("#visualTime");
var vState = { playing: false, pos: 0, len: parseLen("3:21"), timer: null };
function vRender() {
if (vTime) vTime.textContent = fmt(vState.pos) + " / 3:21";
}
function vTick() {
vState.pos += 1;
if (vState.pos >= vState.len) {
vStop();
toast("Video ended");
return;
}
vRender();
}
function vPlay() {
vState.playing = true;
if (vScreen) vScreen.classList.add("is-playing");
if (vBtn) { vBtn.setAttribute("aria-pressed", "true"); vBtn.classList.add("is-playing"); }
if (vLabel) vLabel.textContent = "Pause";
clearInterval(vState.timer);
vState.timer = setInterval(vTick, 1000);
}
function vStop() {
vState.playing = false;
clearInterval(vState.timer);
if (vScreen) vScreen.classList.remove("is-playing");
if (vBtn) { vBtn.setAttribute("aria-pressed", "false"); vBtn.classList.remove("is-playing"); }
if (vLabel) vLabel.textContent = "Watch video";
}
if (vBtn) {
vBtn.addEventListener("click", function () {
vState.playing ? vStop() : vPlay();
});
}
vRender();
/* ============================================================
SOCIAL links → toast (no real navigation)
============================================================ */
$$("[data-toast]").forEach(function (a) {
a.addEventListener("click", function (e) {
e.preventDefault();
toast(a.getAttribute("data-toast"));
});
});
/* ============================================================
STICKY CTA — reveal after scrolling past the hero, hide
near the footer.
============================================================ */
var sticky = $("#sticky");
var hero = $(".hero");
var foot = $(".foot");
if (sticky) {
function onScroll() {
var y = window.scrollY || window.pageYOffset;
var heroBottom = hero ? hero.offsetTop + hero.offsetHeight : 400;
var nearFooter = foot
? y + window.innerHeight > foot.offsetTop + 80
: false;
var show = y > heroBottom && !nearFooter;
sticky.classList.toggle("is-visible", show);
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>BLOK CITY — Hip-Hop / Club Landing</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=Archivo+Black&family=Oswald:wght@500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- ===== Marquee ticker ===== -->
<div class="ticker" aria-hidden="true">
<div class="ticker__track" id="ticker">
<span>NEW ALBUM — CONCRETE GOSPEL — OUT NOW</span>
<span>•</span>
<span>BLOK CITY TOUR 2026</span>
<span>•</span>
<span>MIDNIGHT PRESSURE FT. VELVET STATIC</span>
<span>•</span>
<span>ACID RAIN</span>
<span>•</span>
<span>PAPER LANTERNS</span>
<span>•</span>
<span>CHROME DREAMS</span>
<span>•</span>
</div>
</div>
<!-- ===== Nav ===== -->
<header class="nav">
<a class="brand" href="#top">
<span class="brand__mark" aria-hidden="true"></span>
BLOK CITY
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#drop">Album</a>
<a href="#tour">Tour</a>
<a href="#merch">Merch</a>
<a href="#visual">Visualizer</a>
</nav>
<a class="btn btn--ghost nav__cta" href="#tour">Tickets</a>
</header>
<main id="top">
<!-- ===== Hero ===== -->
<section class="hero">
<div class="hero__copy">
<p class="eyebrow">DEF·ROW RECORDS — 2026</p>
<h1 class="hero__title">BLOK<br />CITY</h1>
<p class="hero__sub">New album <strong>“Concrete Gospel”</strong> out now. Twelve cuts of chrome-plated boom-bap and basement club heat.</p>
<div class="hero__cta">
<button class="btn btn--acid" id="playHero" aria-pressed="false">
<span class="btn__icon" id="playIcon" aria-hidden="true"></span>
<span id="playLabel">Play album</span>
</button>
<a class="btn btn--chrome" href="#tour">Tour dates</a>
</div>
<div class="hero__player" role="group" aria-label="Now playing">
<div class="eq" id="eq" aria-hidden="true">
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
</div>
<div class="hero__now">
<span class="hero__nowlabel" id="nowTrack">01 — Acid Rain</span>
<span class="hero__nowtime"><span id="curTime">0:00</span> / 3:48</span>
</div>
</div>
</div>
<!-- CSS-drawn boombox -->
<div class="hero__art" aria-hidden="true">
<div class="boombox" id="boombox">
<div class="boombox__handle"></div>
<div class="boombox__body">
<div class="boombox__speaker">
<span class="ring"></span><span class="ring"></span><span class="ring"></span>
</div>
<div class="boombox__deck">
<div class="cassette">
<span class="reel"></span>
<span class="reel"></span>
</div>
<div class="boombox__bars">
<i></i><i></i><i></i><i></i><i></i><i></i><i></i>
</div>
</div>
<div class="boombox__speaker">
<span class="ring"></span><span class="ring"></span><span class="ring"></span>
</div>
</div>
<div class="boombox__sticker">PARENTAL<br />ADVISORY</div>
</div>
<div class="blob blob--a"></div>
<div class="blob blob--b"></div>
</div>
</section>
<!-- ===== Latest drop ===== -->
<section class="drop" id="drop">
<div class="section__head">
<h2 class="section__title">LATEST <span>DROP</span></h2>
<span class="section__tag">12 tracks · 41 min</span>
</div>
<div class="drop__grid">
<!-- Cover -->
<div class="cover" id="cover">
<div class="cover__art">
<div class="cover__shape"></div>
<div class="cover__shape cover__shape--2"></div>
<span class="cover__title">CONCRETE<br />GOSPEL</span>
<span class="cover__artist">BLOK CITY</span>
</div>
<button class="cover__play" id="coverPlay" aria-label="Play Concrete Gospel" aria-pressed="false">
<span class="cover__playicon"></span>
</button>
</div>
<!-- Tracklist -->
<ol class="tracks" id="tracks" aria-label="Tracklist">
<li class="track" data-len="3:48" data-name="01 — Acid Rain">
<button class="track__play" aria-label="Play Acid Rain" aria-pressed="false"><span></span></button>
<span class="track__no">01</span>
<span class="track__name">Acid Rain</span>
<span class="track__plays">2.1M</span>
<button class="track__like" aria-label="Like Acid Rain" aria-pressed="false"></button>
<span class="track__len">3:48</span>
</li>
<li class="track" data-len="3:12" data-name="02 — Chrome Dreams">
<button class="track__play" aria-label="Play Chrome Dreams" aria-pressed="false"><span></span></button>
<span class="track__no">02</span>
<span class="track__name">Chrome Dreams</span>
<span class="track__plays">1.7M</span>
<button class="track__like" aria-label="Like Chrome Dreams" aria-pressed="false"></button>
<span class="track__len">3:12</span>
</li>
<li class="track" data-len="4:02" data-name="03 — Midnight Pressure">
<button class="track__play" aria-label="Play Midnight Pressure" aria-pressed="false"><span></span></button>
<span class="track__no">03</span>
<span class="track__name">Midnight Pressure <em>ft. Velvet Static</em></span>
<span class="track__plays">3.4M</span>
<button class="track__like" aria-label="Like Midnight Pressure" aria-pressed="false"></button>
<span class="track__len">4:02</span>
</li>
<li class="track" data-len="2:55" data-name="04 — Paper Lanterns">
<button class="track__play" aria-label="Play Paper Lanterns" aria-pressed="false"><span></span></button>
<span class="track__no">04</span>
<span class="track__name">Paper Lanterns</span>
<span class="track__plays">988K</span>
<button class="track__like" aria-label="Like Paper Lanterns" aria-pressed="false"></button>
<span class="track__len">2:55</span>
</li>
<li class="track" data-len="3:33" data-name="05 — Block Heat">
<button class="track__play" aria-label="Play Block Heat" aria-pressed="false"><span></span></button>
<span class="track__no">05</span>
<span class="track__name">Block Heat</span>
<span class="track__plays">1.2M</span>
<button class="track__like" aria-label="Like Block Heat" aria-pressed="false"></button>
<span class="track__len">3:33</span>
</li>
<li class="track" data-len="3:21" data-name="06 — Neon Tides">
<button class="track__play" aria-label="Play Neon Tides" aria-pressed="false"><span></span></button>
<span class="track__no">06</span>
<span class="track__name">Neon Tides</span>
<span class="track__plays">760K</span>
<button class="track__like" aria-label="Like Neon Tides" aria-pressed="false"></button>
<span class="track__len">3:21</span>
</li>
</ol>
</div>
<!-- Scrubber -->
<div class="scrub">
<span class="scrub__time" id="scrubCur">0:00</span>
<div class="scrub__bar" id="scrubBar" role="slider" tabindex="0" aria-label="Seek track" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="scrub__fill" id="scrubFill"></div>
<div class="scrub__knob" id="scrubKnob"></div>
</div>
<span class="scrub__time" id="scrubTotal">3:48</span>
</div>
</section>
<!-- ===== Tour ===== -->
<section class="tour" id="tour">
<div class="section__head">
<h2 class="section__title">TOUR <span>2026</span></h2>
<span class="section__tag">North America · Europe</span>
</div>
<ul class="dates" id="dates">
<li class="date">
<span class="date__day"><b>MAY</b>04</span>
<span class="date__city">Brooklyn, NY</span>
<span class="date__venue">The Concrete Room</span>
<button class="btn btn--acid date__btn">Tickets</button>
</li>
<li class="date">
<span class="date__day"><b>MAY</b>09</span>
<span class="date__city">Atlanta, GA</span>
<span class="date__venue">Static Hall</span>
<button class="btn btn--acid date__btn">Tickets</button>
</li>
<li class="date date--soldout">
<span class="date__day"><b>MAY</b>16</span>
<span class="date__city">Chicago, IL</span>
<span class="date__venue">The Reservoir</span>
<button class="btn date__btn" disabled>Sold out</button>
</li>
<li class="date">
<span class="date__day"><b>JUN</b>02</span>
<span class="date__city">Los Angeles, CA</span>
<span class="date__venue">Chrome Yard</span>
<button class="btn btn--acid date__btn">Tickets</button>
</li>
<li class="date date--soldout">
<span class="date__day"><b>JUN</b>21</span>
<span class="date__city">London, UK</span>
<span class="date__venue">Velvet Basement</span>
<button class="btn date__btn" disabled>Sold out</button>
</li>
<li class="date">
<span class="date__day"><b>JUL</b>05</span>
<span class="date__city">Berlin, DE</span>
<span class="date__venue">Acid Bunker</span>
<button class="btn btn--acid date__btn">Tickets</button>
</li>
</ul>
</section>
<!-- ===== Merch ===== -->
<section class="merch" id="merch">
<div class="section__head">
<h2 class="section__title">MERCH <span>BLOCK</span></h2>
<span class="section__tag">Limited run</span>
</div>
<div class="merch__grid" id="merchGrid">
<article class="prod">
<div class="prod__img prod__img--tee"><span class="prod__glyph">BC</span></div>
<div class="prod__row"><span class="prod__name">Acid Logo Tee</span><span class="prod__price">$38</span></div>
<button class="btn btn--acid prod__btn">Add to cart</button>
</article>
<article class="prod">
<div class="prod__img prod__img--hoodie"><span class="prod__glyph">CG</span></div>
<div class="prod__row"><span class="prod__name">Gospel Hoodie</span><span class="prod__price">$74</span></div>
<button class="btn btn--acid prod__btn">Add to cart</button>
</article>
<article class="prod">
<div class="prod__img prod__img--cap"><span class="prod__glyph">BC</span></div>
<div class="prod__row"><span class="prod__name">Chrome Snapback</span><span class="prod__price">$32</span></div>
<button class="btn btn--acid prod__btn">Add to cart</button>
</article>
<article class="prod prod--soldout">
<div class="prod__img prod__img--vinyl"><span class="prod__glyph">LP</span></div>
<div class="prod__row"><span class="prod__name">Concrete Gospel LP</span><span class="prod__price">$29</span></div>
<button class="btn prod__btn" disabled>Sold out</button>
</article>
</div>
</section>
<!-- ===== Visualizer ===== -->
<section class="visual" id="visual">
<div class="visual__inner">
<div class="visual__copy">
<h2 class="section__title">VISUAL<span>IZER</span></h2>
<p>The official video for <strong>“Midnight Pressure”</strong>. Strobe-lit, chrome-soaked, shot in a basement that doesn’t exist.</p>
<button class="btn btn--chrome" id="visualPlay" aria-pressed="false">
<span class="btn__icon" id="visualIcon" aria-hidden="true"></span>
<span id="visualLabel">Watch video</span>
</button>
</div>
<div class="visual__screen" id="visualScreen" aria-hidden="true">
<div class="visual__wave" id="wave"></div>
<span class="visual__time" id="visualTime">3:21</span>
</div>
</div>
</section>
</main>
<!-- ===== Footer ===== -->
<footer class="foot">
<div class="foot__brand">BLOK CITY</div>
<nav class="foot__social" aria-label="Social">
<a href="#" data-toast="Opens Instagram">IG</a>
<a href="#" data-toast="Opens X">X</a>
<a href="#" data-toast="Opens TikTok">TT</a>
<a href="#" data-toast="Opens YouTube">YT</a>
</nav>
<p class="foot__legal">© 2026 Def·Row Records. Fictional. All rights reserved.</p>
</footer>
<!-- Sticky CTA -->
<div class="sticky" id="sticky">
<span>Concrete Gospel — out now</span>
<a class="btn btn--acid" href="#drop">Listen</a>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Hip-Hop / Club Landing
A bold, high-contrast landing page for BLOK CITY, a fictional hip-hop artist on Def·Row Records promoting the new album Concrete Gospel. The palette is pure street: a near-black stage, a metallic chrome gradient on the condensed Archivo Black headings, and an acid-lime accent that snaps off the dark. The hero pairs a chrome-plated title and “Play album” / “Tour dates” CTAs with a fully CSS-drawn boombox — twin speakers, a spinning cassette and a built-in equalizer that shake and pulse when playback starts. A JS-driven marquee of track names loops seamlessly across the top.
A single simulated transport drives every play control on the page: the hero button, the album cover, and each row in the tracklist all share one timer-based player, so hitting play anywhere lights up the equalizer, rattles the boombox and advances the now-playing readout. The latest-drop showcase shows a CSS-drawn cover and a six-track list with per-track play and heart toggles, play counts and durations, plus a draggable, clickable, keyboard-operable scrubber (role="slider") that seeks the current track. Below, a tour-dates list reveals on hover and fires “Tickets” feedback (with line-through sold-out states), a merch grid lifts and accents on hover with an add-to-cart counter, and a visualizer block plays a strobing acid waveform with its own simulated clock.
Everything is vanilla JS with no frameworks, build step or audio files — playback, the marquee, the visualizer and the equalizer “bump” are all driven by timers, requestAnimationFrame and CSS transforms, and a small toast() helper surfaces feedback. A sticky stream CTA slides in past the hero and hides near the footer. The layout is responsive down to ~360px and respects prefers-reduced-motion.
Illustrative UI only — fictional artists, albums, tracks, and data. No real audio playback.