const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const canvas = document.getElementById('heroWave'); const ctx = canvas.getContext('2d'); let rafId = null; let t = 0; function resizeWave() { const ratio = Math.max(1, window.devicePixelRatio || 1); const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * ratio; canvas.height = rect.height * ratio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); } function drawWave() { const w = canvas.clientWidth; const h = canvas.clientHeight; ctx.clearRect(0, 0, w, h); const centerY = h * 0.53; // soft vertical bars behind the line for a stronger speaking look for (let x = 0; x <= w; x += 12) { const norm = x / w; const envelope = Math.pow(Math.sin(Math.PI * norm), 1.75); const bar = ( Math.sin(x * 0.03 + t * 1.2) * 0.55 + Math.sin(x * 0.09 - t * 0.85) * 0.30 ); const amp = Math.max(12, Math.abs(bar) * h * 0.42 * envelope); const alpha = 0.08 + envelope * 0.17; ctx.strokeStyle = `rgba(185,255,0,${alpha})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x, centerY - amp); ctx.lineTo(x, centerY + amp); ctx.stroke(); } const gradient = ctx.createLinearGradient(0, 0, w, 0); gradient.addColorStop(0, 'rgba(185,255,0,0)'); gradient.addColorStop(.14, 'rgba(185,255,0,.48)'); gradient.addColorStop(.5, 'rgba(220,255,120,1)'); gradient.addColorStop(.86, 'rgba(185,255,0,.48)'); gradient.addColorStop(1, 'rgba(185,255,0,0)'); ctx.lineWidth = 2.6; ctx.strokeStyle = gradient; ctx.shadowColor = 'rgba(185,255,0,.95)'; ctx.shadowBlur = 16; ctx.beginPath(); for (let x = 0; x <= w; x += 2) { const norm = x / w; const envelope = Math.pow(Math.sin(Math.PI * norm), 1.9); const carrier = Math.sin(x * 0.055 + t) * 0.62 + Math.sin(x * 0.12 - t * 1.5) * 0.34 + Math.sin(x * 0.22 + t * 0.65) * 0.18; const y = centerY + carrier * h * 0.33 * envelope; x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); } ctx.stroke(); ctx.shadowBlur = 0; ctx.strokeStyle = 'rgba(185,255,0,.28)'; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.moveTo(0, centerY); ctx.lineTo(w, centerY); ctx.stroke(); t += 0.026; if (!reduceMotion) rafId = requestAnimationFrame(drawWave); } resizeWave(); drawWave(); window.addEventListener('resize', resizeWave); const sentence = 'Ich würde gerne den Termin für morgen Nachmittag bestätigen.'; const liveText = document.getElementById('liveText'); const mailText = document.getElementById('mailText'); const demoButton = document.getElementById('demoButton'); let typingTimer = null; function runDemo() { clearTimeout(typingTimer); liveText.textContent = ''; mailText.textContent = ''; let i = 0; const typeLive = () => { liveText.textContent = sentence.slice(0, i); i += 1; if (i <= sentence.length) { typingTimer = setTimeout(typeLive, 35); } else { setTimeout(() => { let j = 0; const typeMail = () => { mailText.textContent = sentence.slice(0, j); j += 1; if (j <= sentence.length) { typingTimer = setTimeout(typeMail, 20); } }; typeMail(); }, 420); } }; typeLive(); } demoButton.addEventListener('click', runDemo); setTimeout(runDemo, 850); setInterval(() => { if (!document.hidden && !reduceMotion) runDemo(); }, 11000); const toggle = document.querySelector('.menu-toggle'); const nav = document.querySelector('.main-nav'); toggle.addEventListener('click', () => { const open = nav.classList.toggle('open'); toggle.setAttribute('aria-expanded', String(open)); });