techdj/index.html

491 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>TechDJ Pro</title>
<link rel="stylesheet" href="style.css?v=7.0">
</head>
<body>
<div id="start-overlay">
<h1 class="overlay-title">TECHDJ PROTOCOL</h1>
<div id="loading-status"
style="color: var(--primary-cyan); font-family: 'Orbitron'; margin-bottom: 20px; font-size: 0.8rem; letter-spacing: 2px;">
READY TO INITIALISE</div>
<button id="start-btn" onclick="initSystem()">INITIALISE SYSTEM</button>
<p style="color:#666; margin-top:20px; font-family:'Rajdhani'">v2.0 // NEON CORE</p>
</div>
<!-- Landscape Orientation Prompt -->
<div class="landscape-prompt" id="landscape-prompt">
<div class="rotate-icon">ROTATE</div>
<h2>OPTIMAL ORIENTATION</h2>
<p>For the best DJ experience, please rotate your device to <strong>landscape mode</strong> (sideways).</p>
<p style="font-size: 0.9rem; color: #888;">Both decks and the crossfader will be visible simultaneously.</p>
<button class="btn-secondary" onclick="dismissLandscapePrompt()"
style="margin-top: 15px; padding: 10px 20px; width: 100%; border: 1px solid var(--primary-cyan); color: var(--primary-cyan); font-family: 'Orbitron', sans-serif; cursor: pointer;">
USE PORTRAIT MODE
</button>
</div>
<!-- Mobile Top Bar -->
<div class="mobile-top-bar">
<div class="status-indicator">
<span class="status-dot"></span>
<span id="system-status">NEON_v2</span>
</div>
<div class="app-logo">TECHDJ PRO</div>
<div class="clock-display" id="clock-display">00:00</div>
</div>
<!-- MAIN APP CONTAINER -->
<div class="app-container">
<!-- Mobile Tabs -->
<nav class="mobile-tabs">
<button class="tab-btn active" onclick="switchTab('library')">
<span class="tab-icon">#</span>
<span>LIB</span>
</button>
<button class="tab-btn" onclick="switchTab('deck-A')">
<span class="tab-icon">A</span>
<span>DECK A</span>
</button>
<button class="tab-btn" onclick="switchTab('deck-B')">
<span class="tab-icon">B</span>
<span>DECK B</span>
</button>
<button class="tab-btn" onclick="switchQueueTab()">
<span class="tab-icon">Q</span>
<span id="queue-tab-label">QUEUE A</span>
</button>
<button class="tab-btn fullscreen-btn" onclick="toggleFullScreen()" id="fullscreen-toggle">
<span class="tab-icon">[]</span>
<span>FULL</span>
</button>
</nav>
<!-- 1. LEFT: LIBRARY -->
<section class="library-section">
<div class="lib-header">
<input type="text" id="lib-search" placeholder="FILTER LIBRARY..." onkeyup="filterLibrary()">
<button class="refresh-btn" onclick="refreshLibrary()" title="Refresh Library">REFRESH</button>
</div>
<div id="library-list" class="library-list">
<!-- Tracks go here -->
</div>
</section>
<!-- 2. MIDDLE: DECK A -->
<div class="deck" id="deck-A">
<div class="deck-header">
<span class="deck-title title-a">DECK A</span>
<span class="track-display" id="display-A">NO TRACK LOADED</span>
</div>
<!-- Waveform Display -->
<div class="waveform-container">
<canvas id="waveform-A" class="waveform-canvas"></canvas>
<div class="playhead" id="playhead-A"></div>
<div class="time-display">
<span id="time-current-A">0:00</span> / <span id="time-total-A">0:00</span>
<span class="bpm-display" id="bpm-A"></span>
</div>
</div>
<div class="disk-container">
<div class="dj-disk" id="disk-A">
<div class="disk-label">A</div>
</div>
</div>
<canvas id="viz-A" width="400" height="100"></canvas>
<!-- Hot Cues -->
<div class="hot-cues">
<button class="cue-btn cue-1" onclick="handleCue('A', 1)"
oncontextmenu="clearCue('A', 1); return false;">CUE 1</button>
<button class="cue-btn cue-2" onclick="handleCue('A', 2)"
oncontextmenu="clearCue('A', 2); return false;">CUE 2</button>
<button class="cue-btn cue-3" onclick="handleCue('A', 3)"
oncontextmenu="clearCue('A', 3); return false;">CUE 3</button>
<button class="cue-btn cue-4" onclick="handleCue('A', 4)"
oncontextmenu="clearCue('A', 4); return false;">CUE 4</button>
</div>
<!-- Loop Controls -->
<div class="loop-controls">
<button class="loop-btn" onclick="setLoop('A', 'in')">LOOP IN</button>
<button class="loop-btn" onclick="setLoop('A', 'out')">LOOP OUT</button>
<button class="loop-btn" onclick="setLoop('A', 'exit')">EXIT</button>
</div>
<!-- Auto-Loop Beat Controls -->
<div class="auto-loop-controls">
<label class="auto-loop-label">AUTO-LOOP</label>
<div class="auto-loop-buttons">
<button class="auto-loop-btn" data-beats="8" onclick="setAutoLoop('A', 8)">8</button>
<button class="auto-loop-btn" data-beats="4" onclick="setAutoLoop('A', 4)">4</button>
<button class="auto-loop-btn" data-beats="2" onclick="setAutoLoop('A', 2)">2</button>
<button class="auto-loop-btn" data-beats="1" onclick="setAutoLoop('A', 1)">1</button>
<button class="auto-loop-btn" data-beats="0.5" onclick="setAutoLoop('A', 0.5)">1/2</button>
<button class="auto-loop-btn" data-beats="0.25" onclick="setAutoLoop('A', 0.25)">1/4</button>
<button class="auto-loop-btn" data-beats="0.125" onclick="setAutoLoop('A', 0.125)">1/8</button>
</div>
</div>
<!-- Integrated Queue -->
<div class="deck-queue">
<div class="queue-header">
<span>UP NEXT - DECK A</span>
<button class="mini-clear-btn" onclick="clearQueue('A')">CLEAR</button>
</div>
<div class="queue-list" id="deck-queue-list-A">
<div class="queue-empty">Queue is empty</div>
</div>
</div>
<div class="controls-grid">
<!-- Volume Fader -->
<div class="fader-group">
<label>VOLUME</label>
<input type="range" orient="vertical" class="volume-fader" min="0" max="100" value="80"
oninput="changeVolume('A', this.value)">
</div>
<!-- EQ -->
<div class="eq-container">
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'high', this.value)"><label>HI</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'mid', this.value)"><label>MID</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'low', this.value)"><label>LO</label></div>
</div>
<!-- Filters -->
<div class="filter-knobs">
<div class="filter-knob">
<label>LOW-PASS</label>
<input type="range" class="filter-slider filter-lp" min="0" max="100" value="100"
oninput="changeFilter('A', 'lowpass', this.value)">
</div>
<div class="filter-knob">
<label>HIGH-PASS</label>
<input type="range" class="filter-slider filter-hp" min="0" max="100" value="0"
oninput="changeFilter('A', 'highpass', this.value)">
</div>
</div>
<!-- Pitch/Tempo -->
<div class="fader-group">
<label>PITCH / TEMPO</label>
<input type="range" class="speed-slider" min="0.5" max="1.5" step="0.01" value="1"
oninput="changeSpeed('A', this.value)" ondblclick="this.value=1; changeSpeed('A', 1);">
<div class="pitch-bend-buttons">
<button class="pitch-bend" onmousedown="pitchBend('A', -0.05)" onmouseup="pitchBend('A', 0)"
onmouseleave="pitchBend('A', 0)">-</button>
<button class="pitch-bend" onmousedown="pitchBend('A', 0.05)" onmouseup="pitchBend('A', 0)"
onmouseleave="pitchBend('A', 0)">+</button>
</div>
</div>
</div>
<div class="transport">
<button class="big-btn play-btn" onclick="playDeck('A')">PLAY</button>
<button class="big-btn pause-btn" onclick="pauseDeck('A')">PAUSE</button>
<button class="big-btn repeat-btn" id="repeat-btn-A" onclick="toggleRepeat('A')"
title="Loop Track">LOOP</button>
<button class="big-btn sync-btn" onclick="syncDecks('A')">SYNC</button>
<button class="big-btn reset-btn mob-hide" onclick="resetDeck('A')"
title="Reset all settings to default">RESET</button>
</div>
</div>
<!-- 3. RIGHT: DECK B -->
<div class="deck" id="deck-B">
<div class="deck-header">
<span class="deck-title title-b">DECK B</span>
<span class="track-display" id="display-B">NO TRACK LOADED</span>
</div>
<!-- Waveform Display -->
<div class="waveform-container">
<canvas id="waveform-B" class="waveform-canvas"></canvas>
<div class="playhead" id="playhead-B"></div>
<div class="time-display">
<span id="time-current-B">0:00</span> / <span id="time-total-B">0:00</span>
<span class="bpm-display" id="bpm-B"></span>
</div>
</div>
<div class="disk-container">
<div class="dj-disk" id="disk-B">
<div class="disk-label">B</div>
</div>
</div>
<canvas id="viz-B" width="400" height="100"></canvas>
<!-- Hot Cues -->
<div class="hot-cues">
<button class="cue-btn cue-1" onclick="handleCue('B', 1)"
oncontextmenu="clearCue('B', 1); return false;">CUE 1</button>
<button class="cue-btn cue-2" onclick="handleCue('B', 2)"
oncontextmenu="clearCue('B', 2); return false;">CUE 2</button>
<button class="cue-btn cue-3" onclick="handleCue('B', 3)"
oncontextmenu="clearCue('B', 3); return false;">CUE 3</button>
<button class="cue-btn cue-4" onclick="handleCue('B', 4)"
oncontextmenu="clearCue('B', 4); return false;">CUE 4</button>
</div>
<!-- Loop Controls -->
<div class="loop-controls">
<button class="loop-btn" onclick="setLoop('B', 'in')">LOOP IN</button>
<button class="loop-btn" onclick="setLoop('B', 'out')">LOOP OUT</button>
<button class="loop-btn" onclick="setLoop('B', 'exit')">EXIT</button>
</div>
<!-- Auto-Loop Beat Controls -->
<div class="auto-loop-controls">
<label class="auto-loop-label">AUTO-LOOP</label>
<div class="auto-loop-buttons">
<button class="auto-loop-btn" data-beats="8" onclick="setAutoLoop('B', 8)">8</button>
<button class="auto-loop-btn" data-beats="4" onclick="setAutoLoop('B', 4)">4</button>
<button class="auto-loop-btn" data-beats="2" onclick="setAutoLoop('B', 2)">2</button>
<button class="auto-loop-btn" data-beats="1" onclick="setAutoLoop('B', 1)">1</button>
<button class="auto-loop-btn" data-beats="0.5" onclick="setAutoLoop('B', 0.5)">1/2</button>
<button class="auto-loop-btn" data-beats="0.25" onclick="setAutoLoop('B', 0.25)">1/4</button>
<button class="auto-loop-btn" data-beats="0.125" onclick="setAutoLoop('B', 0.125)">1/8</button>
</div>
</div>
<!-- Integrated Queue -->
<div class="deck-queue">
<div class="queue-header">
<span>UP NEXT - DECK B</span>
<button class="mini-clear-btn" onclick="clearQueue('B')">CLEAR</button>
</div>
<div class="queue-list" id="deck-queue-list-B">
<div class="queue-empty">Queue is empty</div>
</div>
</div>
<div class="controls-grid">
<!-- Volume Fader -->
<div class="fader-group">
<label>VOLUME</label>
<input type="range" orient="vertical" class="volume-fader" min="0" max="100" value="80"
oninput="changeVolume('B', this.value)">
</div>
<!-- EQ -->
<div class="eq-container">
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'high', this.value)"><label>HI</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'mid', this.value)"><label>MID</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'low', this.value)"><label>LO</label></div>
</div>
<!-- Filters -->
<div class="filter-knobs">
<div class="filter-knob">
<label>LOW-PASS</label>
<input type="range" class="filter-slider filter-lp" min="0" max="100" value="100"
oninput="changeFilter('B', 'lowpass', this.value)">
</div>
<div class="filter-knob">
<label>HIGH-PASS</label>
<input type="range" class="filter-slider filter-hp" min="0" max="100" value="0"
oninput="changeFilter('B', 'highpass', this.value)">
</div>
</div>
<!-- Pitch/Tempo -->
<div class="fader-group">
<label>PITCH / TEMPO</label>
<input type="range" class="speed-slider" min="0.5" max="1.5" step="0.01" value="1"
oninput="changeSpeed('B', this.value)" ondblclick="this.value=1; changeSpeed('B', 1);">
<div class="pitch-bend-buttons">
<button class="pitch-bend" onmousedown="pitchBend('B', -0.05)" onmouseup="pitchBend('B', 0)"
onmouseleave="pitchBend('B', 0)">-</button>
<button class="pitch-bend" onmousedown="pitchBend('B', 0.05)" onmouseup="pitchBend('B', 0)"
onmouseleave="pitchBend('B', 0)">+</button>
</div>
</div>
</div>
<div class="transport">
<button class="big-btn play-btn" onclick="playDeck('B')">PLAY</button>
<button class="big-btn pause-btn" onclick="pauseDeck('B')">PAUSE</button>
<button class="big-btn repeat-btn" id="repeat-btn-B" onclick="toggleRepeat('B')"
title="Loop Track">LOOP</button>
<button class="big-btn sync-btn" onclick="syncDecks('B')">SYNC</button>
<button class="big-btn reset-btn mob-hide" onclick="resetDeck('B')"
title="Reset all settings to default">RESET</button>
</div>
</div>
<!-- STANDALONE QUEUE SECTIONS FOR MOBILE -->
<section class="queue-section" id="queue-A">
<div class="queue-page-header">
<h2 class="queue-page-title">QUEUE A</h2>
<button class="queue-clear-btn" onclick="clearQueue('A')" title="Clear queue">Clear
All</button>
</div>
<div class="queue-list" id="queue-list-A">
<div class="queue-empty">Drop tracks here or click "Queue to A" in library</div>
</div>
</section>
<section class="queue-section" id="queue-B">
<div class="queue-page-header">
<h2 class="queue-page-title">QUEUE B</h2>
<button class="queue-clear-btn" onclick="clearQueue('B')" title="Clear queue">Clear
All</button>
</div>
<div class="queue-list" id="queue-list-B">
<div class="queue-empty">Drop tracks here or click "Queue to B" in library</div>
</div>
</section>
<!-- 4. BOTTOM: CROSSFADER -->
<div class="mixer-section">
<input type="range" class="xfader" id="crossfader" min="0" max="100" value="50"
oninput="updateCrossfader(this.value)">
</div>
</div>
<!-- Live Streaming Panel -->
<div class="streaming-panel" id="streaming-panel">
<div class="streaming-header">
<span>LIVE STREAM</span>
<button class="close-streaming" onclick="toggleStreamingPanel()">X</button>
</div>
<div class="streaming-content">
<div class="broadcast-controls">
<button class="broadcast-btn" id="broadcast-btn" onclick="toggleBroadcast()">
<span class="broadcast-icon"></span>
<span id="broadcast-text">START BROADCAST</span>
</button>
<div class="broadcast-status" id="broadcast-status">Offline</div>
</div>
<div class="listener-info">
<div class="listener-count">
<span class="count-icon"></span>
<span id="listener-count">0</span>
<span class="count-label">Listeners</span>
</div>
</div>
<div class="stream-url-section">
<label>Share this URL:</label>
<div class="url-copy-group">
<input type="text" id="stream-url" readonly value="http://localhost:5001">
<button onclick="copyStreamUrl(event)" class="copy-btn">COPY</button>
</div>
</div>
<div class="stream-settings">
<label>
<input type="checkbox" id="auto-start-stream" onchange="toggleAutoStream(this.checked)">
Auto-start on play
</label>
<div class="quality-selector-group">
<label for="stream-quality">Stream Quality:</label>
<select id="stream-quality" class="stream-quality-select">
<option value="128">High (128kbps)</option>
<option value="96" selected>Medium (96kbps)</option>
<option value="64">Low (64kbps)</option>
<option value="48">Very Low (48kbps)</option>
<option value="32">Minimum (32kbps)</option>
</select>
<span class="quality-hint">Lower = more stable on poor connections</span>
</div>
</div>
</div>
</div>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="script.js"></script>
<!-- Settings Panel -->
<div class="settings-panel" id="settings-panel">
<div class="settings-header">
<span style="font-family:'Orbitron',sans-serif; font-size:1rem; letter-spacing:3px; color:var(--primary-cyan);">SETTINGS</span>
<button onclick="toggleSettings()" style="background:transparent; border:none; color:#aaa; font-size:1.4rem; cursor:pointer; line-height:1; padding:4px 8px;">&times;</button>
</div>
<div class="settings-content">
<div class="setting-item"><label><input type="checkbox" id="repeat-A"
onchange="toggleRepeat('A', this.checked)">Repeat Deck A</label></div>
<div class="setting-item"><label><input type="checkbox" id="repeat-B"
onchange="toggleRepeat('B', this.checked)">Repeat Deck B</label></div>
<div class="setting-item"><label><input type="checkbox" id="auto-mix"
onchange="toggleAutoMix(this.checked)">Auto-Crossfade</label></div>
<div class="setting-item"><label><input type="checkbox" id="shuffle-mode"
onchange="toggleShuffle(this.checked)">Shuffle Library</label></div>
<div class="setting-item"><label><input type="checkbox" id="quantize"
onchange="toggleQuantize(this.checked)">Quantise</label></div>
<div class="setting-item"><label><input type="checkbox" id="auto-play" checked
onchange="toggleAutoPlay(this.checked)">Auto-play next</label></div>
<div class="setting-item"><label><input type="checkbox" id="glow-A"
onchange="updateManualGlow('A', this.checked)">Glow Deck A (Cyan)</label></div>
<div class="setting-item"><label><input type="checkbox" id="glow-B"
onchange="updateManualGlow('B', this.checked)">Glow Deck B (Magenta)</label></div>
<div class="setting-item" style="flex-direction: column; align-items: flex-start;">
<label>Glow Intensity (DJ Panel)</label>
<input type="range" id="glow-intensity" min="1" max="100" value="30" style="width: 100%;"
oninput="updateGlowIntensity(this.value)">
</div>
<div class="setting-item" style="flex-direction: column; align-items: flex-start;">
<label>Listener Page Glow</label>
<input type="range" id="listener-glow-intensity" min="0" max="100" value="30" style="width: 100%;"
oninput="updateListenerGlow(this.value)">
</div>
<div class="setting-item">
<button class="btn-primary" onclick="openKeyboardSettings()"
style="width: 100%; padding: 12px; margin-top: 10px;">
CUSTOM KEYBOARD MAPS
</button>
</div>
</div>
</div>
<!-- Tools FAB for Mobile -->
<div class="fab-container">
<button class="fab-main" onclick="toggleFabMenu(event)">SET</button>
<div class="fab-menu" id="fab-menu">
<button class="fab-item" onclick="toggleSettings(); toggleFabMenu()">SET</button>
<button class="fab-item"
onclick="document.getElementById('file-upload').click(); toggleFabMenu()">UP</button>
<button class="fab-item" onclick="toggleStreamingPanel(); toggleFabMenu()">LIVE</button>
<button class="fab-item" onclick="openKeyboardSettings(); toggleFabMenu()">KB</button>
</div>
</div>
<button class="keyboard-btn pc-only" onclick="openKeyboardSettings()">KB</button>
<button class="streaming-btn pc-only" onclick="toggleStreamingPanel()">STREAM</button>
<button class="upload-btn pc-only"
onclick="document.getElementById('file-upload').click()">UPLOAD</button>
<input type="file" id="file-upload" accept=".mp3,.m4a,.wav,.flac,.ogg" multiple style="display:none"
onchange="handleFileUpload(event)">
<button class="settings-btn pc-only" onclick="toggleSettings()">SET</button>
<div class="toast-container" id="toast-container"></div>
</body>
</html>