类型:Site模板 · 收录:2026-09-23 19:21:50 · 源码:会员可见
Build a single, self-contained, production-quality HTML file for a dark, editorial
electronic-music artist landing page ("DJ Zak Derdan"). Output ONE file: inline
<style> and inline <script>, no build step, no frameworks, no external JS/CSS
libraries. Vanilla HTML + CSS + JS only. Must be fully mobile responsive.
════════════════════════════════════════════════════════════════════
1. REMOTE ASSETS (use these exact URLs — do not substitute placeholders)
════════════════════════════════════════════════════════════════════
Base: https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/
Images (9):
hero portrait ....... bf3df250-6dbc-4deb-a275-776985e9cd2a.png
statement hover ..... 45ff55db-2ee5-4706-b0ac-449ddc3e23fa.png
album cover ......... 7ccc41a0-1638-41dd-99bb-0e63578cd365.png
tour London ......... 0a96faf9-8962-4a6b-b380-8c27e96a6c81.jpg
tour Berlin ......... cd33407b-c0ff-43b0-8e7e-adb7a883c53f.jpg
tour Paris .......... c810cd28-c08c-4789-9fbe-d41f9ad56124.jpg
tour Amsterdam ...... 62f6a29f-1912-4492-ae31-5ae2eb285364.jpg
tour Chicago ........ 8ea3ca62-e972-49b0-a416-9f422b17ada9.jpg
tour Detroit ........ fa644c2e-0f73-4c6c-81a1-1a50399990fe.jpg
Audio (2, audio/mpeg):
main theme .......... 26c56a9f-8ed6-4a21-a7b0-032861351dc4.mp3
32-second preview ... c713acb2-dec3-4887-bc30-08babb564591.mp3
There is NO video anywhere on this page.
The CDN sends Access-Control-Allow-Origin: *, so the hero image can be used as a
CORS-clean WebGL texture.
════════════════════════════════════════════════════════════════════
2. FONTS
════════════════════════════════════════════════════════════════════
Two families:
A) "MonaSemi" = Mona Sans SemiCondensed (GitHub's Mona Sans, SIL OFL).
Three @font-face declarations sharing family name MonaSemi:
font-weight:250 -> SemiCondensed ExtraLight
font-weight:400 -> SemiCondensed Regular
font-weight:500 -> SemiCondensed Medium
Used for ALL display type (hero name, statement, release title, tour title,
menu links). Stack: MonaSemi, sans-serif
B) "Mona" = Mona Sans Regular (normal width). Used for <body> default and the
small superscript marks. Stack: Mona, Arial, sans-serif
C) UI text variable:
:root{--pp:"PP Neue Montreal",Arial,sans-serif}
IMPORTANT: PP Neue Montreal is deliberately NOT loaded (it is a commercial
font). Declare the variable exactly as above so it falls back to Arial. Every
nav/label/table/caption/button uses var(--pp). Do NOT add a webfont for it.
Load Mona Sans from a CDN, and if unavailable let it fall back to sans-serif
without breaking layout.
════════════════════════════════════════════════════════════════════
3. GLOBAL TOKENS
════════════════════════════════════════════════════════════════════
:root{--orange:#ff630f;--grey:#949494;--pp:"PP Neue Montreal",Arial,sans-serif}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:#000;color:#fff;font-family:Mona,Arial,sans-serif}
button,a{font:inherit} button{color:inherit;font-weight:400}
a{color:inherit;text-decoration:none}
Reusable .pill button:
display:inline-flex;align-items:center;justify-content:center;
background:#fff;color:#000;border-radius:999px;gap:10px;padding:16px 20px;
font:400 18px/18px var(--pp);letter-spacing:-.18px;transition:.25s
:hover -> background:var(--orange);color:#fff;transform:scale(1.04)
Custom cursor (pointer:fine only):
html,body,body *{cursor:none!important}
.defaultCursor — position:fixed;z-index:9999;20x20px;border:1px solid #fff;
border-radius:50%;background:rgba(255,255,255,.5);opacity:0;pointer-events:none;
transition:opacity .16s cubic-bezier(.44,0,.56,1). JS sets
transform:translate3d(Xpx,Ypx,0) translate(-50%,-50%) on pointermove and adds
.visible. Hidden on blur / pointerleave of <html>. Class .overAlbum -> opacity:0.
On pointer:coarse restore normal cursor and hide it.
Reduced motion:
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;
animation:none!important;transition:none!important}}
════════════════════════════════════════════════════════════════════
4. SECTION 1 — HERO
════════════════════════════════════════════════════════════════════
<section class="hero">: position:relative;min-height:min(900px,100svh);
background:#000;overflow:hidden; custom props --x:50% --y:50% --hero-scale:1
--hero-shift:0%.
::after overlay: inset:0;
background:linear-gradient(180deg,rgba(0,0,0,.5),transparent 35%,rgba(0,0,0,.5));
pointer-events:none.
Two stacked full-bleed layers (both z-index:0, inset:0, 100%x100%,
transform:translate3d(0,var(--hero-shift),0) scale(var(--hero-scale));
transform-origin:50% 50%;will-change:transform;pointer-events:none):
.heroBackdrop — background:url(HERO PNG) center 35%/cover no-repeat (fallback)
.heroShader — <canvas>, opacity:0;transition:opacity .45s;
.ready -> opacity:1, and .heroShader.ready~.cursorGlow{display:none}
.cursorGlow (CSS fallback glow, shown only until WebGL is ready):
position:absolute;inset:0;mix-blend-mode:screen;filter:blur(9px);
background:radial-gradient(circle 180px at var(--x) var(--y),
rgba(255,99,15,.44),rgba(255,99,15,.08) 45%,transparent 73%);
Hidden on @media(pointer:coarse).
JS: on hero pointermove set --x/--y to cursor position relative to hero.
--- WebGL ripple shader ---------------------------------------------
Init only if window.matchMedia("(min-width: 769px) and (pointer: fine)").matches
and a webgl context is available ({alpha:true,antialias:false}).
Fullscreen triangle pair: Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]).
Texture: CLAMP_TO_EDGE both axes, LINEAR min/mag, UNPACK_FLIP_Y_WEBGL=1.
Set image.crossOrigin="anonymous" BEFORE image.src (else the canvas taints).
On load: upload texture, set ready, canvas.classList.add("ready").
DPR: Math.min(devicePixelRatio||1, 2.25); resize on window resize.
Uniforms: sampler2D image; vec2 resolution, imageSize, mouse, trail[9];
float strength, time.
Vertex shader:
attribute vec2 position; varying vec2 vUv;
void main(){vUv=position*.5+.5;gl_Position=vec4(position,0.,1.);}
Fragment shader (use verbatim):
precision highp float;
varying vec2 vUv;
uniform sampler2D image; uniform vec2 resolution,imageSize,mouse,trail[9];
uniform float strength,time;
vec2 coverUv(vec2 uv){
float rs=resolution.x/resolution.y;
float is=imageSize.x/imageSize.y;
vec2 scale=rs<is?vec2(rs/is,1.):vec2(1.,is/rs);
return (uv-.5)*scale+.5;
}
void main(){
vec2 uv=vUv;
vec2 aspect=vec2(resolution.x/resolution.y,1.);
vec2 delta=(uv-mouse)*aspect;
float dist=length(delta);
float radius=.47;
float falloff=smoothstep(radius,0.,dist)*strength;
vec2 dir=delta/(dist+.0001);
vec2 tangent=vec2(-dir.y,dir.x);
float wave=sin(dist*38.-time*2.15)*falloff;
float secondary=sin(dist*19.+time*.7)*falloff;
vec2 displacement=dir*(wave*.00525+falloff*.004);
displacement+=tangent*secondary*.001875;
vec2 sampleUv=coverUv(uv+displacement);
float aberration=(falloff*.0016+abs(wave)*.0012);
vec2 rgbShift=dir*aberration;
float r=texture2D(image,sampleUv+rgbShift).r;
float g=texture2D(image,sampleUv).g;
float b=texture2D(image,sampleUv-rgbShift).b;
vec3 color=vec3(r,g,b);
vec3 orange=vec3(1.,.22,.025);
float glow=falloff*(.10+.08*(sin(dist*28.-time*1.5)*.5+.5));
float trailGlow=0.;
for(int i=0;i<9;i++){
vec2 trailDelta=(uv-trail[i])*aspect;
float trailDistance=length(trailDelta);
float age=1.-float(i)/9.;
float halo=exp(-trailDistance*trailDistance*145.);
float core=exp(-trailDistance*trailDistance*420.);
float shimmer=.82+.18*sin(time*2.-float(i)*.72);
trailGlow+=(halo*.48+core*.28)*age*age*shimmer;
}
float headProtection=smoothstep(.035,.105,dist);
trailGlow=min(trailGlow,1.35)*strength*headProtection;
vec3 trailOrange=vec3(1.,.31,.045);
vec3 trailGold=vec3(1.,.58,.20);
color+=orange*glow;
color+=trailOrange*trailGlow*.46;
color+=trailGold*pow(trailGlow,1.65)*.11;
gl_FragColor=vec4(color,1.);
}
Animation loop (requestAnimationFrame):
mouse normalized to hero rect; y inverted (1 - (clientY-top)/height).
currentX += (targetX-currentX)*.075 (same for Y)
currentStrength += (targetStrength-currentStrength) * (targetStrength ? .07 : .028)
targetStrength = 1 on pointermove, 0 on pointerleave.
9-point follow chain, each point chasing the previous:
for i in 0..8: follow = .19 - i*.009; trail[i] += (leader - trail[i])*follow;
leader = trail[i]
Pass time = timestamp*.001.
--- Hero nav (z-index:6) ---
position:absolute;top:28px;left:3%;right:3%;display:flex;
justify-content:space-between;align-items:center;
font:400 18px/18px var(--pp);letter-spacing:-.18px.
Left: clock — "GMT+X // LOCAL" in #747474 + live <time> in #fff, updating every
1000ms via toLocaleTimeString with hour12:false and 2-digit h/m/s.
Offset computed from -new Date().getTimezoneOffset()/60, integer shown
plain, otherwise toFixed(1).
Center: .menu button — position:absolute;top:50%;left:50%;
transform:translate(-50%,-50%);display:grid;place-items:center;gap:6px.
.equalizer: height:12px;display:flex;align-items:flex-end;gap:3px with
seven <i> bars, width:3px, height:var(--bar-height) =
3,7,12,4,8,6,3 px; transition:height .28s cubic-bezier(.22,1,.36,1);
.menu.open i{height:4px}.
<small>MENU</small> — font:400 14px/18px var(--pp);letter-spacing:.14px.
Right: .pill "Buy tickets" linking to #tour.
Nav entrance: animation:introFadeDown .5s cubic-bezier(.44,0,.56,1) both
@keyframes introFadeDown{from{opacity:0;transform:translateY(-16px)}to{...}}
--- Menu panel (full-width black overlay) ---
.menuPanel{position:absolute;z-index:4;inset:0 0 auto;height:507px;
display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
gap:48px;padding:105px 40px 80px;background:#000;
opacity:0;visibility:hidden;pointer-events:none;transform:translateY(-24px);
transition:opacity .42s cubic-bezier(.44,0,.56,1),
transform .42s cubic-bezier(.44,0,.56,1),
visibility 0s linear .42s}
.menuPanel.open{opacity:1;visibility:visible;pointer-events:auto;
transform:translateY(0); same transitions but visibility delay 0s}
Links: Statement / Release / Tour -> #statement / #release / #tour
font:500 96px/88px MonaSemi;letter-spacing:-3.84px;color:#fff;text-align:center
ROLLING LETTER HOVER — each link is built per character:
.rollingLink{display:flex}
.rollingLetter{display:inline-block;height:88px;overflow:hidden}
.rollingTrack{display:flex;flex-direction:column;height:176px;
transform:translateY(0);
transition:transform .5s cubic-bezier(.44,0,.56,1);
transition-delay:var(--roll-delay)} // --roll-delay = index * 34ms
.rollingTrack>span{display:block;height:88px;flex:0 0 88px;line-height:88px}
Each track contains the SAME letter twice.
.rollingLink:hover .rollingTrack{transform:translateY(-88px)}
@media(max-width:700px): panel height 430px, padding-top 100px, gap 28px,
link font-size/line-height 64px, letter-spacing -2.56px,
.rollingLetter height 64px, .rollingTrack height 128px,
.rollingTrack>span height/flex-basis/line-height 64px,
hover translateY(-64px).
--- Hero title ---
.heroTitle{position:absolute;z-index:2;top:130px;left:3%;right:3%;display:block}
.heroNameRow{display:flex;flex-direction:row;align-items:flex-start;
justify-content:space-between;width:100%;height:168px}
Both the <b>DJ</b> and the <h1> use:
font-family:MonaSemi;font-size:231px;line-height:.727;letter-spacing:-13.86px;
color:#fff;margin:0
<b>DJ</b> font-weight:500 and transform:translateX(calc(-.085em + 8px))
<h1> position:relative;display:flex;align-items:flex-start;text-align:right
containing <strong>Zak</strong> (font-weight:500) immediately followed by
<span class="derdan">Derdan</span> (font-weight:250) — NO space between,
plus <sup>®</sup>:
position:absolute;top:0;right:0;font:250 40px/normal MonaSemi;
letter-spacing:-2.4px
.heroSubrow{display:flex;justify-content:space-between;align-items:center;
width:100%;margin-top:28px;min-height:18px} with
left <small>CLASSIC DEEP HOUSE MUSICIAN</small>
right <small>[ SPEC-01 // DEEP HOUSE ]</small> (text-align:right)
PER-LETTER INTRO (the signature effect):
.animatedWord{display:inline-flex;font:inherit;letter-spacing:inherit}
.letterMask{display:inline-block;overflow:visible;
padding-bottom:.08em;margin-bottom:-.08em}
.heroLetter{display:inline-block;font:inherit;letter-spacing:inherit;
transform:translateY(130%);opacity:0;filter:blur(40px);
will-change:transform,opacity,filter;
animation:heroLetterIn .5s cubic-bezier(.44,0,.56,1) var(--letter-delay) both}
@keyframes heroLetterIn{
from{opacity:0;transform:translateY(130%);filter:blur(40px)}
to{opacity:1;transform:translateY(0);filter:blur(0)}}
--letter-delay = START + charIndex * 0.1s, with START:
"DJ" = .38s, "Zak" = .58s, "Derdan" = .88s
Staggered entrances:
h1 sup -> animation:introFade .5s cubic-bezier(.44,0,.56,1) 1.48s both
.heroSubrow -> introFade ... 1.18s both
.heroNotes -> introRise ... 1.55s both
.heroCta -> introRise ... 2.075s both
@keyframes introFade{from{opacity:0}to{opacity:1}}
@keyframes introRise{from{opacity:0;transform:translateY(56px)}to{...translateY(0)}}
--- Hero footer bits ---
.heroNotes{position:absolute;z-index:2;left:3%;bottom:40px;width:400px;
max-width:calc(100vw - 6%);font:400 18px/1.2 var(--pp);letter-spacing:.02em}
p{margin:0 0 8px}
Line 1: "• Analog synthetics, vinyl-only recordings, 120BPM systemics."
Line 2: "• Resident advisor booking list // Spring-Summer Tour ’25"
.heroCta{position:absolute;z-index:2;right:3%;bottom:34px} .pill "See schedule" -> #tour
--- Hero scroll parallax ---
On scroll (passive) + resize, rAF-throttled:
progress = clamp(0..1, -rect.top / rect.height)
--hero-scale = 1 + progress*.2
--hero-shift = (progress * -7) + "%"
Skip entirely when prefers-reduced-motion: reduce.
════════════════════════════════════════════════════════════════════
5. SECTION 2 — ARTIST STATEMENT (id="statement")
════════════════════════════════════════════════════════════════════
.statement{min-height:900px;padding:40px 3%;position:relative}
.sectionKicker "// Artist Statement" — font:400 18px/18px var(--pp);letter-spacing:-.18px
.statementText{position:relative;font-family:MonaSemi;font-weight:500;
font-size:clamp(58px,6.65vw,96px);line-height:.92;letter-spacing:-.04em;
margin-top:65px}
Five lines, each wrapped in a mask (exact copy):
"Rooted in the golden-era deep house"
"aesthetics of Chicago and Detroit, Zak"
"Derdan crafts stripped-back grooves"
"utilizing original vintage synthesizers"
"and analog mixing desks."
.statementLineMask{display:block;overflow:hidden;color:#fff;cursor:crosshair;
padding-bottom:.14em;margin-bottom:-.14em}
.statementLineMask>span{display:block;transform:translateY(108%);opacity:.15;
transition:transform .9s cubic-bezier(.22,1,.36,1) var(--line-delay),
opacity .65s ease var(--line-delay)}
--line-delay = index * 110ms (0,110,220,330,440)
.statement.visible .statementLineMask>span{transform:translateY(0);opacity:1}
POINTER-TRAILING IMAGE:
.statementHover{position:absolute;z-index:3;left:0;top:0;width:280px;opacity:0;
transform:translate3d(var(--follow-x,68%),var(--follow-y,58%),0)
translate(-45%,-38%);
scale:.94;transition:scale .42s cubic-bezier(.22,1,.36,1);
mix-blend-mode:difference;pointer-events:none;will-change:transform}
.statementText:hover .statementHover{opacity:1;scale:1}
Hidden on @media(pointer:coarse).
JS easing loop on .statementText pointermove:
delta = min(32, now - prev); follow = 1 - Math.exp(-delta/140);
current += (target-current)*follow; write --follow-x/--follow-y in px.
Stop the rAF when |target-current| <= .1 on both axes.
.statementMeta{position:absolute;left:3%;right:3%;bottom:45px;display:flex;
justify-content:space-between;align-items:end;color:var(--grey)}
All children font:400 18px/1.2 var(--pp);letter-spacing:.02em
Left <p> (with <br>): "Each composition is structured around / raw physical
grooves, warm spatial / reverbs, and hypnotic basslines designed / for dark,
extended club sets."
Right column (text-align:right): <small>EQUIPMENT</small> then three pill
buttons "Roland TR-909", "Juno-106", "Moog Minitaur" —
border:1px solid #777;background:none;border-radius:99px;padding:10px 14px;
margin-top:12px;color:var(--grey)
Reveal: IntersectionObserver threshold .2 -> add .visible once, then disconnect.
════════════════════════════════════════════════════════════════════
6. SECTION 3 — RELEASE (id="release")
════════════════════════════════════════════════════════════════════
.release{position:relative;height:900px;min-height:900px;padding:38px 3%;
perspective:850px;overflow:hidden;isolation:isolate}
.releaseTop{display:flex;justify-content:space-between;
font:400 18px/18px var(--pp);letter-spacing:-.18px}
Left "// Latest Release & Set Showcase" Right "RELEASING MAY ’25 // VINYL & DIGITAL"
<h2> font:500 clamp(90px,12.5vw,180px)/.84 MonaSemi;text-align:center;
letter-spacing:-.04em;margin:90px 0 0
Line 1: "GRIDLOCK" inside .releaseTitleFirst
{display:flex;align-items:flex-start;justify-content:center}
plus <sup>[LP]</sup> — font:14px Mona;position:static;margin:10px 0 0 8px;
opacity:0;transition:opacity .5s cubic-bezier(.44,0,.56,1) 1.35s;
.release.visible h2 sup{opacity:1}
Line 2: "HYPNOTICS" (.releaseTitleLine{display:block})
PER-LETTER REVEAL:
.releaseLetterMask{display:inline-block;overflow:visible}
.releaseLetter{display:inline-block;font:inherit;letter-spacing:inherit;
opacity:0;filter:blur(40px);transform:translateY(130%);
will-change:transform,opacity,filter;
transition:transform .5s cubic-bezier(.44,0,.56,1) var(--release-delay),
opacity .5s cubic-bezier(.44,0,.56,1) var(--release-delay),
filter .5s cubic-bezier(.44,0,.56,1) var(--release-delay)}
--release-delay = (offset + charIndex) * .05s, offset 0 for GRIDLOCK, 8 for HYPNOTICS
.release.visible .releaseLetter{opacity:1;filter:blur(0);transform:translateY(0)}
3D ALBUM CARD (a <button>):
.album{position:absolute;width:347px;height:347px;left:50%;top:401px;
--rx:0deg;--ry:0deg;
transform:translateX(-50%) rotateX(var(--rx)) rotateY(var(--ry)) scale(1.025);
transition:transform .12s cubic-bezier(.22,.8,.3,1);
transform-style:preserve-3d;mix-blend-mode:difference;
background:none;border:0;padding:0;cursor:none;z-index:3}
img{width:100%;height:100%;object-fit:contain;mix-blend-mode:normal}
On .release pointermove:
--rx = ((clientY-rect.top)/rect.height - .5) * -24.2 deg
--ry = ((clientX-rect.left)/rect.width - .5) * 30.8 deg
.playerIcon{position:absolute;inset:50% auto auto 50%;width:64px;height:64px;
display:flex;align-items:center;justify-content:center;
transform:translate(-50%,-50%)}
Play glyph = white right-pointing triangle, 42x52, rounded corners.
Pause glyph = two white rounded bars, 36x52 (each 14 wide, 8 gap, radius 2).
CIRCULAR HOVER LABEL:
.albumCursor{position:absolute;z-index:20;width:100px;height:100px;
padding:39px 19px;display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:10px;border:1px solid #fff;border-radius:50%;
color:#fff;text-align:center;font:400 14px/12px var(--pp);
letter-spacing:-.14px;opacity:0;
transform:translate(-50%,-50%) scale(.68);
transition:opacity .22s,transform .38s cubic-bezier(.22,1,.36,1);
pointer-events:none}
.albumCursor.visible{opacity:1;transform:translate(-50%,-50%) scale(1)}
Text: "Listen to<br>an excerpt" (span{white-space:nowrap})
On album pointerenter/pointermove position it at cursor relative to .release and
add .visible + add .overAlbum to .defaultCursor; remove both on pointerleave.
Hidden on @media(pointer:coarse) (and .album gets cursor:pointer back).
WAVEFORM CANVAS:
.wave{position:absolute;z-index:1;left:0;right:0;top:623px;width:100%;
height:90px;opacity:0;transition:.4s} .wave.visible{opacity:1}
Only animates while the main theme is playing. 2D canvas, DPR-scaled,
strokeStyle #fff, lineWidth 1, step x by 4:
energy = sin(x*0.13 + frame*0.09) * sin(x*0.031 - frame*0.04)
y = height/2 + energy * (9 + 8*sin(frame*0.03 + x*0.01))
frame++ each rAF.
.releaseCaption{position:absolute;top:788px;left:0;right:0;text-align:center;
color:#fff;font:400 18px/18px var(--pp);letter-spacing:-.18px}
span{display:block} span+span{margin-top:12px;color:#949494}
"Listen to Promotional Mix" / "10 Tracks // 66 Mins Running Time"
Reveal: IntersectionObserver threshold .22.
════════════════════════════════════════════════════════════════════
7. SECTION 4 — TRACKLIST
════════════════════════════════════════════════════════════════════
.trackSection{min-height:830px;padding:40px 4%;padding-bottom:40px;
background:linear-gradient(180deg,#000 0%,#000 48%,#ea4f16 78%,#f6af60 100%)}
Kicker: "TRACKLIST"
Grid for BOTH header and rows: grid-template-columns:42px 38px 1fr 90px 70px;
align-items:center
.trackHead{margin-top:35px;margin-bottom:6px;padding:0 16px;color:#949494;
font:400 14px/20px var(--pp);letter-spacing:-.14px}
Cells: "#", "Track name" (grid-column:2/4), "Example" (text-align:left),
"Duration" (text-align:right)
.track{position:relative;min-height:54px;padding:16px;border-top:1px solid #fff;
font-family:var(--pp);font-weight:400;isolation:isolate}
.track>*{z-index:1}
Progress fill: .track:before{content:"";position:absolute;z-index:2;inset:0;
width:var(--progress,0);background:#fff;mix-blend-mode:difference;
pointer-events:none}
Cell styles:
index -> color:rgba(255,255,255,.5);font:400 14px/20px var(--pp);ls -.14px
title -> color:#fff;font:400 18px/18px var(--pp);ls -.18px
example -> color:rgba(255,255,255,.5);font:400 14px/20px var(--pp);ls -.14px
duration -> color:#fff;text-align:right;font:400 14px/20px var(--pp);ls -.14px
Button: 24x24 flex-centered, border:0;background:none;padding:0;
:not(:disabled){cursor:pointer}; :disabled{opacity:1}
Play glyph 14x18 white triangle / Pause glyph 13x18 two white bars.
Rows (index, title, example, duration):
01 Midnight Horizon (Original Mix) 00:32 07:42
02 Subterranean Cables (Dub) 00:43 06:18
03 Detroit Wire (Classic Echo Out) 00:40 08:05
04 Dusty Faders (Studio Jam) 00:42 05:51
05 Echoes in the Grid (Live Edit) 00:41 07:12
06 Low Frequency Drift (Deep Mix) 00:41 08:30
07 Signal Loss (Reprise) 00:44 04:22
08 The Last Transmission (Outro) 00:38 03:10
09 Resonant Horizon (Extended Mix) 00:39 09:44
10 Static Pulse (Bonus Track) 00:46 06:00
Only row 01 is interactive; rows 02-10 have disabled buttons.
.trackBottom{display:flex;justify-content:space-between;align-items:center;
margin-top:105px}
span -> color:#fff;font:400 18px/18px var(--pp);letter-spacing:-.18px
"HEAR IT LIVE – SUMMER TOUR ’26" + .pill "View Schedule" -> #tour
.trackBottom .pill:hover{background:#fff;color:#000} (overrides orange hover)
IN-VIEW SEQUENCE (IntersectionObserver threshold .5):
.sectionKicker,.trackHead,.track,.trackBottom start
opacity:0;transform:translateY(34px);
transition:opacity .5s cubic-bezier(.44,0,.56,1),
transform .5s cubic-bezier(.44,0,.56,1)
delays: kicker 0s | head .08s | each row var(--track-delay) = .16 + i*.085 s |
bottom 1.5s
.trackSection.visible ... {opacity:1;transform:translateY(0)}
════════════════════════════════════════════════════════════════════
8. SECTION 5 — TOUR (id="tour")
════════════════════════════════════════════════════════════════════
.tour{padding:40px 3% 0;height:900px;min-height:900px}
header{display:flex;justify-content:space-between;align-items:start}
h2{font:500 clamp(52px,6vw,86px)/1 MonaSemi;letter-spacing:-.045em;margin:0}
"SPRING / SUMMER " then <em>TOUR ’26</em>
em{font-style:normal;color:var(--orange)}
header>span "// TOUR DATES" -> font:400 18px/18px var(--pp);letter-spacing:-.18px
PER-LETTER REVEAL (same recipe, different var):
.tourTitlePart{display:inline} .tourLetterMask{display:inline-block;overflow:visible}
.tourLetter{display:inline-block;opacity:0;filter:blur(40px);
transform:translateY(130%);will-change:transform,opacity,filter;
transition:transform .5s cubic-bezier(.44,0,.56,1) var(--tour-letter-delay),
opacity .5s cubic-bezier(.44,0,.56,1) var(--tour-letter-delay),
filter .5s cubic-bezier(.44,0,.56,1) var(--tour-letter-delay)}
--tour-letter-delay = (offset + charIndex) * .05s; offset 0 for the first part,
16 for "TOUR ’26". Render spaces as .
.tour.visible .tourLetter{opacity:1;filter:blur(0);transform:translateY(0)}
Table grid (header + rows): grid-template-columns:1.1fr 1.4fr 2.8fr 1.1fr 140px;
align-items:center
.dateHead{margin-top:85px;padding:12px 0;color:#949494;font:400 14px/20px var(--pp)}
DATE | LOCATION | VENUE / SYSTEM SETUP | AVAILABILITY | REGISTRATION
.date{position:relative;border-top:1px solid #fff;padding:16px 0;
font-family:var(--pp)}
first cell -> font:500 18px/20px var(--pp);letter-spacing:-.18px;color:#fff
cells 2-4 -> font:400 18px/20px var(--pp);letter-spacing:-.18px;color:#fff
.date.soldOut>span:nth-child(4){color:rgba(255,255,255,.5)}
button{justify-self:end;background:var(--orange);border:0;border-radius:99px;
padding:16px 20px;white-space:nowrap;color:#fff;
font:400 18px/18px var(--pp);letter-spacing:-.18px;
transition:background-color .3s cubic-bezier(.44,0,.56,1),
color .3s cubic-bezier(.44,0,.56,1)}
:not(.sold){cursor:pointer} :not(.sold):hover{background:#fff;color:#000}
button.sold{background:transparent;border:1px solid rgba(255,255,255,.5);
color:rgba(255,255,255,.5);padding:12px 24px}
Rows (date, location, venue, availability, image):
12 MAY 2025 | London, UK | Fold Club | Available | London jpg
19 MAY 2025 | Berlin, DE | Tresor (Basement) | Available | Berlin jpg
05 JUN 2025 | Paris, FR | Rex Club | Available | Paris jpg
12 JUN 2025 | Amsterdam, NL | De School Vault | Available | Amsterdam jpg
24 JUN 2025 | Chicago, US | Smartbar | Available | Chicago jpg
02 JUL 2025 | Detroit, US | TV Lounge | SOLD OUT | Detroit jpg
Button label: "Get Ticket", or "Sold Out" + class "sold" when SOLD OUT.
ROW HOVER THUMBNAIL:
.dateHoverImage{position:absolute;z-index:4;left:62%;top:50%;
width:180px;height:180px;opacity:0;
transform:translate(-50%,-44%) scale(.96);filter:blur(8px);
pointer-events:none;
transition:opacity .42s cubic-bezier(.44,0,.56,1),
transform .42s cubic-bezier(.44,0,.56,1),
filter .42s cubic-bezier(.44,0,.56,1)}
img{display:block;width:100%;height:100%;object-fit:cover}
.date:hover{z-index:5}
.date:hover .dateHoverImage{opacity:1;transform:translate(-50%,-50%) scale(1);
filter:blur(0)}
footer{display:flex;justify-content:space-between;margin-top:75px;
padding-bottom:24px;color:rgba(255,255,255,.5);
font:400 18px/18px var(--pp);letter-spacing:-.18px}
"* General admission doors open at 22:00. Sound checking starts 2 hours prior."
"DOCUMENT SYSTEM WIREFRAME V1.0 // ZAK DERDAN"
IN-VIEW SEQUENCE (IntersectionObserver threshold .5):
header>span, .dateHead, .date, footer start opacity:0;transform:translateY(34px)
with the same .5s cubic-bezier(.44,0,.56,1) transitions;
delays: header span .08s | dateHead .1s | each row var(--delay)=.16+i*.085s |
footer 1.2s; .tour.visible ... {opacity:1;transform:translateY(0)}
════════════════════════════════════════════════════════════════════
9. AUDIO BEHAVIOUR
════════════════════════════════════════════════════════════════════
Two <audio> elements (main theme, 32s preview). They are mutually exclusive:
starting one pauses the other and resets its icon/state.
Album click -> toggle main theme; swap play/pause glyph; toggle .wave.visible and
start/stop the waveform rAF; update aria-label
("Play main theme" / "Pause main theme"); on "ended" reset to paused.
Track 01 btn -> toggle preview; swap the 14x18/13x18 glyph; toggle .playing on the row.
On loadedmetadata: show total duration in the "Example" cell.
On timeupdate: cell shows REMAINING time counting down, and
--progress = (currentTime/duration)*100 + "%" (drives the white difference wipe).
On ended: reset --progress to 0%, restore full duration, reset icon.
Time format: mm:ss, zero-padded, using Math.ceil and clamped at >= 0.
════════════════════════════════════════════════════════════════════
10. RESPONSIVE (must all be implemented)
════════════════════════════════════════════════════════════════════
@media(max-width:1200px){
.heroNameRow>b,.heroTitle h1{font-size:clamp(112px,16vw,190px);letter-spacing:-.06em}
.heroTitle h1 sup{font-size:32px}
}
@media(max-width:900px){
.hero{min-height:760px}
.statement{min-height:800px}
.statementMeta{position:static;margin-top:80px;gap:30px}
.release{height:680px;min-height:680px}
.album{width:278px;height:278px;top:250px}
.wave{top:403px} .releaseCaption{top:568px}
.trackSection{min-height:780px}
.tour{height:auto;min-height:780px}
.dateHead{display:none}
.date{grid-template-columns:1fr 1fr;gap:8px;padding:18px 0}
.date span:nth-child(3){grid-column:1/2}
.date button{grid-column:2;grid-row:2/4}
.dateHoverImage{display:none}
.tour header{display:block} .tour header>span{display:block;margin-top:10px}
.tour footer{gap:30px}
}
@media(max-width:700px){
.heroTitle{top:110px}
.heroNameRow>b,.heroTitle h1{font-size:clamp(62px,17vw,112px)}
.heroTitle h1 sup{font-size:20px;letter-spacing:-1.2px}
.heroSubrow{margin-top:18px;font-size:9px}
(plus the menu-panel overrides listed in section 4)
}
@media(max-width:560px){
.heroNotes{bottom:80px}
.heroCta{left:3%;right:auto}
.statement{padding-top:30px;min-height:760px}
.statementText{font-size:43px;line-height:.95}
.statementMeta{display:block}
.statementMeta div{text-align:left;margin-top:30px}
.release{height:590px;min-height:590px}
.releaseTop span:last-child{display:none}
.release h2{font-size:66px;margin-top:90px}
.album{width:208px;height:208px;top:230px}
.wave{top:313px} .releaseCaption{top:478px}
.trackSection{padding-left:0;padding-right:0}
.trackSection>.sectionKicker,.trackBottom{margin-left:3%;margin-right:3%}
.trackHead{display:none}
.track{grid-template-columns:28px 26px 1fr 42px;padding:13px 10px}
.track span:nth-last-child(2){display:none}
.track span:last-child{text-align:right}
.trackBottom{margin-top:70px}
.tour h2{font-size:48px}
.date{grid-template-columns:1fr 1fr}
.tour footer{display:block;line-height:1.5}
.tour footer span:last-child{display:block;margin-top:24px}
}
@media(pointer:coarse){
.cursorGlow{display:none}
.statementHover{display:none}
.albumCursor{display:none}
.album{cursor:pointer}
html,body,body *{cursor:auto}
.defaultCursor{display:none}
}
════════════════════════════════════════════════════════════════════
11. REQUIREMENTS
════════════════════════════════════════════════════════════════════
- <title>DJ Zak Derdan — Deep House Musician</title>
- Meta description: "Gridlock Hypnotics. DJ Zak Derdan releases, tracklist and
Summer Tour ’26."
- Keep all typographic apostrophes as ’ (U+2019), not '.
- Build repeated markup (per-letter spans, 10 track rows, 6 date rows) in JS from
data arrays rather than hand-writing it.
- Every IntersectionObserver fires ONCE then disconnects.
- Semantic HTML: <main>, <section>, <nav>, <header>, <footer>, <h1>, <h2>;
aria-label on letter-split words so screen readers get the whole word;
aria-hidden on the decorative per-letter spans, canvases and cursors;
aria-expanded on the menu button.
- No console errors. Degrade gracefully with no WebGL (CSS backdrop + glow remain).[/hidecontent]