---export interface Track { title: string; meta: string; bpm: number; duration: number; color: string; src?: string; }export interface Props { tracks: Track[];}const { tracks } = Astro.props;---Zeile 17–484: Markup & CSS<script define:vars={{ tracks }}> const ARM_PARKED = -30; const ARM_START = -25; const ARM_END = 10; let currentTrack = null; let isPlaying = false; let progress = 0; let ticker = null; let bpmTicker = null; let waveHeights = []; let audio = null; const vinyl = document.getElementById('vinyl'); const tonearm = document.getElementById('tonearm'); const platterRing = document.getElementById('platterRing'); const playBtn = document.getElementById('playBtn'); const playIcon = document.getElementById('playIcon'); const volIconBtn = document.getElementById('volIconBtn'); const volIcon = document.getElementById('volIcon'); const trackTitle = document.getElementById('trackTitle'); const trackMeta = document.getElementById('trackMeta'); const bpmLabel = document.getElementById('bpmLabel'); const bpmDot = document.getElementById('bpmDot'); const nowPlaying = document.getElementById('nowPlayingLabel'); const progressFill = document.getElementById('progressFill'); const timeElapsed = document.getElementById('timeElapsed'); const timeTotal = document.getElementById('timeTotal'); const waveformEl = document.getElementById('waveform'); let lastVolume = 0.8; function fmt(s) { return Math.floor(s/60) + ':' + String(Math.floor(s%60)).padStart(2,'0'); } function pctToAngle(pct) { return ARM_START + (ARM_END - ARM_START) * (pct / 100); } function setArm(deg) { tonearm.style.transform = 'rotate(' + deg + 'deg)'; } function genWave() { waveHeights = Array.from({length: 48}, () => 8 + Math.random() * 36); waveformEl.innerHTML = ''; waveHeights.forEach(h => { const b = document.createElement('div'); b.style.cssText = `flex:1;height:${h}px;min-height:4px;border-radius:2px;background:var(--wave-unplayed,rgba(255,255,255,0.18));transition:background 0.15s;`; waveformEl.appendChild(b); }); } function drawWave(pct) { const bars = waveformEl.children; const played = Math.floor((pct / 100) * waveHeights.length); for (let i = 0; i < bars.length; i++) { bars[i].style.background = i < played ? 'var(--accent)' : i === played && isPlaying ? 'var(--accent)' : 'var(--wave-unplayed, rgba(255,255,255,0.18))'; } } //reduced motion keeps only the fade const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); function revealTrackInfo() { const frames = reduceMotion.matches ? [{ opacity: 0 }, { opacity: 1 }] : [{ opacity: 0, transform: 'translateY(6px)' }, { opacity: 1, transform: 'none' }]; [trackTitle, trackMeta].forEach((el, i) => el.animate(frames, { duration: 240, delay: i * 40, easing: 'cubic-bezier(0.23, 1, 0.32, 1)', fill: 'backwards', })); } function hexRgba(hex, a) { const r = parseInt(hex.slice(1,3),16); const g = parseInt(hex.slice(3,5),16); const b = parseInt(hex.slice(5,7),16); return 'rgba('+r+','+g+','+b+','+a+')'; } function selectTrack(i) { if (currentTrack === i && isPlaying) { pause(); return; } currentTrack = i; progress = 0; const t = tracks[i]; document.querySelectorAll('.track-btn').forEach((b,j) => b.classList.toggle('active', j === i)); trackTitle.textContent = t.title; trackMeta.textContent = t.meta; revealTrackInfo(); bpmLabel.textContent = t.bpm + ' BPM'; timeTotal.textContent = fmt(t.duration); nowPlaying.classList.add('visible'); document.documentElement.style.setProperty('--accent', t.color); document.documentElement.style.setProperty('--accent-dim', hexRgba(t.color, 0.1)); vinyl.style.setProperty('--glow-color', hexRgba(t.color, 0.20)); vinyl.style.setProperty('--glow-color-soft', hexRgba(t.color, 0.08)); //audio support if (audio) { audio.pause(); audio = null; } if (t.src) { audio = new Audio(t.src); audio.volume = document.getElementById('volSlider').value / 100; } genWave(); drawWave(0); play(); } function play() { isPlaying = true; vinyl.classList.add('playing'); vinyl.style.animationPlayState = 'running'; platterRing.classList.add('active'); playIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>'; setArm(pctToAngle(progress)); if (audio) audio.play().catch(() => {}); // bpm pulse clearInterval(bpmTicker); bpmTicker = setInterval(() => { bpmDot.style.transform = 'scale(1.5)'; const c = tracks[currentTrack].color; vinyl.style.boxShadow = `0 0 0 2px #222, 0 0 40px rgba(0,0,0,0.8), 0 0 70px ${hexRgba(c,0.20)}, 0 0 110px ${hexRgba(c,0.07)}`; const beatMs = (60 / tracks[currentTrack].bpm) * 1000 * 0.5; setTimeout(() => { bpmDot.style.transform = ''; vinyl.style.boxShadow = ''; }, beatMs); }, (60 / tracks[currentTrack].bpm) * 1000); // progress tick clearInterval(ticker); const t = tracks[currentTrack]; if (audio) { // sync with audio ticker = setInterval(() => { if (!audio) return; progress = (audio.currentTime / audio.duration) * 100 || 0; progressFill.style.width = progress + '%'; timeElapsed.textContent = fmt(audio.currentTime); drawWave(progress); setArm(pctToAngle(progress)); if (audio.ended) { clearInterval(ticker); clearInterval(bpmTicker); nextTrack(); } }, 100); } else { // simulated progress const stepPerTick = (100 / t.duration) * 0.1; ticker = setInterval(() => { progress = Math.min(progress + stepPerTick, 100); progressFill.style.width = progress + '%'; timeElapsed.textContent = fmt((progress / 100) * t.duration); drawWave(progress); setArm(pctToAngle(progress)); if (progress >= 100) { clearInterval(ticker); clearInterval(bpmTicker); nextTrack(); } }, 100); } } function pause() { isPlaying = false; vinyl.style.animationPlayState = 'paused'; platterRing.classList.remove('active'); clearInterval(ticker); clearInterval(bpmTicker); playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>'; if (audio) audio.pause(); } function nextTrack() { setArm(ARM_PARKED); const next = currentTrack + 1; if (next >= tracks.length) { setTimeout(() => { isPlaying = false; vinyl.classList.remove('playing'); vinyl.style.animationPlayState = ''; platterRing.classList.remove('active'); playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>'; progress = 0; progressFill.style.width = '0%'; timeElapsed.textContent = fmt(0); drawWave(0); }, 600); } else { setTimeout(() => selectTrack(next), 600); } } function prevTrack() { setArm(ARM_PARKED); setTimeout(() => selectTrack((currentTrack - 1 + tracks.length) % tracks.length), 600); } // events playBtn.onclick = () => { if (currentTrack === null) { selectTrack(0); return; } if (isPlaying) pause(); else play(); }; document.getElementById('nextBtn').onclick = nextTrack; document.getElementById('prevBtn').onclick = prevTrack; document.getElementById('progressBar').onclick = (e) => { if (currentTrack === null) return; const rect = e.currentTarget.getBoundingClientRect(); progress = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)); progressFill.style.width = progress + '%'; timeElapsed.textContent = fmt((progress/100) * tracks[currentTrack].duration); if (audio) audio.currentTime = (progress / 100) * audio.duration; drawWave(progress); setArm(pctToAngle(progress)); }; waveformEl.onclick = (e) => { if (currentTrack === null) return; const rect = waveformEl.getBoundingClientRect(); progress = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)); progressFill.style.width = progress + '%'; timeElapsed.textContent = fmt((progress / 100) * tracks[currentTrack].duration); if (audio) audio.currentTime = (progress / 100) * audio.duration; drawWave(progress); setArm(pctToAngle(progress)); }; document.getElementById('volSlider').oninput = (e) => { const v = Number(e.target.value); if (audio) audio.volume = v / 100; volIcon.textContent = v === 0 ? '🔇' : '🔈'; if (v > 0) lastVolume = v / 100; }; volIconBtn.addEventListener('click', () => { const slider = document.getElementById('volSlider'); const currentVolume = Number(slider.value); if (currentVolume !== 0) { lastVolume = currentVolume / 100; slider.value = '0'; if (audio) audio.volume = 0; volIcon.textContent = '🔇'; } else { slider.value = String(Math.round(lastVolume * 100)); if (audio) audio.volume = lastVolume; volIcon.textContent = '🔈'; } }); document.querySelectorAll('.track-btn').forEach((btn, i) => { btn.onclick = () => selectTrack(i); }); // init setArm(ARM_PARKED); genWave(); drawWave(0);</script>Webentwicklung
Moderne, schnelle Websites & Web-Apps — von der Landing Page bis zum kompletten Auftritt.
