From a3ab70ec94300e8367323835170780afb5bba8c6 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 3 Aug 2025 17:26:20 +0200 Subject: [PATCH] ed --- resources/[tools]/nordi_dj/client/main.lua | 100 +- resources/[tools]/nordi_dj/html/index.html | 389 +++++- resources/[tools]/nordi_dj/html/script.js | 939 +++++++++++++ resources/[tools]/nordi_dj/html/style.css | 1454 +++++++++++++++++++- 4 files changed, 2773 insertions(+), 109 deletions(-) diff --git a/resources/[tools]/nordi_dj/client/main.lua b/resources/[tools]/nordi_dj/client/main.lua index 55069d83a..b40af42e4 100644 --- a/resources/[tools]/nordi_dj/client/main.lua +++ b/resources/[tools]/nordi_dj/client/main.lua @@ -75,70 +75,56 @@ function GetNearbyDJBooth() return nil end -function OpenDJMenu() - TriggerServerEvent('dj:server:getPlaylists') - - local options = { - { - title = 'YouTube Song abspielen', - description = 'Spiele einen Song von YouTube ab', - icon = 'fab fa-youtube', - onSelect = function() - OpenYouTubeMenu() - end - }, - { - title = 'Direkte URL abspielen', - description = 'Spiele einen Song von einer direkten URL ab', - icon = 'play', - onSelect = function() - OpenDirectUrlMenu() - end - }, - { - title = 'Musik stoppen', - description = 'Stoppe die aktuelle Musik', - icon = 'stop', - onSelect = function() - StopMusic() - end - }, - { - title = 'Lautstärke ändern', - description = 'Aktuelle Lautstärke: ' .. currentVolume .. '%', - icon = 'volume-up', - onSelect = function() - OpenVolumeMenu() - end - }, - { - title = 'Playlists verwalten', - description = 'Erstelle und verwalte Playlists', - icon = 'list', - onSelect = function() - OpenPlaylistMenu() - end - } - } - - if isPlaying and currentSong then - table.insert(options, 2, { - title = 'Aktueller Song', - description = currentSong.title, - icon = 'music', - disabled = true +-- Aktualisierte Client-Funktionen für das neue UI +function OpenDJInterface() + if not isDJBooth then + lib.notify({ + title = 'DJ System', + description = 'Du musst an einem DJ Pult stehen', + type = 'error' }) + return end - lib.registerContext({ - id = 'dj_main_menu', - title = 'DJ System - ' .. currentDJBooth.name, - options = options + SetNuiFocus(true, true) + SendNUIMessage({ + type = 'showDJInterface' }) - lib.showContext('dj_main_menu') + isUIOpen = true + + -- Disable controls while UI is open + CreateThread(function() + while isUIOpen do + DisableAllControlActions(0) + EnableControlAction(0, 1, true) -- Mouse look + EnableControlAction(0, 2, true) -- Mouse look + Wait(0) + end + end) end +-- NUI Callbacks für das neue Interface +RegisterNUICallback('djInterfaceClosed', function(data, cb) + SetNuiFocus(false, false) + isUIOpen = false + cb('ok') +end) + +RegisterNUICallback('deckStateChanged', function(data, cb) + print(string.format('[DJ System] Deck %s %s: %s', + data.deck, + data.isPlaying and 'playing' or 'stopped', + data.track and data.track.title or 'No track' + )) + + -- Hier könntest du zusätzliche Logik hinzufügen + -- z.B. Synchronisation mit anderen Spielern + + cb('ok') +end) + + function OpenYouTubeMenu() local input = lib.inputDialog('YouTube Song abspielen', { {type = 'input', label = 'Song Titel', placeholder = 'z.B. Daft Punk - One More Time'}, diff --git a/resources/[tools]/nordi_dj/html/index.html b/resources/[tools]/nordi_dj/html/index.html index 7f3fa6d63..6e7538855 100644 --- a/resources/[tools]/nordi_dj/html/index.html +++ b/resources/[tools]/nordi_dj/html/index.html @@ -3,23 +3,386 @@ - DJ System - YouTube Streaming + DJ System - Professional Interface + + -
- - - - - + + - + + + + + + + + diff --git a/resources/[tools]/nordi_dj/html/script.js b/resources/[tools]/nordi_dj/html/script.js index 9bc2164f9..888e6eadc 100644 --- a/resources/[tools]/nordi_dj/html/script.js +++ b/resources/[tools]/nordi_dj/html/script.js @@ -377,3 +377,942 @@ window.djDebug = { }; console.log('DJ System: YouTube streaming system loaded'); + +// DJ System - Professional Interface +let djInterface = { + decks: { + A: { + track: null, + isPlaying: false, + volume: 75, + pitch: 0, + cuePoint: 0, + player: null + }, + B: { + track: null, + isPlaying: false, + volume: 75, + pitch: 0, + cuePoint: 0, + player: null + } + }, + mixer: { + crossfader: 50, + masterVolume: 80, + eq: { + A: { high: 0, mid: 0, low: 0 }, + B: { high: 0, mid: 0, low: 0 } + } + }, + effects: { + reverb: false, + delay: false, + filter: false, + flanger: false, + wetDry: 0 + }, + currentDeck: null, + isRecording: false +}; + +// Initialize DJ Interface +document.addEventListener('DOMContentLoaded', function() { + initializeDJSystem(); + setupEventListeners(); + startAnimations(); + loadYouTubeAPI(); +}); + +function initializeDJSystem() { + console.log('DJ System: Initializing professional interface...'); + + // Initialize audio players + djInterface.decks.A.player = document.getElementById('audio-player-a'); + djInterface.decks.B.player = document.getElementById('audio-player-b'); + + // Setup audio event listeners + setupAudioEventListeners(); + + // Initialize waveforms + initializeWaveforms(); + + // Start time display + updateTimeDisplay(); + setInterval(updateTimeDisplay, 1000); + + // Start VU meters animation + startVUMeters(); + + console.log('DJ System: Professional interface ready!'); +} + +function setupEventListeners() { + // Knob interactions + setupKnobControls(); + + // Keyboard shortcuts + document.addEventListener('keydown', handleKeyboardShortcuts); + + // Window resize + window.addEventListener('resize', handleResize); +} + +function setupKnobControls() { + const knobs = document.querySelectorAll('.knob'); + + knobs.forEach(knob => { + let isDragging = false; + let startY = 0; + let startRotation = 0; + + knob.addEventListener('mousedown', (e) => { + isDragging = true; + startY = e.clientY; + startRotation = getCurrentRotation(knob.querySelector('.knob-indicator')); + document.body.style.cursor = 'grabbing'; + e.preventDefault(); + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + + const deltaY = startY - e.clientY; + const rotation = Math.max(-150, Math.min(150, startRotation + deltaY * 2)); + + const indicator = knob.querySelector('.knob-indicator'); + indicator.style.transform = `translateX(-50%) rotate(${rotation}deg)`; + + // Update EQ values + const channel = knob.dataset.channel; + const eqType = knob.dataset.eq; + const effect = knob.dataset.effect; + + if (channel && eqType) { + const value = Math.round((rotation / 150) * 100); + djInterface.mixer.eq[channel][eqType] = value; + + const valueSpan = knob.parentElement.querySelector('.eq-value'); + if (valueSpan) { + valueSpan.textContent = value > 0 ? `+${value}` : value; + } + + applyEQ(channel, eqType, value); + } + + if (effect) { + const value = Math.round(((rotation + 150) / 300) * 100); + djInterface.effects[effect] = value; + applyEffect(effect, value); + } + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + document.body.style.cursor = 'default'; + }); + }); +} + +function getCurrentRotation(element) { + const transform = window.getComputedStyle(element).transform; + if (transform === 'none') return 0; + + const matrix = transform.match(/matrix\(([^)]+)\)/); + if (!matrix) return 0; + + const values = matrix[1].split(',').map(parseFloat); + const angle = Math.atan2(values[1], values[0]) * (180 / Math.PI); + return angle; +} + +// Deck Functions +function loadTrackToDeck(deck) { + djInterface.currentDeck = deck; + document.getElementById('track-loader').classList.remove('hidden'); +} + +function confirmLoadTrack() { + const title = document.getElementById('track-title').value; + const artist = document.getElementById('track-artist').value; + const url = document.getElementById('track-url').value; + + if (!title || !url) { + showNotification('Please fill in title and URL', 'error'); + return; + } + + const deck = djInterface.currentDeck; + const trackData = { title, artist, url }; + + loadTrackToPlayer(deck, trackData); + closeTrackLoader(); + + showNotification(`Track loaded to Deck ${deck}: ${title}`, 'success'); +} + +function loadTrackToPlayer(deck, trackData) { + const deckData = djInterface.decks[deck]; + deckData.track = trackData; + + // Update UI + document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = trackData.title.substring(0, 15); + document.getElementById(`title-${deck.toLowerCase()}`).textContent = trackData.title; + document.getElementById(`artist-${deck.toLowerCase()}`).textContent = trackData.artist || 'Unknown Artist'; + + // Load audio + if (isYouTubeUrl(trackData.url)) { + loadYouTubeTrack(deck, trackData); + } else { + loadDirectTrack(deck, trackData); + } + + // Generate waveform + generateWaveform(deck, trackData.url); +} + +function loadYouTubeTrack(deck, trackData) { + const videoId = extractYouTubeVideoId(trackData.url); + if (!videoId) { + showNotification('Invalid YouTube URL', 'error'); + return; + } + + // Create hidden YouTube player for this deck + createYouTubePlayerForDeck(deck, videoId); +} + +function loadDirectTrack(deck, trackData) { + const player = djInterface.decks[deck].player; + player.src = trackData.url; + player.load(); +} + +function togglePlay(deck) { + const deckData = djInterface.decks[deck]; + const playBtn = document.getElementById(`play-${deck.toLowerCase()}`); + const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`); + + if (!deckData.track) { + showNotification(`No track loaded in Deck ${deck}`, 'warning'); + return; + } + + if (deckData.isPlaying) { + // Pause + pauseTrack(deck); + playBtn.innerHTML = ''; + playBtn.classList.remove('playing'); + vinyl.classList.remove('spinning'); + deckData.isPlaying = false; + } else { + // Play + playTrack(deck); + playBtn.innerHTML = ''; + playBtn.classList.add('playing'); + vinyl.classList.add('spinning'); + deckData.isPlaying = true; + } + + // Send to FiveM + notifyFiveM('deckStateChanged', { + deck: deck, + isPlaying: deckData.isPlaying, + track: deckData.track + }); +} + +function playTrack(deck) { + const deckData = djInterface.decks[deck]; + + if (deckData.youtubePlayer) { + deckData.youtubePlayer.playVideo(); + } else if (deckData.player) { + deckData.player.play(); + } +} + +function pauseTrack(deck) { + const deckData = djInterface.decks[deck]; + + if (deckData.youtubePlayer) { + deckData.youtubePlayer.pauseVideo(); + } else if (deckData.player) { + deckData.player.pause(); + } +} + +function cue(deck) { + const deckData = djInterface.decks[deck]; + + if (!deckData.track) { + showNotification(`No track loaded in Deck ${deck}`, 'warning'); + return; + } + + // Set cue point to current position or go to cue point + if (deckData.isPlaying) { + deckData.cuePoint = getCurrentTime(deck); + showNotification(`Cue point set in Deck ${deck}`, 'info'); + } else { + seekTo(deck, deckData.cuePoint); + showNotification(`Jumped to cue point in Deck ${deck}`, 'info'); + } +} + +function adjustPitch(deck, value) { + const deckData = djInterface.decks[deck]; + deckData.pitch = parseFloat(value); + + document.getElementById(`pitch-value-${deck.toLowerCase()}`).textContent = `${value}%`; + + // Apply pitch change (this would need audio processing) + applyPitchChange(deck, value); +} + +function adjustVolume(deck, value) { + const deckData = djInterface.decks[deck]; + deckData.volume = parseInt(value); + + if (deckData.youtubePlayer) { + deckData.youtubePlayer.setVolume(value); + } else if (deckData.player) { + deckData.player.volume = value / 100; + } + + updateVUMeter(deck, value); +} + +// Crossfader (Fortsetzung) +function adjustCrossfader(value) { + djInterface.mixer.crossfader = parseInt(value); + + const volumeA = value <= 50 ? 100 : (100 - (value - 50) * 2); + const volumeB = value >= 50 ? 100 : (value * 2); + + // Apply crossfader mixing + applyCrossfaderMix(volumeA, volumeB); + + // Visual feedback + const crossfader = document.getElementById('crossfader'); + crossfader.style.background = `linear-gradient(90deg, + rgba(78, 205, 196, ${volumeA/100}) 0%, + rgba(255, 107, 107, 0.5) 50%, + rgba(78, 205, 196, ${volumeB/100}) 100%)`; +} + +function applyCrossfaderMix(volumeA, volumeB) { + const deckA = djInterface.decks.A; + const deckB = djInterface.decks.B; + + const finalVolumeA = (deckA.volume * volumeA / 100) / 100; + const finalVolumeB = (deckB.volume * volumeB / 100) / 100; + + if (deckA.youtubePlayer) { + deckA.youtubePlayer.setVolume(finalVolumeA * 100); + } else if (deckA.player) { + deckA.player.volume = finalVolumeA; + } + + if (deckB.youtubePlayer) { + deckB.youtubePlayer.setVolume(finalVolumeB * 100); + } else if (deckB.player) { + deckB.player.volume = finalVolumeB; + } +} + +function adjustMasterVolume(value) { + djInterface.mixer.masterVolume = parseInt(value); + + // Apply master volume to both decks + const masterMultiplier = value / 100; + + Object.keys(djInterface.decks).forEach(deck => { + const deckData = djInterface.decks[deck]; + const finalVolume = (deckData.volume * masterMultiplier) / 100; + + if (deckData.youtubePlayer) { + deckData.youtubePlayer.setVolume(finalVolume * 100); + } else if (deckData.player) { + deckData.player.volume = finalVolume; + } + }); + + updateMasterVU(value); +} + +function setCrossfaderCurve(type) { + document.querySelectorAll('.curve-btn').forEach(btn => btn.classList.remove('active')); + event.target.classList.add('active'); + + djInterface.mixer.crossfaderCurve = type; + showNotification(`Crossfader curve set to ${type}`, 'info'); +} + +// EQ Functions +function applyEQ(channel, eqType, value) { + const deckData = djInterface.decks[channel]; + + if (!deckData.audioContext) { + setupAudioContext(channel); + } + + // Apply EQ filter (simplified) + console.log(`Applying ${eqType} EQ: ${value}% to Deck ${channel}`); + + // Visual feedback + const knob = document.querySelector(`[data-channel="${channel}"][data-eq="${eqType}"] .knob-indicator`); + if (knob) { + const hue = value > 0 ? 120 : value < 0 ? 0 : 200; + knob.style.boxShadow = `0 0 8px hsl(${hue}, 70%, 50%)`; + } +} + +// Effects Functions +function toggleEffect(effectType) { + const isActive = djInterface.effects[effectType]; + djInterface.effects[effectType] = !isActive; + + const btn = event.target; + btn.classList.toggle('active'); + + if (djInterface.effects[effectType]) { + applyEffect(effectType, 50); + showNotification(`${effectType.toUpperCase()} ON`, 'success'); + } else { + removeEffect(effectType); + showNotification(`${effectType.toUpperCase()} OFF`, 'info'); + } +} + +function applyEffect(effectType, value) { + console.log(`Applying ${effectType} effect: ${value}%`); + + // Effect processing would go here + switch(effectType) { + case 'reverb': + applyReverb(value); + break; + case 'delay': + applyDelay(value); + break; + case 'filter': + applyFilter(value); + break; + case 'flanger': + applyFlanger(value); + break; + } +} + +function removeEffect(effectType) { + console.log(`Removing ${effectType} effect`); + // Remove effect processing +} + +// Waveform Functions +function initializeWaveforms() { + const canvasA = document.getElementById('waveform-a'); + const canvasB = document.getElementById('waveform-b'); + + if (canvasA && canvasB) { + drawEmptyWaveform(canvasA); + drawEmptyWaveform(canvasB); + } +} + +function drawEmptyWaveform(canvas) { + const ctx = canvas.getContext('2d'); + const width = canvas.width; + const height = canvas.height; + + ctx.clearRect(0, 0, width, height); + + // Draw center line + ctx.strokeStyle = 'rgba(78, 205, 196, 0.3)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(0, height / 2); + ctx.lineTo(width, height / 2); + ctx.stroke(); + + // Draw grid + ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; + for (let i = 0; i < width; i += 20) { + ctx.beginPath(); + ctx.moveTo(i, 0); + ctx.lineTo(i, height); + ctx.stroke(); + } +} + +function generateWaveform(deck, url) { + const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`); + const ctx = canvas.getContext('2d'); + + // Simulate waveform generation + drawSimulatedWaveform(ctx, canvas.width, canvas.height); +} + +function drawSimulatedWaveform(ctx, width, height) { + ctx.clearRect(0, 0, width, height); + + const centerY = height / 2; + const gradient = ctx.createLinearGradient(0, 0, 0, height); + gradient.addColorStop(0, 'rgba(255, 107, 107, 0.8)'); + gradient.addColorStop(0.5, 'rgba(78, 205, 196, 0.8)'); + gradient.addColorStop(1, 'rgba(255, 107, 107, 0.8)'); + + ctx.fillStyle = gradient; + + for (let x = 0; x < width; x += 2) { + const amplitude = Math.random() * (height / 2) * (0.3 + Math.sin(x * 0.01) * 0.7); + ctx.fillRect(x, centerY - amplitude, 2, amplitude * 2); + } +} + +// VU Meters +function startVUMeters() { + setInterval(() => { + updateVUMeter('A', djInterface.decks.A.isPlaying ? Math.random() * 100 : 0); + updateVUMeter('B', djInterface.decks.B.isPlaying ? Math.random() * 100 : 0); + updateMasterVU(djInterface.mixer.masterVolume); + }, 100); +} + +function updateVUMeter(deck, level) { + const vuBar = document.getElementById(`vu-${deck.toLowerCase()}`); + if (vuBar) { + vuBar.style.height = `${level}%`; + + // Color based on level + if (level > 80) { + vuBar.style.background = 'linear-gradient(180deg, #ff0000, #ff6b6b)'; + } else if (level > 60) { + vuBar.style.background = 'linear-gradient(180deg, #feca57, #ff6b6b)'; + } else { + vuBar.style.background = 'linear-gradient(180deg, #4ecdc4, #feca57)'; + } + } +} + +function updateMasterVU(level) { + const masterVU = document.getElementById('master-vu'); + if (masterVU) { + masterVU.style.height = `${level}%`; + } +} + +// Time and BPM +function updateTimeDisplay() { + const now = new Date(); + const timeString = now.toLocaleTimeString('de-DE', { + hour: '2-digit', + minute: '2-digit' + }); + + const timeDisplay = document.getElementById('current-time'); + if (timeDisplay) { + timeDisplay.textContent = timeString; + } + + // Update track times + updateTrackTimes(); + + // Update BPM (simulated) + updateBPMDisplay(); +} + +function updateTrackTimes() { + ['A', 'B'].forEach(deck => { + const deckData = djInterface.decks[deck]; + if (deckData.track && deckData.isPlaying) { + const currentTime = getCurrentTime(deck); + const duration = getDuration(deck); + + document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = + formatTime(currentTime); + document.getElementById(`time-total-${deck.toLowerCase()}`).textContent = + formatTime(duration); + } + }); +} + +function getCurrentTime(deck) { + const deckData = djInterface.decks[deck]; + + if (deckData.youtubePlayer) { + return deckData.youtubePlayer.getCurrentTime() || 0; + } else if (deckData.player) { + return deckData.player.currentTime || 0; + } + + return 0; +} + +function getDuration(deck) { + const deckData = djInterface.decks[deck]; + + if (deckData.youtubePlayer) { + return deckData.youtubePlayer.getDuration() || 0; + } else if (deckData.player) { + return deckData.player.duration || 0; + } + + return 0; +} + +function formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; +} + +function updateBPMDisplay() { + // Simulate BPM detection + const bpm = 120 + Math.floor(Math.random() * 40); + const bpmDisplay = document.getElementById('bpm-display'); + if (bpmDisplay) { + bpmDisplay.textContent = bpm; + } +} + +// Recording Functions +function toggleRecording() { + djInterface.isRecording = !djInterface.isRecording; + + const recordBtn = document.querySelector('.record-btn'); + const recordTime = document.querySelector('.recording-time'); + + if (djInterface.isRecording) { + recordBtn.classList.add('recording'); + recordBtn.innerHTML = 'STOP'; + startRecordingTimer(); + showNotification('Recording started', 'success'); + } else { + recordBtn.classList.remove('recording'); + recordBtn.innerHTML = 'REC'; + stopRecordingTimer(); + showNotification('Recording stopped', 'info'); + } +} + +let recordingStartTime = 0; +let recordingTimer = null; + +function startRecordingTimer() { + recordingStartTime = Date.now(); + recordingTimer = setInterval(() => { + const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000); + const recordTime = document.querySelector('.recording-time'); + if (recordTime) { + recordTime.textContent = formatTime(elapsed); + } + }, 1000); +} + +function stopRecordingTimer() { + if (recordingTimer) { + clearInterval(recordingTimer); + recordingTimer = null; + } + + const recordTime = document.querySelector('.recording-time'); + if (recordTime) { + recordTime.textContent = '00:00'; + } +} + +// Search and Library Functions +function searchTracks(query) { + console.log('Searching tracks:', query); + + // Simulate track search + const mockTracks = [ + { title: 'Summer Vibes', artist: 'DJ Cool', url: 'https://example.com/track1' }, + { title: 'Night Drive', artist: 'Electronic Dreams', url: 'https://example.com/track2' }, + { title: 'Bass Drop', artist: 'Heavy Beats', url: 'https://example.com/track3' }, + { title: 'Chill Out', artist: 'Ambient Sounds', url: 'https://example.com/track4' } + ]; + + const filteredTracks = mockTracks.filter(track => + track.title.toLowerCase().includes(query.toLowerCase()) || + track.artist.toLowerCase().includes(query.toLowerCase()) + ); + + displayTrackList(filteredTracks); +} + +function displayTrackList(tracks) { + const trackList = document.getElementById('track-list'); + trackList.innerHTML = ''; + + tracks.forEach(track => { + const trackItem = document.createElement('div'); + trackItem.className = 'track-item'; + trackItem.innerHTML = ` +
${track.title}
+
${track.artist}
+ `; + + trackItem.addEventListener('dblclick', () => { + if (djInterface.currentDeck) { + loadTrackToPlayer(djInterface.currentDeck, track); + showNotification(`${track.title} loaded to Deck ${djInterface.currentDeck}`, 'success'); + } + }); + + trackList.appendChild(trackItem); + }); +} + +// Keyboard Shortcuts +function handleKeyboardShortcuts(event) { + if (event.target.tagName === 'INPUT') return; + + switch(event.code) { + case 'Space': + event.preventDefault(); + if (djInterface.currentDeck) { + togglePlay(djInterface.currentDeck); + } + break; + case 'KeyQ': + togglePlay('A'); + break; + case 'KeyW': + togglePlay('B'); + break; + case 'KeyA': + cue('A'); + break; + case 'KeyS': + cue('B'); + break; + case 'KeyZ': + adjustCrossfader(0); // Full A + break; + case 'KeyX': + adjustCrossfader(50); // Center + break; + case 'KeyC': + adjustCrossfader(100); // Full B + break; + case 'KeyR': + toggleRecording(); + break; + } +} + +// Animations +function startAnimations() { + // Vinyl spinning animation is handled by CSS + + // Playhead animation + setInterval(() => { + ['A', 'B'].forEach(deck => { + const deckData = djInterface.decks[deck]; + if (deckData.isPlaying && deckData.track) { + updatePlayhead(deck); + } + }); + }, 100); +} + +function updatePlayhead(deck) { + const playhead = document.getElementById(`playhead-${deck.toLowerCase()}`); + const currentTime = getCurrentTime(deck); + const duration = getDuration(deck); + + if (duration > 0) { + const progress = (currentTime / duration) * 100; + playhead.style.left = `${progress}%`; + } +} + +// Modal Functions +function closeTrackLoader() { + document.getElementById('track-loader').classList.add('hidden'); + + // Clear form + document.getElementById('track-title').value = ''; + document.getElementById('track-artist').value = ''; + document.getElementById('track-url').value = ''; +} + +function ejectDeck(deck) { + const deckData = djInterface.decks[deck]; + + if (deckData.isPlaying) { + togglePlay(deck); + } + + // Clear deck + deckData.track = null; + deckData.cuePoint = 0; + + // Clear UI + document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = 'NO TRACK'; + document.getElementById(`title-${deck.toLowerCase()}`).textContent = 'No Track Loaded'; + document.getElementById(`artist-${deck.toLowerCase()}`).textContent = '-'; + document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = '00:00'; + document.getElementById(`time-total-${deck.toLowerCase()}`).textContent = '00:00'; + + // Clear waveform + const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`); + drawEmptyWaveform(canvas); + + showNotification(`Deck ${deck} ejected`, 'info'); +} + +// Interface Control +function closeDJInterface() { + document.getElementById('dj-interface').classList.add('hidden'); + + // Stop all playback + ['A', 'B'].forEach(deck => { + if (djInterface.decks[deck].isPlaying) { + togglePlay(deck); + } + }); + + // Notify FiveM + notifyFiveM('djInterfaceClosed', {}); +} + +function showDJInterface() { + document.getElementById('dj-interface').classList.remove('hidden'); +} + +// Utility Functions +function showNotification(message, type = 'info') { + console.log(`DJ System [${type.toUpperCase()}]: ${message}`); + + // Create notification element + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + notification.style.cssText = ` + position: fixed; + top: 80px; + right: 20px; + padding: 15px 20px; + border-radius: 10px; + color: white; + font-weight: 600; + z-index: 10000; + transform: translateX(100%); + transition: transform 0.3s ease; + background: ${type === 'error' ? '#ff6b6b' : type === 'success' ? '#4ecdc4' : type === 'warning' ? '#feca57' : '#45b7d1'}; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); + `; + + document.body.appendChild(notification); + + // Animate in + setTimeout(() => { + notification.style.transform = 'translateX(0)'; + }, 100); + + // Remove after 3 seconds + setTimeout(() => { + notification.style.transform = 'translateX(100%)'; + setTimeout(() => { + document.body.removeChild(notification); + }, 300); + }, 3000); +} + +function handleResize() { + // Handle responsive design + const djInterface = document.getElementById('dj-interface'); + if (window.innerWidth < 1200) { + djInterface.classList.add('mobile-layout'); + } else { + djInterface.classList.remove('mobile-layout'); + } +} + +// YouTube Integration (from previous code) +function isYouTubeUrl(url) { + const youtubePatterns = [ + /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, + /youtube\.com\/watch\?.*v=([^&\n?#]+)/ + ]; + + return youtubePatterns.some(pattern => pattern.test(url)); +} + +function extractYouTubeVideoId(url) { + const patterns = [ + /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, + /youtube\.com\/watch\?.*v=([^&\n?#]+)/ + ]; + + for (let pattern of patterns) { + const match = url.match(pattern); + if (match && match[1]) { + return match[1]; + } + } + + return null; +} + +// FiveM Integration +function notifyFiveM(event, data) { + fetch(`https://${GetParentResourceName()}/${event}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }).catch(err => { + console.error('DJ System: Failed to notify FiveM:', err); + }); +} + +function GetParentResourceName() { + return window.location.hostname; +} + +// Message Handler for FiveM +window.addEventListener('message', function(event) { + const data = event.data; + + switch(data.type) { + case 'showDJInterface': + showDJInterface(); + break; + case 'hideDJInterface': + closeDJInterface(); + break; + case 'loadTrack': + if (data.deck && data.track) { + loadTrackToPlayer(data.deck, data.track); + } + break; + case 'setVolume': + if (data.deck && data.volume !== undefined) { + adjustVolume(data.deck, data.volume); + } + break; + } +}); + +// Initialize search with default tracks +document.addEventListener('DOMContentLoaded', function() { + setTimeout(() => { + searchTracks(''); // Load default tracks + }, 1000); +}); + +console.log('DJ System: Professional interface loaded! 🎛️'); +console.log('Keyboard shortcuts:'); +console.log('Q/W - Play/Pause Deck A/B'); +console.log('A/S - Cue Deck A/B'); +console.log('Z/X/C - Crossfader positions'); +console.log('R - Toggle recording'); +console.log('Space - Play/Pause current deck'); + diff --git a/resources/[tools]/nordi_dj/html/style.css b/resources/[tools]/nordi_dj/html/style.css index fbc090dd9..361085036 100644 --- a/resources/[tools]/nordi_dj/html/style.css +++ b/resources/[tools]/nordi_dj/html/style.css @@ -1,3 +1,4 @@ +/* DJ System Professional Styling */ * { margin: 0; padding: 0; @@ -5,68 +6,1443 @@ } body { - background: transparent; - font-family: 'Arial', sans-serif; + font-family: 'Rajdhani', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); + color: #ffffff; overflow: hidden; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; } -#music-player { - position: absolute; - top: -9999px; - left: -9999px; - width: 1px; - height: 1px; - opacity: 0; - pointer-events: none; +.hidden { + display: none !important; } -#audio-player { - width: 100%; - height: auto; +/* DJ Interface Container */ +#dj-interface { + width: 100vw; + height: 100vh; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%); + background: -webkit-linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%); + background: -moz-linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%); + position: relative; } -/* Optional progress bar styles */ -#progress-container { - width: 300px; - height: 4px; +/* Header */ +.dj-header { + height: 60px; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57); + background: -webkit-linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57); + background: -moz-linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57); + background-size: 300% 300%; + animation: gradientShift 3s ease infinite; + -webkit-animation: gradientShift 3s ease infinite; + -moz-animation: gradientShift 3s ease infinite; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: space-between; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + padding: 0 20px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); +} + +@keyframes gradientShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +@-webkit-keyframes gradientShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +@-moz-keyframes gradientShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.logo { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 10px; + font-family: 'Orbitron', 'Arial Black', Gadget, sans-serif; + font-weight: 900; + font-size: 24px; + text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); +} + +.status-bar { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + gap: 20px; +} + +.status-item { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 5px; + font-weight: 600; +} + +.close-btn { + width: 40px; + height: 40px; + border-radius: 50%; background: rgba(255, 255, 255, 0.2); + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.close-btn:hover { + background: rgba(255, 0, 0, 0.7); + transform: scale(1.1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); +} + +/* Main DJ Console */ +.dj-console { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + padding: 20px; + gap: 20px; + min-height: 0; +} + +/* Decks */ +.deck { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + background: linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -moz-linear-gradient(145deg, #1e1e2e, #2a2a3e); + border-radius: 20px; + padding: 20px; + box-shadow: + inset 5px 5px 15px rgba(0, 0, 0, 0.5), + inset -5px -5px 15px rgba(255, 255, 255, 0.1), + 0 10px 30px rgba(0, 0, 0, 0.3); + border: 2px solid rgba(255, 255, 255, 0.1); +} + +.deck-header { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + justify-content: space-between; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); +} + +.deck-header h3 { + font-family: 'Orbitron', 'Arial Black', Gadget, sans-serif; + font-size: 18px; + color: #4ecdc4; + text-shadow: 0 0 10px rgba(78, 205, 196, 0.5); +} + +.deck-controls { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + gap: 10px; +} + +.btn-deck { + width: 40px; + height: 40px; + border: none; + border-radius: 10px; + background: linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -moz-linear-gradient(145deg, #3a3a4e, #2a2a3e); + color: #ffffff; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + box-shadow: + 5px 5px 10px rgba(0, 0, 0, 0.3), + -5px -5px 10px rgba(255, 255, 255, 0.1); +} + +.btn-deck:hover { + transform: translateY(-2px); + -webkit-transform: translateY(-2px); + -moz-transform: translateY(-2px); + box-shadow: + 7px 7px 15px rgba(0, 0, 0, 0.4), + -7px -7px 15px rgba(255, 255, 255, 0.15); +} + +.btn-deck:active { + transform: translateY(0); + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + box-shadow: + inset 3px 3px 8px rgba(0, 0, 0, 0.3), + inset -3px -3px 8px rgba(255, 255, 255, 0.1); +} + +/* Vinyl Plattenspieler */ +.turntable { + margin-bottom: 20px; +} + +.vinyl-container { + position: relative; + width: 200px; + height: 200px; + margin: 0 auto 20px; +} + +.vinyl { + width: 200px; + height: 200px; + border-radius: 50%; + background: radial-gradient(circle, #1a1a1a 30%, #000000 70%); + background: -webkit-radial-gradient(circle, #1a1a1a 30%, #000000 70%); + background: -moz-radial-gradient(circle, #1a1a1a 30%, #000000 70%); + position: relative; + transition: transform 0.1s ease; + -webkit-transition: -webkit-transform 0.1s ease; + -moz-transition: -moz-transform 0.1s ease; + box-shadow: + 0 0 30px rgba(0, 0, 0, 0.8), + inset 0 0 20px rgba(255, 255, 255, 0.1); +} + +.vinyl.spinning { + animation: spin 2s linear infinite; + -webkit-animation: spin 2s linear infinite; + -moz-animation: spin 2s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@-webkit-keyframes spin { + from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } +} + +@-moz-keyframes spin { + from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(360deg); } +} + +.vinyl-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + width: 80px; + height: 80px; + border-radius: 50%; + background: linear-gradient(145deg, #ff6b6b, #4ecdc4); + background: -webkit-linear-gradient(145deg, #ff6b6b, #4ecdc4); + background: -moz-linear-gradient(145deg, #ff6b6b, #4ecdc4); + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + box-shadow: 0 0 20px rgba(255, 107, 107, 0.5); +} + +.vinyl-hole { + width: 20px; + height: 20px; + border-radius: 50%; + background: #000000; + position: absolute; +} + +.vinyl-label { + position: absolute; + width: 70px; + height: 70px; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + text-align: center; + font-size: 10px; + font-weight: 600; + color: #ffffff; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.8); +} + +.vinyl-grooves { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; +} + +.groove { + position: absolute; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 50%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); +} + +.groove:nth-child(1) { width: 90%; height: 90%; } +.groove:nth-child(2) { width: 75%; height: 75%; } +.groove:nth-child(3) { width: 60%; height: 60%; } +.groove:nth-child(4) { width: 45%; height: 45%; } +.groove:nth-child(5) { width: 30%; height: 30%; } + +/* Tonearm */ +.tonearm { + position: absolute; + top: 20px; + right: 20px; + width: 80px; + height: 80px; +} + +.tonearm-base { + width: 20px; + height: 20px; + border-radius: 50%; + background: linear-gradient(145deg, #c0c0c0, #808080); + background: -webkit-linear-gradient(145deg, #c0c0c0, #808080); + background: -moz-linear-gradient(145deg, #c0c0c0, #808080); + position: absolute; + top: 0; + right: 0; +} + +.tonearm-arm { + width: 60px; + height: 3px; + background: linear-gradient(90deg, #c0c0c0, #808080); + background: -webkit-linear-gradient(90deg, #c0c0c0, #808080); + background: -moz-linear-gradient(90deg, #c0c0c0, #808080); + position: absolute; + top: 8px; + right: 10px; + transform-origin: right center; + -webkit-transform-origin: right center; + -moz-transform-origin: right center; + transform: rotate(-30deg); + -webkit-transform: rotate(-30deg); + -moz-transform: rotate(-30deg); border-radius: 2px; +} + +.tonearm-needle { + width: 8px; + height: 8px; + background: #ffd700; /* Gold */ + border-radius: 50%; + position: absolute; + top: 6px; + right: 65px; + box-shadow: 0 0 5px rgba(255, 215, 0, 0.8); +} + +/* Turntable Controls */ +.turntable-controls { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: space-between; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + gap: 15px; +} + +.play-btn { + width: 60px; + height: 60px; + border: none; + border-radius: 50%; + background: linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -webkit-linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -moz-linear-gradient(145deg, #4ecdc4, #45b7d1); + color: #ffffff; + font-size: 24px; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + box-shadow: + 0 8px 20px rgba(78, 205, 196, 0.4), + inset 0 0 20px rgba(255, 255, 255, 0.2); +} + +.play-btn:hover { + transform: scale(1.1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + box-shadow: + 0 12px 30px rgba(78, 205, 196, 0.6), + inset 0 0 25px rgba(255, 255, 255, 0.3); +} + +.play-btn.playing { + background: linear-gradient(145deg, #ff6b6b, #ff5722); + background: -webkit-linear-gradient(145deg, #ff6b6b, #ff5722); + background: -moz-linear-gradient(145deg, #ff6b6b, #ff5722); + box-shadow: + 0 8px 20px rgba(255, 107, 107, 0.4), + inset 0 0 20px rgba(255, 255, 255, 0.2); +} + +.cue-btn { + padding: 10px 20px; + border: none; + border-radius: 10px; + background: linear-gradient(145deg, #feca57, #ff9ff3); + background: -webkit-linear-gradient(145deg, #feca57, #ff9ff3); + background: -moz-linear-gradient(145deg, #feca57, #ff9ff3); + color: #000000; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.cue-btn:hover { + transform: translateY(-2px); + -webkit-transform: translateY(-2px); + -moz-transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(254, 202, 87, 0.4); +} + +.pitch-slider { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 5px; +} + +.pitch-slider label { + font-size: 12px; + font-weight: 600; + color: #4ecdc4; +} + +.pitch-slider input[type="range"] { + width: 80px; + height: 5px; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4); + background: -webkit-linear-gradient(90deg, #ff6b6b, #4ecdc4); + background: -moz-linear-gradient(90deg, #ff6b6b, #4ecdc4); + border-radius: 5px; + outline: none; + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} + +.pitch-slider input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 15px; + height: 15px; + border-radius: 50%; + background: #ffffff; + cursor: pointer; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); +} + +.pitch-slider input[type="range"]::-moz-range-thumb { + width: 15px; + height: 15px; + border-radius: 50%; + background: #ffffff; + cursor: pointer; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); + border: none; +} + +#pitch-value-a, #pitch-value-b { + font-size: 12px; + font-weight: 600; + color: #ffffff; +} + +/* Waveform */ +.waveform-container { + position: relative; + height: 80px; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + margin-bottom: 15px; overflow: hidden; } -#progress-bar { +.waveform-container canvas { + width: 100%; height: 100%; - background: linear-gradient(90deg, #ff6b6b, #4ecdc4); - width: 0%; - transition: width 0.1s ease; } -/* Optional song info styles */ -#song-info { - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 10px; - border-radius: 5px; +.playhead { + position: absolute; + top: 0; + left: 0; + width: 2px; + height: 100%; + background: #ff6b6b; + box-shadow: 0 0 10px rgba(255, 107, 107, 0.8); + transition: left 0.1s ease; + -webkit-transition: left 0.1s ease; + -moz-transition: left 0.1s ease; +} + +/* Track Info */ +.track-info { + text-align: center; +} + +.track-title { + font-size: 16px; + font-weight: 700; + color: #4ecdc4; + margin-bottom: 5px; + text-shadow: 0 0 10px rgba(78, 205, 196, 0.5); +} + +.track-artist { + font-size: 14px; + color: #ffffff; + margin-bottom: 10px; +} + +.track-time { font-size: 12px; + color: #888888; + font-family: 'Orbitron', 'Courier New', Courier, monospace; } -#song-title { - font-weight: bold; - margin-right: 10px; +/* Mixer */ +.mixer { + width: 400px; + background: linear-gradient(145deg, #2a2a3e, #1e1e2e); + background: -webkit-linear-gradient(145deg, #2a2a3e, #1e1e2e); + background: -moz-linear-gradient(145deg, #2a2a3e, #1e1e2e); + border-radius: 20px; + padding: 20px; + box-shadow: + inset 5px 5px 15px rgba(0, 0, 0, 0.5), + inset -5px -5px 15px rgba(255, 255, 255, 0.1), + 0 10px 30px rgba(0, 0, 0, 0.3); + border: 2px solid rgba(255, 255, 255, 0.1); } -#song-time { - opacity: 0.7; +.mixer-header { + text-align: center; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); } -/* Responsive design */ -@media (max-width: 768px) { - #progress-container { - width: 250px; +.mixer-header h3 { + font-family: 'Orbitron', 'Arial Black', Gadget, sans-serif; + font-size: 18px; + color: #ff6b6b; + text-shadow: 0 0 10px rgba(255, 107, 107, 0.5); +} + +/* Crossfader Section */ +.crossfader-section { + margin-bottom: 30px; +} + +.crossfader-container { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 15px; + margin-bottom: 10px; +} + +.crossfader-container label { + font-weight: 700; + font-size: 18px; + color: #4ecdc4; + text-shadow: 0 0 5px rgba(78, 205, 196, 0.5); +} + +#crossfader { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + height: 8px; + background: linear-gradient(90deg, #4ecdc4, #ff6b6b, #4ecdc4); + background: -webkit-linear-gradient(90deg, #4ecdc4, #ff6b6b, #4ecdc4); + background: -moz-linear-gradient(90deg, #4ecdc4, #ff6b6b, #4ecdc4); + border-radius: 10px; + outline: none; + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} + +#crossfader::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 25px; + height: 25px; + border-radius: 50%; + background: linear-gradient(145deg, #ffffff, #cccccc); + background: -webkit-linear-gradient(145deg, #ffffff, #cccccc); + background: -moz-linear-gradient(145deg, #ffffff, #cccccc); + cursor: pointer; + box-shadow: + 0 0 15px rgba(255, 255, 255, 0.8), + inset 2px 2px 5px rgba(0, 0, 0, 0.2); +} + +#crossfader::-moz-range-thumb { + width: 25px; + height: 25px; + border-radius: 50%; + background: linear-gradient(145deg, #ffffff, #cccccc); + background: -moz-linear-gradient(145deg, #ffffff, #cccccc); + cursor: pointer; + box-shadow: + 0 0 15px rgba(255, 255, 255, 0.8), + inset 2px 2px 5px rgba(0, 0, 0, 0.2); + border: none; +} + +.crossfader-curve { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + gap: 5px; + justify-content: center; + -webkit-justify-content: center; + -ms-flex-pack: center; +} + +.curve-btn { + width: 30px; + height: 30px; + border: none; + border-radius: 5px; + background: linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -moz-linear-gradient(145deg, #3a3a4e, #2a2a3e); + color: #ffffff; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.curve-btn.active { + background: linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -webkit-linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -moz-linear-gradient(145deg, #4ecdc4, #45b7d1); + box-shadow: 0 0 15px rgba(78, 205, 196, 0.5); +} + +/* Channel Controls */ +.channel-controls { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + gap: 20px; + margin-bottom: 30px; +} + +.channel { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + background: linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -webkit-linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -moz-linear-gradient(145deg, #1a1a2e, #0f0f23); + border-radius: 15px; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.master-section { + width: 80px; + background: linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -webkit-linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -moz-linear-gradient(145deg, #1a1a2e, #0f0f23); + border-radius: 15px; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.channel-header, .master-header { + text-align: center; + font-weight: 700; + color: #ffffff; + margin-bottom: 15px; + font-size: 14px; +} + +/* EQ Section */ +.eq-section { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + gap: 15px; + margin-bottom: 20px; +} + +.eq-knob { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 5px; +} + +.eq-knob label { + font-size: 10px; + font-weight: 600; + color: #888888; +} + +.knob { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -moz-linear-gradient(145deg, #3a3a4e, #2a2a3e); + position: relative; + cursor: pointer; + box-shadow: + inset 3px 3px 8px rgba(0, 0, 0, 0.3), + inset -3px -3px 8px rgba(255, 255, 255, 0.1); + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.knob:hover { + box-shadow: + inset 4px 4px 10px rgba(0, 0, 0, 0.4), + inset -4px -4px 10px rgba(255, 255, 255, 0.15); +} + +.knob-indicator { + position: absolute; + top: 3px; + left: 50%; + transform: translateX(-50%); + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + width: 3px; + height: 15px; + background: #4ecdc4; + border-radius: 2px; + box-shadow: 0 0 8px rgba(78, 205, 196, 0.8); + transform-origin: center 17px; + -webkit-transform-origin: center 17px; + -moz-transform-origin: center 17px; +} + +.eq-value { + font-size: 10px; + color: #ffffff; + font-family: 'Orbitron', 'Courier New', Courier, monospace; +} + +/* Volume Faders */ +.volume-fader, .master-volume { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 10px; + margin-bottom: 15px; +} + +.volume-fader input[type="range"], .master-volume input[type="range"] { + writing-mode: bt-lr; /* IE */ + -webkit-appearance: slider-vertical; /* WebKit */ + appearance: slider-vertical; + width: 8px; + height: 150px; + background: linear-gradient(180deg, #ff6b6b, #feca57, #4ecdc4); + background: -webkit-linear-gradient(180deg, #ff6b6b, #feca57, #4ecdc4); + background: -moz-linear-gradient(180deg, #ff6b6b, #feca57, #4ecdc4); + border-radius: 5px; + outline: none; + cursor: pointer; + margin: 0; + padding: 0; +} + +/* Firefox-spezifische Korrekturen für vertikale Slider */ +@supports (-moz-appearance:none) { + .volume-fader input[type="range"], .master-volume input[type="range"] { + transform: rotate(270deg); + -moz-transform: rotate(270deg); + margin: 60px 0; + width: 150px; + } +} + +.volume-fader input[type="range"]::-webkit-slider-thumb, +.master-volume input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 15px; + border-radius: 3px; + background: linear-gradient(145deg, #ffffff, #cccccc); + background: -webkit-linear-gradient(145deg, #ffffff, #cccccc); + cursor: pointer; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); +} + +.volume-fader input[type="range"]::-moz-range-thumb, +.master-volume input[type="range"]::-moz-range-thumb { + width: 20px; + height: 15px; + border-radius: 3px; + background: linear-gradient(145deg, #ffffff, #cccccc); + background: -moz-linear-gradient(145deg, #ffffff, #cccccc); + cursor: pointer; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); + border: none; +} + +.volume-fader label, .master-volume label { + font-size: 10px; + font-weight: 600; + color: #888888; + writing-mode: vertical-rl; + -webkit-writing-mode: vertical-rl; + -ms-writing-mode: tb-rl; + text-orientation: mixed; + -webkit-text-orientation: mixed; +} + +/* VU Meters */ +.vu-meter, .master-vu { + width: 20px; + height: 150px; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + position: relative; + overflow: hidden; +} + +.vu-bar { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 0%; + background: linear-gradient(180deg, #ff0000, #ff6b6b, #feca57, #4ecdc4); + background: -webkit-linear-gradient(180deg, #ff0000, #ff6b6b, #feca57, #4ecdc4); + background: -moz-linear-gradient(180deg, #ff0000, #ff6b6b, #feca57, #4ecdc4); + border-radius: 10px; + transition: height 0.1s ease; + -webkit-transition: height 0.1s ease; + -moz-transition: height 0.1s ease; +} + +/* BPM Display */ +.bpm-display { + text-align: center; + margin-top: 15px; +} + +.bpm-value { + font-size: 24px; + font-weight: 700; + color: #4ecdc4; + font-family: 'Orbitron', 'Courier New', Courier, monospace; + text-shadow: 0 0 10px rgba(78, 205, 196, 0.5); +} + +.bpm-display label { + font-size: 12px; + color: #888888; +} + +/* Effects Section */ +.effects-section { + background: linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -webkit-linear-gradient(145deg, #1a1a2e, #0f0f23); + background: -moz-linear-gradient(145deg, #1a1a2e, #0f0f23); + border-radius: 15px; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.effect-unit h4 { + text-align: center; + color: #ff6b6b; + margin-bottom: 15px; + font-size: 14px; + text-shadow: 0 0 5px rgba(255, 107, 107, 0.5); +} + +.effect-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 15px; +} + +.effect-btn { + padding: 8px 12px; + border: none; + border-radius: 8px; + background: linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #3a3a4e, #2a2a3e); + background: -moz-linear-gradient(145deg, #3a3a4e, #2a2a3e); + color: #ffffff; + font-size: 10px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.effect-btn:hover { + transform: translateY(-1px); + -webkit-transform: translateY(-1px); + -moz-transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.effect-btn.active { + background: linear-gradient(145deg, #ff6b6b, #ff5722); + background: -webkit-linear-gradient(145deg, #ff6b6b, #ff5722); + background: -moz-linear-gradient(145deg, #ff6b6b, #ff5722); + box-shadow: 0 0 15px rgba(255, 107, 107, 0.5); +} + +.effect-knob { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + gap: 5px; +} + +/* Bottom Panel */ +.bottom-panel { + height: 200px; + background: linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -moz-linear-gradient(145deg, #1e1e2e, #2a2a3e); + border-top: 2px solid rgba(255, 255, 255, 0.1); + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + padding: 15px; + gap: 20px; +} + +/* Playlist Browser */ +.playlist-browser { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + background: linear-gradient(145deg, #0f0f23, #1a1a2e); + background: -webkit-linear-gradient(145deg, #0f0f23, #1a1a2e); + background: -moz-linear-gradient(145deg, #0f0f23, #1a1a2e); + border-radius: 15px; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.browser-header { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + justify-content: space-between; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.browser-header h4 { + color: #4ecdc4; + font-size: 14px; + text-shadow: 0 0 5px rgba(78, 205, 196, 0.5); +} + +.search-box { + position: relative; + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; +} + +.search-box i { + position: absolute; + left: 10px; + color: #888888; +} + +.search-box input { + padding: 8px 15px 8px 35px; + border: none; + border-radius: 20px; + background: rgba(0, 0, 0, 0.3); + color: #ffffff; + font-size: 12px; + outline: none; + width: 200px; +} + +.search-box input::placeholder { + color: #888888; +} + +.track-list { + height: 120px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #4ecdc4 transparent; +} + +.track-list::-webkit-scrollbar { + width: 6px; +} + +.track-list::-webkit-scrollbar-track { + background: transparent; +} + +.track-list::-webkit-scrollbar-thumb { + background: #4ecdc4; + border-radius: 3px; +} + +.track-item { + padding: 8px 12px; + border-radius: 8px; + margin-bottom: 5px; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + border: 1px solid transparent; +} + +.track-item:hover { + background: rgba(78, 205, 196, 0.1); + border-color: rgba(78, 205, 196, 0.3); +} + +.track-item-title { + font-size: 12px; + font-weight: 600; + color: #ffffff; + margin-bottom: 2px; +} + +.track-item-artist { + font-size: 10px; + color: #888888; +} + +/* Recording Section */ +.recording-section { + width: 150px; + background: linear-gradient(145deg, #0f0f23, #1a1a2e); + background: -webkit-linear-gradient(145deg, #0f0f23, #1a1a2e); + background: -moz-linear-gradient(145deg, #0f0f23, #1a1a2e); + border-radius: 15px; + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + text-align: center; +} + +.recording-section h4 { + color: #ff6b6b; + font-size: 14px; + margin-bottom: 15px; + text-shadow: 0 0 5px rgba(255, 107, 107, 0.5); +} + +.record-btn { + width: 60px; + height: 60px; + border: none; + border-radius: 50%; + background: linear-gradient(145deg, #ff6b6b, #ff5722); + background: -webkit-linear-gradient(145deg, #ff6b6b, #ff5722); + background: -moz-linear-gradient(145deg, #ff6b6b, #ff5722); + color: #ffffff; + font-size: 12px; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + margin-bottom: 15px; + box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); +} + +.record-btn:hover { + transform: scale(1.1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + box-shadow: 0 12px 30px rgba(255, 107, 107, 0.6); +} + +.record-btn.recording { + animation: recordPulse 1s ease-in-out infinite; + -webkit-animation: recordPulse 1s ease-in-out infinite; + -moz-animation: recordPulse 1s ease-in-out infinite; +} + +@keyframes recordPulse { + 0%, 100% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); } + 50% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.8); } +} + +@-webkit-keyframes recordPulse { + 0%, 100% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); } + 50% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.8); } +} + +@-moz-keyframes recordPulse { + 0%, 100% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); } + 50% { box-shadow: 0 8px 20px rgba(255, 107, 107, 0.8); } +} + +.recording-time { + font-family: 'Orbitron', 'Courier New', Courier, monospace; + font-size: 16px; + color: #4ecdc4; + text-shadow: 0 0 5px rgba(78, 205, 196, 0.5); +} + +/* Modal */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + justify-content: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + z-index: 1000; +} + +.modal-content { + background: linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -webkit-linear-gradient(145deg, #1e1e2e, #2a2a3e); + background: -moz-linear-gradient(145deg, #1e1e2e, #2a2a3e); + border-radius: 20px; + padding: 30px; + width: 500px; + max-width: 90vw; + border: 2px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); +} + +.modal-header { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + justify-content: space-between; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + align-items: center; + -webkit-align-items: center; + -ms-flex-align: center; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); +} + +.modal-header h3 { + color: #4ecdc4; + font-size: 20px; + text-shadow: 0 0 10px rgba(78, 205, 196, 0.5); +} + +.close-modal { + width: 30px; + height: 30px; + border: none; + border-radius: 50%; + background: rgba(255, 0, 0, 0.7); + color: #ffffff; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.close-modal:hover { + transform: scale(1.1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + background: rgba(255, 0, 0, 0.9); +} + +.input-group { + margin-bottom: 20px; +} + +.input-group label { + display: block; + margin-bottom: 8px; + color: #ffffff; + font-weight: 600; +} + +.input-group input { + width: 100%; + padding: 12px 15px; + border: none; + border-radius: 10px; + background: rgba(0, 0, 0, 0.3); + color: #ffffff; + font-size: 14px; + outline: none; + border: 2px solid transparent; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.input-group input:focus { + border-color: #4ecdc4; + box-shadow: 0 0 15px rgba(78, 205, 196, 0.3); +} + +.input-group input::placeholder { + color: #888888; +} + +.modal-actions { + display: flex; + display: -webkit-flex; + display: -ms-flexbox; + gap: 15px; + justify-content: flex-end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + margin-top: 30px; +} + +.btn-cancel, .btn-load { + padding: 12px 25px; + border: none; + border-radius: 10px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} + +.btn-cancel { + background: linear-gradient(145deg, #666666, #555555); + background: -webkit-linear-gradient(145deg, #666666, #555555); + background: -moz-linear-gradient(145deg, #666666, #555555); + color: #ffffff; +} + +.btn-cancel:hover { + transform: translateY(-2px); + -webkit-transform: translateY(-2px); + -moz-transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); +} + +.btn-load { + background: linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -webkit-linear-gradient(145deg, #4ecdc4, #45b7d1); + background: -moz-linear-gradient(145deg, #4ecdc4, #45b7d1); + color: #ffffff; +} + +.btn-load:hover { + transform: translateY(-2px); + -webkit-transform: translateY(-2px); + -moz-transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(78, 205, 196, 0.4); +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .dj-console { + flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + gap: 15px; } - #song-info { - font-size: 11px; - padding: 8px; + .mixer { + width: 100%; + } + + .deck { + width: 100%; + } + + .vinyl-container { + width: 150px; + height: 150px; + } + + .vinyl { + width: 150px; + height: 150px; } } + +/* Animations */ +@keyframes glow { + 0%, 100% { box-shadow: 0 0 5px rgba(78, 205, 196, 0.5); } + 50% { box-shadow: 0 0 20px rgba(78, 205, 196, 0.8); } +} + +@-webkit-keyframes glow { + 0%, 100% { box-shadow: 0 0 5px rgba(78, 205, 196, 0.5); } + 50% { box-shadow: 0 0 20px rgba(78, 205, 196, 0.8); } +} + +@-moz-keyframes glow { + 0%, 100% { box-shadow: 0 0 5px rgba(78, 205, 196, 0.5); } + 50% { box-shadow: 0 0 20px rgba(78, 205, 196, 0.8); } +} + +.glowing { + animation: glow 2s ease-in-out infinite; + -webkit-animation: glow 2s ease-in-out infinite; + -moz-animation: glow 2s ease-in-out infinite; +} + +/* Utility Classes */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.mb-10 { margin-bottom: 10px; } +.mb-15 { margin-bottom: 15px; } +.mb-20 { margin-bottom: 20px; } + +.p-10 { padding: 10px; } +.p-15 { padding: 15px; } +.p-20 { padding: 20px; }