| |
| |
| |
| |
| |
| |
|
|
| (function () { |
| const splash = document.getElementById('splash'); |
| if (!splash) return; |
|
|
| if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { |
| splash.remove(); |
| return; |
| } |
|
|
| const flight = document.getElementById('flight'); |
| const trail = document.getElementById('trail'); |
| const plane = document.getElementById('plane'); |
|
|
| const totalLength = flight.getTotalLength(); |
| trail.style.strokeDasharray = `${totalLength}`; |
| trail.style.strokeDashoffset = `${totalLength}`; |
|
|
| |
| |
| const KEY_T = [0, 0.14, 0.52, 0.74, 1]; |
| const KEY_S = [0, 0.095, 0.492, 0.704, 1]; |
| const easeOutQuad = (x) => 1 - (1 - x) * (1 - x); |
| const easeInCubic = (x) => x * x * x; |
| const linear = (x) => x; |
| const SEG_EASE = [easeOutQuad, linear, linear, easeInCubic]; |
|
|
| function distanceAt(t) { |
| for (let i = 0; i < KEY_T.length - 1; i++) { |
| if (t <= KEY_T[i + 1]) { |
| const local = (t - KEY_T[i]) / (KEY_T[i + 1] - KEY_T[i]); |
| return KEY_S[i] + (KEY_S[i + 1] - KEY_S[i]) * SEG_EASE[i](local); |
| } |
| } |
| return 1; |
| } |
|
|
| |
| const FLIGHT_START = 250; |
| const FLIGHT_MS = 3550; |
| const NET_AT = 2750; |
| const TEXT_AT = 3050; |
| const LOGO_AT = 3800; |
| const PULSE_AT = 4300; |
| const FADE_AT = 4800; |
| const REMOVE_AT = 5450; |
|
|
| let rafId = null; |
| let startTime = null; |
|
|
| function frame(now) { |
| if (startTime === null) startTime = now; |
| const t = Math.min(1, (now - startTime) / FLIGHT_MS); |
| const dist = distanceAt(t) * totalLength; |
|
|
| const point = flight.getPointAtLength(dist); |
| const ahead = flight.getPointAtLength(Math.min(totalLength, dist + 2)); |
| const angle = Math.atan2(ahead.y - point.y, ahead.x - point.x) * (180 / Math.PI); |
|
|
| plane.setAttribute('transform', `translate(${point.x} ${point.y}) rotate(${angle})`); |
| trail.style.strokeDashoffset = `${totalLength - dist}`; |
|
|
| if (t < 1) rafId = requestAnimationFrame(frame); |
| } |
|
|
| const timers = [ |
| setTimeout(() => { rafId = requestAnimationFrame(frame); }, FLIGHT_START), |
| setTimeout(() => splash.classList.add('phase-net'), NET_AT), |
| setTimeout(() => splash.classList.add('phase-text'), TEXT_AT), |
| setTimeout(() => splash.classList.add('phase-logo'), LOGO_AT), |
| setTimeout(() => splash.classList.add('phase-pulse'), PULSE_AT), |
| setTimeout(() => splash.classList.add('phase-out'), FADE_AT), |
| setTimeout(dismiss, REMOVE_AT), |
| ]; |
|
|
| function dismiss() { |
| timers.forEach(clearTimeout); |
| if (rafId) cancelAnimationFrame(rafId); |
| splash.remove(); |
| } |
|
|
| |
| splash.addEventListener('click', () => { |
| splash.classList.add('phase-out'); |
| setTimeout(dismiss, 380); |
| }); |
|
|
| |
| const PARTICLE_SPOTS = [ |
| [12, 22], [22, 70], [38, 14], [62, 80], [74, 18], [86, 58], [90, 32], [8, 48], |
| ]; |
| PARTICLE_SPOTS.forEach(([x, y], index) => { |
| const dot = document.createElement('span'); |
| dot.className = 'splash-particle'; |
| dot.style.left = `${x}%`; |
| dot.style.top = `${y}%`; |
| dot.style.setProperty('--pd', `${index * 0.28}s`); |
| splash.appendChild(dot); |
| }); |
| })(); |
|
|