482 lines
14 KiB
JavaScript
482 lines
14 KiB
JavaScript
// ============================================
|
|
// MercyV Loading Screen - Premium Edition
|
|
// ============================================
|
|
|
|
// DOM Elements
|
|
const progressFill = document.getElementById('progress-fill');
|
|
const loadingPercent = document.querySelector('.loading-percent');
|
|
const loadingStatus = document.getElementById('loading-status');
|
|
const backgroundMusic = document.getElementById('background-music');
|
|
const playBtn = document.getElementById('play-btn');
|
|
const playIcon = document.getElementById('play-icon');
|
|
const prevBtn = document.getElementById('prev-btn');
|
|
const nextBtn = document.getElementById('next-btn');
|
|
const volumeBtn = document.getElementById('volume-btn');
|
|
const volumeIcon = document.getElementById('volume-icon');
|
|
const volumeSlider = document.getElementById('volume-slider');
|
|
const trackName = document.getElementById('track-name');
|
|
const visualizer = document.querySelector('.audio-visualizer');
|
|
|
|
// Config references
|
|
const loadingMessages = CONFIG.loadingMessages;
|
|
const tracks = CONFIG.tracks;
|
|
|
|
let currentTrack = 0;
|
|
let isPlaying = false;
|
|
let isMuted = false;
|
|
let lastVolume = 50;
|
|
|
|
// ============================================
|
|
// Particle System
|
|
// ============================================
|
|
|
|
class ParticleSystem {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.ctx = canvas.getContext('2d');
|
|
this.particles = [];
|
|
this.resize();
|
|
window.addEventListener('resize', () => this.resize());
|
|
this.init(35);
|
|
this.animate();
|
|
}
|
|
|
|
resize() {
|
|
this.canvas.width = window.innerWidth;
|
|
this.canvas.height = window.innerHeight;
|
|
}
|
|
|
|
init(count) {
|
|
const colors = ['255,107,53', '255,154,86', '255,193,7', '255,180,120'];
|
|
for (let i = 0; i < count; i++) {
|
|
this.particles.push({
|
|
x: Math.random() * this.canvas.width,
|
|
y: Math.random() * this.canvas.height,
|
|
radius: Math.random() * 2.5 + 1,
|
|
color: colors[Math.floor(Math.random() * colors.length)],
|
|
alpha: Math.random() * 0.25 + 0.05,
|
|
vx: (Math.random() - 0.5) * 0.3,
|
|
vy: -(Math.random() * 0.4 + 0.1),
|
|
pulse: Math.random() * Math.PI * 2
|
|
});
|
|
}
|
|
}
|
|
|
|
animate() {
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
for (const p of this.particles) {
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
p.pulse += 0.02;
|
|
|
|
const alpha = p.alpha * (0.7 + 0.3 * Math.sin(p.pulse));
|
|
|
|
if (p.y < -10) p.y = this.canvas.height + 10;
|
|
if (p.x < -10) p.x = this.canvas.width + 10;
|
|
if (p.x > this.canvas.width + 10) p.x = -10;
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
|
|
this.ctx.fillStyle = `rgba(${p.color},${alpha})`;
|
|
this.ctx.shadowBlur = 15;
|
|
this.ctx.shadowColor = `rgba(${p.color},${alpha * 0.8})`;
|
|
this.ctx.fill();
|
|
}
|
|
|
|
this.ctx.shadowBlur = 0;
|
|
requestAnimationFrame(() => this.animate());
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Mouse Parallax
|
|
// ============================================
|
|
|
|
function initParallax() {
|
|
const logo = document.querySelector('.logo');
|
|
const glow = document.querySelector('.ambient-glow');
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
const x = (e.clientX / window.innerWidth - 0.5) * 2;
|
|
const y = (e.clientY / window.innerHeight - 0.5) * 2;
|
|
|
|
logo.style.transform = `translate(${x * 5}px, ${y * 3}px)`;
|
|
glow.style.transform = `translate(calc(-50% + ${x * 15}px), calc(-60% + ${y * 10}px))`;
|
|
});
|
|
}
|
|
|
|
// ============================================
|
|
// Progress Enhancements
|
|
// ============================================
|
|
|
|
let displayedPercent = 0;
|
|
let percentAnimFrame = null;
|
|
|
|
function animatePercentage(target) {
|
|
const targetFloor = Math.floor(target);
|
|
if (displayedPercent === targetFloor) return;
|
|
|
|
if (percentAnimFrame) cancelAnimationFrame(percentAnimFrame);
|
|
|
|
const step = () => {
|
|
if (displayedPercent < targetFloor) {
|
|
displayedPercent++;
|
|
loadingPercent.textContent = displayedPercent + '%';
|
|
percentAnimFrame = requestAnimationFrame(step);
|
|
} else {
|
|
percentAnimFrame = null;
|
|
}
|
|
};
|
|
percentAnimFrame = requestAnimationFrame(step);
|
|
}
|
|
|
|
// ============================================
|
|
// Initialize
|
|
// ============================================
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initAudio();
|
|
initFiveMHandlers();
|
|
initParallax();
|
|
initRotatingContent();
|
|
initClock();
|
|
|
|
// Particle System
|
|
const particleCanvas = document.getElementById('particles');
|
|
if (particleCanvas) {
|
|
new ParticleSystem(particleCanvas);
|
|
}
|
|
|
|
// Volume slider fill
|
|
volumeSlider.style.setProperty('--volume-pct', volumeSlider.value + '%');
|
|
});
|
|
|
|
// ============================================
|
|
// Audio
|
|
// ============================================
|
|
|
|
function initAudio() {
|
|
backgroundMusic.volume = volumeSlider.value / 100;
|
|
|
|
if (!backgroundMusic.paused) {
|
|
isPlaying = true;
|
|
playIcon.classList.remove('fa-play');
|
|
playIcon.classList.add('fa-pause');
|
|
if (visualizer) visualizer.classList.remove('paused');
|
|
} else {
|
|
if (visualizer) visualizer.classList.add('paused');
|
|
backgroundMusic.play().then(() => {
|
|
isPlaying = true;
|
|
playIcon.classList.remove('fa-play');
|
|
playIcon.classList.add('fa-pause');
|
|
if (visualizer) visualizer.classList.remove('paused');
|
|
}).catch(() => {});
|
|
}
|
|
}
|
|
|
|
function loadTrack(index) {
|
|
if (index >= 0 && index < tracks.length) {
|
|
currentTrack = index;
|
|
backgroundMusic.src = tracks[currentTrack].src;
|
|
trackName.textContent = tracks[currentTrack].name;
|
|
if (isPlaying) {
|
|
backgroundMusic.play().catch(() => {});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Play/Pause Toggle
|
|
playBtn.addEventListener('click', () => {
|
|
if (isPlaying) {
|
|
pauseMusic();
|
|
} else {
|
|
playMusic();
|
|
}
|
|
});
|
|
|
|
function playMusic() {
|
|
backgroundMusic.play().then(() => {
|
|
isPlaying = true;
|
|
playIcon.classList.remove('fa-play');
|
|
playIcon.classList.add('fa-pause');
|
|
if (visualizer) visualizer.classList.remove('paused');
|
|
}).catch(error => {
|
|
console.log('Autoplay blocked:', error);
|
|
});
|
|
}
|
|
|
|
function pauseMusic() {
|
|
backgroundMusic.pause();
|
|
isPlaying = false;
|
|
playIcon.classList.remove('fa-pause');
|
|
playIcon.classList.add('fa-play');
|
|
if (visualizer) visualizer.classList.add('paused');
|
|
}
|
|
|
|
// Previous Track
|
|
prevBtn.addEventListener('click', () => {
|
|
let newIndex = currentTrack - 1;
|
|
if (newIndex < 0) newIndex = tracks.length - 1;
|
|
loadTrack(newIndex);
|
|
});
|
|
|
|
// Next Track
|
|
nextBtn.addEventListener('click', () => {
|
|
let newIndex = currentTrack + 1;
|
|
if (newIndex >= tracks.length) newIndex = 0;
|
|
loadTrack(newIndex);
|
|
});
|
|
|
|
// Track ended - play next
|
|
backgroundMusic.addEventListener('ended', () => {
|
|
let newIndex = currentTrack + 1;
|
|
if (newIndex >= tracks.length) newIndex = 0;
|
|
loadTrack(newIndex);
|
|
if (isPlaying) {
|
|
backgroundMusic.play().catch(() => {});
|
|
}
|
|
});
|
|
|
|
// Volume Control
|
|
volumeSlider.addEventListener('input', (e) => {
|
|
const volume = e.target.value;
|
|
backgroundMusic.volume = volume / 100;
|
|
lastVolume = volume;
|
|
updateVolumeIcon(volume);
|
|
e.target.style.setProperty('--volume-pct', volume + '%');
|
|
|
|
if (volume > 0 && isMuted) {
|
|
isMuted = false;
|
|
}
|
|
});
|
|
|
|
volumeBtn.addEventListener('click', () => {
|
|
if (isMuted) {
|
|
backgroundMusic.volume = lastVolume / 100;
|
|
volumeSlider.value = lastVolume;
|
|
isMuted = false;
|
|
updateVolumeIcon(lastVolume);
|
|
volumeSlider.style.setProperty('--volume-pct', lastVolume + '%');
|
|
} else {
|
|
lastVolume = volumeSlider.value;
|
|
backgroundMusic.volume = 0;
|
|
volumeSlider.value = 0;
|
|
isMuted = true;
|
|
updateVolumeIcon(0);
|
|
volumeSlider.style.setProperty('--volume-pct', '0%');
|
|
}
|
|
});
|
|
|
|
function updateVolumeIcon(volume) {
|
|
volumeIcon.classList.remove('fa-volume-up', 'fa-volume-down', 'fa-volume-mute', 'fa-volume-off');
|
|
|
|
if (volume == 0) {
|
|
volumeIcon.classList.add('fa-volume-mute');
|
|
} else if (volume < 30) {
|
|
volumeIcon.classList.add('fa-volume-off');
|
|
} else if (volume < 70) {
|
|
volumeIcon.classList.add('fa-volume-down');
|
|
} else {
|
|
volumeIcon.classList.add('fa-volume-up');
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// FiveM Loading Screen Handlers
|
|
// ============================================
|
|
|
|
function initFiveMHandlers() {
|
|
// FiveM sendet message events - nutze diese um Audio zu starten
|
|
window.addEventListener('message', () => {
|
|
if (!isPlaying) {
|
|
backgroundMusic.play().then(() => {
|
|
isPlaying = true;
|
|
playIcon.classList.remove('fa-play');
|
|
playIcon.classList.add('fa-pause');
|
|
if (visualizer) visualizer.classList.remove('paused');
|
|
}).catch(() => {});
|
|
}
|
|
}, { once: true });
|
|
|
|
// Handler fuer Ladefortschritt
|
|
const handlers = {
|
|
startInitFunction(data) {
|
|
loadingStatus.textContent = data.type === 'INIT_SESSION' ? 'Session wird initialisiert...' : 'Lade ' + (data.type || 'Daten') + '...';
|
|
},
|
|
|
|
startInitFunctionOrder() {},
|
|
|
|
initFunctionInvoking(data) {
|
|
loadingStatus.textContent = 'Lade ' + (data.name || 'Module') + '...';
|
|
},
|
|
|
|
initFunctionInvoked() {},
|
|
|
|
startDataFileEntries(data) {
|
|
loadingStatus.textContent = 'Lade Daten: ' + (data.count || 0) + ' Einträge...';
|
|
},
|
|
|
|
onDataFileEntry() {},
|
|
|
|
endDataFileEntries() {
|
|
loadingStatus.textContent = 'Daten geladen...';
|
|
},
|
|
|
|
performMapLoadFunction(data) {
|
|
loadingStatus.textContent = 'Lade Map: ' + (data.idx + 1) + '/' + data.count;
|
|
},
|
|
|
|
onLogLine() {}
|
|
};
|
|
|
|
window.addEventListener('message', (event) => {
|
|
const handler = handlers[event.data.eventName];
|
|
if (handler) {
|
|
handler(event.data);
|
|
}
|
|
});
|
|
|
|
// Ladefortschritt ueber loadProgress Event
|
|
let loadProgress = 0;
|
|
const updateProgress = (progress) => {
|
|
loadProgress = Math.min(Math.max(loadProgress, progress), 100);
|
|
progressFill.style.width = `${loadProgress}%`;
|
|
|
|
animatePercentage(loadProgress);
|
|
|
|
const messageIndex = Math.min(Math.floor(loadProgress / 10), loadingMessages.length - 1);
|
|
if (messageIndex >= 0) {
|
|
loadingStatus.textContent = loadingMessages[messageIndex];
|
|
}
|
|
|
|
if (loadProgress >= 100) {
|
|
loadingStatus.textContent = 'Bereit zum Spielen!';
|
|
document.querySelector('.loading-section').classList.add('complete');
|
|
}
|
|
};
|
|
|
|
// FiveM loadProgress Handler
|
|
window.addEventListener('message', (event) => {
|
|
if (event.data.eventName === 'loadProgress') {
|
|
updateProgress(event.data.loadFraction * 100);
|
|
}
|
|
});
|
|
|
|
// Player count handler
|
|
window.addEventListener('message', (event) => {
|
|
if (event.data.eventName === 'playerCount') {
|
|
const el = document.querySelector('.player-count');
|
|
if (el) {
|
|
el.textContent = `${event.data.count || 0}/${event.data.max || CONFIG.maxPlayers} Spieler Online`;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Expose for testing in browser console
|
|
window.updateProgress = updateProgress;
|
|
}
|
|
|
|
// ============================================
|
|
// Rotating Tips
|
|
// ============================================
|
|
|
|
function initRotatingContent() {
|
|
const changelogText = document.querySelector('.changelog-text');
|
|
let changelogIndex = 0;
|
|
|
|
// Set initial player count from config
|
|
const playerCount = document.querySelector('.player-count');
|
|
if (playerCount) {
|
|
playerCount.textContent = `0/${CONFIG.maxPlayers} Spieler Online`;
|
|
}
|
|
|
|
if (changelogText && CONFIG.changelogs.length > 0) {
|
|
setInterval(() => {
|
|
changelogText.classList.add('fade-out');
|
|
setTimeout(() => {
|
|
changelogIndex = (changelogIndex + 1) % CONFIG.changelogs.length;
|
|
changelogText.textContent = CONFIG.changelogs[changelogIndex];
|
|
changelogText.classList.remove('fade-out');
|
|
}, 400);
|
|
}, CONFIG.changelogInterval);
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Clock
|
|
// ============================================
|
|
|
|
function initClock() {
|
|
const clockEl = document.getElementById('clock');
|
|
if (!clockEl) return;
|
|
|
|
const update = () => {
|
|
const now = new Date();
|
|
const h = String(now.getHours()).padStart(2, '0');
|
|
const m = String(now.getMinutes()).padStart(2, '0');
|
|
clockEl.textContent = `${h}:${m}`;
|
|
};
|
|
|
|
update();
|
|
setInterval(update, 1000);
|
|
}
|
|
|
|
// ============================================
|
|
// URL Handler (FiveM compatible)
|
|
// ============================================
|
|
|
|
function openUrl(url) {
|
|
try {
|
|
if (typeof invokeNative === 'function') {
|
|
invokeNative('openUrl', url);
|
|
} else {
|
|
window.open(url, '_blank');
|
|
}
|
|
} catch (e) {
|
|
window.open(url, '_blank');
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Handle visibility change
|
|
// ============================================
|
|
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden && isPlaying) {
|
|
// Optional: pause when tab is hidden
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
// Keyboard shortcuts
|
|
// ============================================
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
switch(e.code) {
|
|
case 'Space':
|
|
e.preventDefault();
|
|
playBtn.click();
|
|
break;
|
|
case 'ArrowRight':
|
|
nextBtn.click();
|
|
break;
|
|
case 'ArrowLeft':
|
|
prevBtn.click();
|
|
break;
|
|
case 'ArrowUp':
|
|
e.preventDefault();
|
|
volumeSlider.value = Math.min(100, parseInt(volumeSlider.value) + 10);
|
|
volumeSlider.dispatchEvent(new Event('input'));
|
|
break;
|
|
case 'ArrowDown':
|
|
e.preventDefault();
|
|
volumeSlider.value = Math.max(0, parseInt(volumeSlider.value) - 10);
|
|
volumeSlider.dispatchEvent(new Event('input'));
|
|
break;
|
|
case 'KeyM':
|
|
volumeBtn.click();
|
|
break;
|
|
}
|
|
});
|