motionsites中文站 / DJ ZakDerdan
DJ ZakDerdan
aadmin2026-09-23 发布Site模板0 阅读

DJ ZakDerdan

类型:Site模板 · 收录:2026-09-23 19:21:50 · 源码:会员可见

[hidecontent type="vip1"]
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]
← 返回首页