diff --git a/server.py b/server.py new file mode 100644 index 0000000..afd6b19 --- /dev/null +++ b/server.py @@ -0,0 +1,272 @@ +# Monkey patch MUST be first - before any other imports! +import eventlet +eventlet.monkey_patch() + +import os +from flask import Flask, send_from_directory, jsonify, request, session +from flask_socketio import SocketIO, emit +from dotenv import load_dotenv +# Load environment variables from .env file +load_dotenv() +import downloader + +# Relay State +broadcast_state = { + 'active': False +} +listener_count = 0 +MUSIC_FOLDER = "music" +# Ensure music folder exists +if not os.path.exists(MUSIC_FOLDER): + os.makedirs(MUSIC_FOLDER) + +# Helper for shared routes +def setup_shared_routes(app): + @app.route('/library.json') + def get_library(): + library = [] + if os.path.exists(MUSIC_FOLDER): + for filename in sorted(os.listdir(MUSIC_FOLDER)): + if filename.lower().endswith(('.mp3', '.m4a', '.wav', '.flac', '.ogg')): + library.append({ + "title": os.path.splitext(filename)[0], + "file": f"music/{filename}" + }) + return jsonify(library) + + @app.route('/download', methods=['POST']) + def download(): + data = request.json + url = data.get('url') + quality = data.get('quality', '320') + if not url: + return jsonify({"success": False, "error": "No URL provided"}), 400 + result = downloader.download_mp3(url, quality) + return jsonify(result) + + @app.route('/search_youtube', methods=['GET']) + def search_youtube(): + query = request.args.get('q', '') + if not query: + return jsonify({"success": False, "error": "No query provided"}), 400 + + # Get API key from environment variable + api_key = os.environ.get('YOUTUBE_API_KEY', '') + if not api_key: + return jsonify({ + "success": False, + "error": "YouTube API key not configured. Set YOUTUBE_API_KEY environment variable." + }), 500 + + try: + import requests + # Search YouTube using Data API v3 + url = 'https://www.googleapis.com/youtube/v3/search' + params = { + 'part': 'snippet', + 'q': query, + 'type': 'video', + 'videoCategoryId': '10', # Music category + 'maxResults': 20, + 'key': api_key + } + + response = requests.get(url, params=params) + data = response.json() + + if 'error' in data: + return jsonify({ + "success": False, + "error": data['error'].get('message', 'YouTube API error') + }), 400 + + # Format results + results = [] + for item in data.get('items', []): + results.append({ + 'videoId': item['id']['videoId'], + 'title': item['snippet']['title'], + 'channel': item['snippet']['channelTitle'], + 'thumbnail': item['snippet']['thumbnails']['medium']['url'], + 'url': f"https://www.youtube.com/watch?v={item['id']['videoId']}" + }) + + return jsonify({"success": True, "results": results}) + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route('/') + def serve_static(filename): + response = send_from_directory('.', filename) + if filename.endswith(('.css', '.js', '.html')): + response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' + return response + + @app.route('/') + def index(): + return send_from_directory('.', 'index.html') + + @app.route('/upload', methods=['POST']) + def upload_file(): + if 'file' not in request.files: + return jsonify({"success": False, "error": "No file provided"}), 400 + + file = request.files['file'] + + if file.filename == '': + return jsonify({"success": False, "error": "No file selected"}), 400 + + if not file.filename.endswith('.mp3'): + return jsonify({"success": False, "error": "Only MP3 files are allowed"}), 400 + + # Sanitize filename + import re + filename = re.sub(r'[^\w\s-]', '', file.filename) + filename = re.sub(r'[-\s]+', '-', filename) + + filepath = os.path.join(MUSIC_FOLDER, filename) + + try: + file.save(filepath) + print(f"✅ Uploaded: {filename}") + return jsonify({"success": True, "filename": filename}) + except Exception as e: + print(f"❌ Upload error: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + +# === DJ SERVER (Port 5000) === +dj_app = Flask(__name__, static_folder='.', static_url_path='') +dj_app.config['SECRET_KEY'] = 'dj_panel_secret' +setup_shared_routes(dj_app) +dj_socketio = SocketIO( + dj_app, + cors_allowed_origins="*", + async_mode='eventlet', + max_http_buffer_size=1e8, # 100MB buffer + ping_timeout=10, + ping_interval=5, + logger=False, + engineio_logger=False +) + +@dj_socketio.on('connect') +def dj_connect(): + print(f"🎧 DJ connected: {request.sid}") + session['is_dj'] = True + +@dj_socketio.on('disconnect') +def dj_disconnect(): + if session.get('is_dj'): + print("⚠️ DJ disconnected - broadcast will continue until manually stopped") + session['is_dj'] = False + # Don't stop streaming_active - let it continue + # Broadcast will resume when DJ reconnects + +def stop_broadcast_after_timeout(): + """No longer used - broadcasts don't auto-stop""" + pass + +@dj_socketio.on('start_broadcast') +def dj_start(): + broadcast_state['active'] = True + session['is_dj'] = True + print("🎙️ Broadcast -> ACTIVE") + + listener_socketio.emit('broadcast_started', namespace='/') + listener_socketio.emit('stream_status', {'active': True}, namespace='/') + +@dj_socketio.on('stop_broadcast') +def dj_stop(): + broadcast_state['active'] = False + session['is_dj'] = False + print("🛑 DJ stopped broadcasting") + + listener_socketio.emit('broadcast_stopped', namespace='/') + listener_socketio.emit('stream_status', {'active': False}, namespace='/') + +@dj_socketio.on('audio_chunk') +def dj_audio(data): + # Relay audio chunk to all listeners immediately + if broadcast_state['active']: + listener_socketio.emit('audio_data', data, namespace='/') + +# === LISTENER SERVER (Port 6000) === +listener_app = Flask(__name__, static_folder='.', static_url_path='') +listener_app.config['SECRET_KEY'] = 'listener_secret' +setup_shared_routes(listener_app) +listener_socketio = SocketIO( + listener_app, + cors_allowed_origins="*", + async_mode='eventlet', + max_http_buffer_size=1e8, # 100MB buffer + ping_timeout=10, + ping_interval=5, + logger=False, + engineio_logger=False +) + +@listener_socketio.on('connect') +def listener_connect(): + print(f"👂 Listener Socket Connected: {request.sid}") + +@listener_socketio.on('disconnect') +def listener_disconnect(): + global listener_count + if session.get('is_listener'): + # Clear session flag FIRST to prevent re-entry issues + session['is_listener'] = False + listener_count = max(0, listener_count - 1) + print(f"❌ Listener left. Total: {listener_count}") + # Broadcast to all listeners + listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/') + # Broadcast to all DJs + dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/') + +@listener_socketio.on('join_listener') +def listener_join(): + global listener_count + if not session.get('is_listener'): + session['is_listener'] = True + listener_count += 1 + print(f"👂 New listener joined. Total: {listener_count}") + # Broadcast to all listeners + listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/') + # Broadcast to all DJs + dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/') + + emit('stream_status', {'active': broadcast_state['active']}) + +@listener_socketio.on('request_header') +def handle_request_header(): + # Header logic removed for local relay mode + pass + +@listener_socketio.on('get_listener_count') +def listener_get_count(): + emit('listener_count', {'count': listener_count}) + +# DJ Panel Routes (No engine commands needed in local mode) +@dj_socketio.on('get_mixer_status') +def get_mixer_status(): + pass + +@dj_socketio.on('audio_sync_queue') +def audio_sync_queue(data): + pass + + +if __name__ == '__main__': + print("=" * 50) + print("🎧 TECHDJ PRO - DUAL PORT ARCHITECTURE") + print("=" * 50) + print("👉 DJ PANEL: http://localhost:5000") + print("👉 LISTEN PAGE: http://localhost:5001") + print("=" * 50) + + # Audio engine DISABLED + print("✅ Local Radio server ready") + + # Run both servers using eventlet's spawn + eventlet.spawn(dj_socketio.run, dj_app, host='0.0.0.0', port=5000, debug=False) + listener_socketio.run(listener_app, host='0.0.0.0', port=5001, debug=False) diff --git a/style.css b/style.css new file mode 100644 index 0000000..46d6457 --- /dev/null +++ b/style.css @@ -0,0 +1,3792 @@ +@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;500;700&display=swap'); + +:root { + --bg-dark: #0a0a12; + --panel-bg: rgba(20, 20, 30, 0.8); + --primary-cyan: #00f3ff; + --secondary-magenta: #bc13fe; + --text-main: #e0e0e0; + --text-dim: #888; + --glass-border: 1px solid rgba(255, 255, 255, 0.1); + --glow-opacity: 0.3; + --glow-spread: 30px; + --glow-border: 5px; +} + +body { + margin: 0; + background-color: var(--bg-dark); + color: var(--text-main); + font-family: 'Rajdhani', sans-serif; + height: 100vh; + display: flex; + flex-direction: column; + overflow: hidden; + background-image: + radial-gradient(circle at 10% 20%, rgba(0, 243, 255, 0.15) 0%, transparent 25%), + radial-gradient(circle at 90% 80%, rgba(188, 19, 254, 0.15) 0%, transparent 25%), + radial-gradient(circle at 50% 50%, rgba(0, 243, 255, 0.05) 0%, transparent 50%); +} + +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 99999; + border: 1px solid rgba(80, 80, 80, 0.1); + box-sizing: border-box; +} + +body.playing-A::before { + border: none; + box-shadow: + 0 0 var(--glow-spread) rgba(0, 243, 255, var(--glow-opacity)), + inset 0 0 var(--glow-spread) rgba(0, 243, 255, calc(var(--glow-opacity) * 0.8)); + animation: pulse-cyan 3s ease-in-out infinite; +} + +body.playing-B::before { + border: none; + box-shadow: + 0 0 var(--glow-spread) rgba(188, 19, 254, var(--glow-opacity)), + inset 0 0 var(--glow-spread) rgba(188, 19, 254, calc(var(--glow-opacity) * 0.8)); + animation: pulse-magenta 3s ease-in-out infinite; +} + +body.playing-A.playing-B::before { + border: none; + box-shadow: + 0 0 var(--glow-spread) rgba(0, 243, 255, var(--glow-opacity)), + 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, var(--glow-opacity)), + inset 0 0 var(--glow-spread) rgba(0, 243, 255, calc(var(--glow-opacity) * 0.6)), + inset 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, calc(var(--glow-opacity) * 0.6)); + animation: pulse-both 3s ease-in-out infinite; +} + +body.listener-glow::before { + border: none; + box-shadow: + 0 0 var(--glow-spread) rgba(0, 80, 255, calc(var(--glow-opacity) * 1.5)), + 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, calc(var(--glow-opacity) * 1.5)), + 0 0 calc(var(--glow-spread) * 2.5) rgba(0, 243, 255, calc(var(--glow-opacity) * 0.5)), + inset 0 0 var(--glow-spread) rgba(0, 80, 255, calc(var(--glow-opacity) * 1)), + inset 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, calc(var(--glow-opacity) * 1)); + animation: pulse-listener 4s ease-in-out infinite; +} + +@keyframes pulse-listener { + + 0%, + 100% { + filter: hue-rotate(0deg) brightness(1.2); + box-shadow: + 0 0 var(--glow-spread) rgba(0, 80, 255, calc(var(--glow-opacity) * 1.5)), + 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, calc(var(--glow-opacity) * 1.5)), + inset 0 0 var(--glow-spread) rgba(0, 80, 255, calc(var(--glow-opacity) * 1)), + inset 0 0 calc(var(--glow-spread) * 1.5) rgba(188, 19, 254, calc(var(--glow-opacity) * 1)); + } + + 50% { + filter: hue-rotate(15deg) brightness(1.8); + box-shadow: + 0 0 calc(var(--glow-spread) * 1.5) rgba(0, 120, 255, calc(var(--glow-opacity) * 2.2)), + 0 0 calc(var(--glow-spread) * 2) rgba(220, 50, 255, calc(var(--glow-opacity) * 2.2)), + 0 0 calc(var(--glow-spread) * 4) rgba(0, 243, 255, calc(var(--glow-opacity) * 1)), + inset 0 0 calc(var(--glow-spread) * 1.5) rgba(0, 120, 255, calc(var(--glow-opacity) * 1.5)), + inset 0 0 calc(var(--glow-spread) * 2) rgba(220, 50, 255, calc(var(--glow-opacity) * 1.5)); + } +} + + + + +#start-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.95); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(10px); +} + +.overlay-title { + font-family: 'Orbitron', sans-serif; + font-size: 3rem; + color: var(--primary-cyan); + text-shadow: 0 0 20px var(--primary-cyan); + margin-bottom: 2rem; + letter-spacing: 5px; +} + +#start-btn { + padding: 15px 40px; + font-family: 'Orbitron', sans-serif; + font-size: 1.2rem; + background: transparent; + color: var(--primary-cyan); + border: 2px solid var(--primary-cyan); + cursor: pointer; + transition: all 0.3s; + box-shadow: 0 0 15px rgba(0, 243, 255, 0.2); +} + +#start-btn:hover { + background: var(--primary-cyan); + color: #000; + box-shadow: 0 0 30px var(--primary-cyan); +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + height: 60px; + box-sizing: border-box; + background: rgba(0, 0, 0, 0.5); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +header h1 { + font-family: 'Orbitron', sans-serif; + color: #fff; + margin: 0; + font-size: 1.5rem; + letter-spacing: 2px; +} + +.status-bar { + font-family: 'Orbitron', sans-serif; + font-size: 0.8rem; + color: #00ff00; + text-shadow: 0 0 5px #00ff00; +} + +/* LAYOUT GRID */ +.app-container { + display: grid; + grid-template-columns: 320px 1fr 1fr; + grid-template-rows: 1fr 80px; + gap: 10px; + padding: 10px; + height: calc(100vh - 60px); + /* Adjust based on header height */ + min-height: 0; + overflow: hidden; +} + +/* LIBRARY SECTION */ +.library-section { + grid-row: 1 / -1; + background: var(--panel-bg); + border: 2px solid var(--primary-cyan); + box-shadow: 0 0 20px rgba(0, 243, 255, 0.1); + border-radius: 10px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.lib-header { + padding: 15px; + background: rgba(255, 255, 255, 0.02); + border-bottom: var(--glass-border); + display: flex; + gap: 10px; + align-items: center; +} + +.lib-header input { + flex: 1; + padding: 10px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid #333; + color: #fff; + border-radius: 4px; + font-family: 'Rajdhani', sans-serif; +} + +.refresh-btn { + background: rgba(0, 243, 255, 0.1); + border: 1px solid var(--primary-cyan); + color: var(--primary-cyan); + border-radius: 4px; + padding: 8px 12px; + cursor: pointer; + font-size: 1.2rem; + transition: all 0.3s; +} + +.refresh-btn:hover { + background: rgba(0, 243, 255, 0.2); + box-shadow: 0 0 10px rgba(0, 243, 255, 0.3); +} + +.refresh-btn.spinning { + animation: spin 0.5s linear; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +/* YouTube Search */ +.youtube-search-container { + padding: 10px 15px; + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.youtube-search-container input { + width: 100%; + padding: 10px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid var(--primary-cyan); + color: #fff; + border-radius: 4px; + font-family: 'Rajdhani', sans-serif; + font-size: 0.9rem; + box-shadow: 0 0 10px rgba(0, 243, 255, 0.2); + transition: all 0.3s; +} + +.youtube-search-container input:focus { + outline: none; + border-color: var(--primary-cyan); + box-shadow: 0 0 15px rgba(0, 243, 255, 0.4); +} + +.youtube-results { + max-height: 300px; + overflow-y: auto; + margin-top: 10px; +} + +.youtube-result-card { + display: flex; + align-items: center; + gap: 10px; + padding: 8px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 4px; + margin-bottom: 6px; + transition: all 0.2s; + cursor: pointer; +} + +.youtube-result-card:hover { + background: rgba(255, 255, 255, 0.08); + border-color: var(--primary-cyan); + transform: translateX(2px); +} + +.result-thumbnail { + width: 80px; + height: 60px; + object-fit: cover; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.result-info { + flex: 1; + min-width: 0; +} + +.result-title { + font-size: 0.85rem; + font-weight: bold; + color: #fff; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-channel { + font-size: 0.75rem; + color: #888; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-download-btn { + padding: 8px 12px; + background: rgba(0, 243, 255, 0.2); + border: 1px solid var(--primary-cyan); + color: var(--primary-cyan); + border-radius: 4px; + cursor: pointer; + font-size: 1.2rem; + transition: all 0.2s; + flex-shrink: 0; +} + +.result-download-btn:hover { + background: rgba(0, 243, 255, 0.3); + box-shadow: 0 0 10px rgba(0, 243, 255, 0.4); + transform: scale(1.1); +} + +.result-download-btn:active { + transform: scale(0.95); +} + +.search-loading, +.search-error, +.search-success, +.search-empty { + padding: 15px; + text-align: center; + border-radius: 4px; + font-family: 'Orbitron', sans-serif; + font-size: 0.9rem; +} + +.search-loading { + background: rgba(255, 187, 0, 0.1); + color: #ffbb00; + border: 1px solid rgba(255, 187, 0, 0.3); +} + +.search-error { + background: rgba(255, 68, 68, 0.1); + color: #ff4444; + border: 1px solid rgba(255, 68, 68, 0.3); +} + +.search-success { + background: rgba(0, 255, 0, 0.1); + color: #00ff00; + border: 1px solid rgba(0, 255, 0, 0.3); +} + +.search-empty { + background: rgba(255, 255, 255, 0.03); + color: #666; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.library-list { + flex: 1; + overflow-y: auto; + padding: 10px; +} + +.track-row { + background: rgba(255, 255, 255, 0.03); + margin-bottom: 8px; + padding: 10px; + border-radius: 4px; + transition: 0.2s; + border-left: 3px solid transparent; +} + +.track-row:hover { + background: rgba(255, 255, 255, 0.08); + border-left: 3px solid var(--primary-cyan); +} + +.track-name { + display: block; + font-weight: bold; + margin-bottom: 5px; + color: #fff; +} + +.load-actions { + display: flex; + gap: 5px; +} + +.load-btn { + flex: 1; + border: none; + padding: 4px; + font-size: 0.7rem; + cursor: pointer; + font-family: 'Orbitron', sans-serif; + opacity: 0.7; + transition: 0.2s; +} + +.btn-a { + background: rgba(0, 243, 255, 0.2); + color: var(--primary-cyan); + border: 1px solid var(--primary-cyan); +} + +.btn-b { + background: rgba(188, 19, 254, 0.2); + color: var(--secondary-magenta); + border: 1px solid var(--secondary-magenta); +} + +.load-btn:hover { + opacity: 1; + box-shadow: 0 0 10px currentColor; +} + +/* DECKS */ +.deck { + background: var(--panel-bg); + border: var(--glass-border); + border-radius: 8px; + padding: 8px; + display: flex; + flex-direction: column; + position: relative; + transition: box-shadow 0.3s; + min-height: 0; + overflow-y: auto; +} + +#deck-A { + border: 2px solid var(--primary-cyan); + box-shadow: 0 0 25px rgba(0, 243, 255, 0.3); +} + +#deck-B { + border: 2px solid var(--secondary-magenta); + box-shadow: 0 0 25px rgba(188, 19, 254, 0.3); +} + +.deck.playing { + box-shadow: 0 0 45px rgba(0, 243, 255, 0.5); +} + +#deck-B.playing { + box-shadow: 0 0 45px rgba(188, 19, 254, 0.5); +} + +.deck-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; + background: #000; + padding: 4px 6px; + border-radius: 4px; + border: 1px solid #333; +} + +.deck-title { + font-family: 'Orbitron', sans-serif; + font-size: 1rem; + font-weight: bold; +} + +.title-a { + color: var(--primary-cyan); +} + +.title-b { + color: var(--secondary-magenta); +} + +.track-display { + font-family: 'Rajdhani', monospace; + color: #fff; + font-size: 0.9rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 200px; +} + +.blink { + animation: blinker 1s linear infinite; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} + +/* DISK */ +.disk-container { + display: flex; + justify-content: center; + margin-bottom: 6px; +} + +.dj-disk { + width: 90px; + height: 90px; + background: radial-gradient(circle, #222 10%, #111 11%, #000 100%); + border-radius: 50%; + border: 2px solid #333; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + cursor: pointer; + position: relative; + transition: transform 0.2s; +} + +.dj-disk::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 50%; + background: repeating-radial-gradient(#111 0, + #111 2px, + #181818 3px, + #181818 4px); + opacity: 0.3; + pointer-events: none; +} + +.dj-disk:active { + transform: scale(0.95); +} + +.disk-label { + width: 38px; + height: 38px; + background: #fff; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; + font-size: 0.9rem; + color: #000; + font-family: 'Orbitron', sans-serif; + z-index: 2; + border: 2px solid #ccc; +} + +#deck-A .disk-label { + background: var(--primary-cyan); + box-shadow: 0 0 10px var(--primary-cyan); +} + +#deck-B .disk-label { + background: var(--secondary-magenta); + box-shadow: 0 0 10px var(--secondary-magenta); +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +.deck.playing .dj-disk { + animation: rotate 2s linear infinite; + box-shadow: 0 0 20px rgba(0, 243, 255, 0.3); +} + +#deck-B.playing .dj-disk { + box-shadow: 0 0 20px rgba(188, 19, 254, 0.3); +} + +canvas { + width: 100%; + height: 60px; + background: #000; + border-radius: 4px; + margin-bottom: 6px; + border: 1px solid #333; +} + +/* SEARCH & DOWNLOAD */ +.start-group { + position: relative; + margin-bottom: 4px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.search-input { + width: 100%; + padding: 6px 8px; + background: #111; + border: 1px solid #444; + color: #fff; + font-family: 'Rajdhani', sans-serif; + font-size: 0.85rem; + border-radius: 4px; + box-sizing: border-box; +} + +.download-btn { + width: 100%; + padding: 6px; + background: var(--primary-cyan); + border: 1px solid var(--primary-cyan); + font-weight: bold; + cursor: pointer; + font-size: 0.8rem; + font-family: 'Orbitron', sans-serif; + color: #000; + border-radius: 4px; + transition: all 0.3s; + box-shadow: 0 0 10px rgba(0, 243, 255, 0.2); +} + +.download-btn:hover { + box-shadow: 0 0 20px rgba(0, 243, 255, 0.5); + transform: translateY(-1px); +} + +.download-btn:active { + transform: translateY(0); +} + +#deck-B .download-btn { + background: var(--secondary-magenta); + border-color: var(--secondary-magenta); + color: #fff; + box-shadow: 0 0 10px rgba(188, 19, 254, 0.2); +} + +#deck-B .download-btn:hover { + box-shadow: 0 0 20px rgba(188, 19, 254, 0.5); +} + + +.quality-selector { + background: rgba(10, 10, 12, 0.8); + color: var(--text-color); + border: 1px solid rgba(255, 255, 255, 0.2); + padding: 4px 6px; + border-radius: 4px; + font-family: 'Rajdhani', sans-serif; + font-size: 0.8rem; + outline: none; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.download-status .loading { + color: #ffa500; + animation: pulse 1.5s ease-in-out infinite; +} + +.download-status .success { + color: #00ff00; + font-weight: bold; +} + +.download-status .error { + color: #ff4444; + font-weight: bold; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +/* CONTROLS */ +.controls-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; + margin-top: auto; +} + +.fader-group { + display: flex; + flex-direction: column; + align-items: center; +} + +.fader-group label { + font-size: 0.65rem; + color: #888; + margin-bottom: 3px; +} + +input[type=range] { + width: 100%; + cursor: pointer; +} + +/* EQ */ +.eq-container { + display: flex; + justify-content: space-between; + margin: 4px 0; +} + +.eq-band { + display: flex; + flex-direction: column; + align-items: center; + width: 30%; +} + +.eq-band input { + writing-mode: bt-lr; + /* IE */ + -webkit-appearance: slider-vertical; + appearance: slider-vertical; + width: 8px; + height: 60px; +} + +/* TRANSPORT */ +.transport { + display: flex; + gap: 4px; + margin-top: 3px; +} + +.big-btn { + flex: 1; + padding: 6px; + border: none; + font-family: 'Orbitron', sans-serif; + font-weight: bold; + cursor: pointer; + font-size: 0.85rem; + transition: 0.1s; + background: #222; + color: #666; + border-bottom: 2px solid #000; +} + +.big-btn:active { + transform: translateY(2px); + border-bottom: 1px solid #000; +} + +.play-btn:hover { + background: #333; + color: #fff; +} + +.deck.playing .play-btn { + background: var(--primary-cyan); + color: #000; + box-shadow: 0 0 15px var(--primary-cyan); +} + +#deck-B.playing .play-btn { + background: var(--secondary-magenta); + color: #fff; + box-shadow: 0 0 15px var(--secondary-magenta); +} + +/* CROSSFADER */ +.mixer-section { + grid-column: 2 / 4; + grid-row: 2; + background: linear-gradient(to bottom, #1a1a1a, #0a0a0a); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + padding: 15px 40px; + border: 2px solid #444; + position: relative; + box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.8), inset 0 2px 10px rgba(255, 255, 255, 0.05); +} + +.mixer-section::before { + content: 'A'; + position: absolute; + left: 20px; + font-family: 'Orbitron', sans-serif; + font-size: 1.5rem; + font-weight: bold; + color: var(--primary-cyan); + text-shadow: 0 0 10px var(--primary-cyan); +} + +.mixer-section::after { + content: 'B'; + position: absolute; + right: 20px; + font-family: 'Orbitron', sans-serif; + font-size: 1.5rem; + font-weight: bold; + color: var(--secondary-magenta); + text-shadow: 0 0 10px var(--secondary-magenta); +} + +.xfader { + width: 100%; + appearance: none; + height: 12px; + background: linear-gradient(to right, + var(--primary-cyan) 0%, + #333 50%, + var(--secondary-magenta) 100%); + border-radius: 6px; + outline: none; + border: 2px solid #555; + box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 255, 255, 0.1); +} + +.xfader::-webkit-slider-thumb { + -webkit-appearance: none; + width: 60px; + height: 40px; + background: linear-gradient(145deg, #aaa, #666); + border: 3px solid #ccc; + border-radius: 8px; + cursor: grab; + box-shadow: 0 0 25px rgba(255, 255, 255, 0.4), + inset 0 -3px 8px rgba(0, 0, 0, 0.6); + transition: all 0.1s; +} + +.xfader::-webkit-slider-thumb:hover { + background: linear-gradient(145deg, #ccc, #888); + box-shadow: 0 0 35px rgba(255, 255, 255, 0.6); +} + +.xfader::-webkit-slider-thumb:active { + cursor: grabbing; + box-shadow: 0 0 40px rgba(255, 255, 255, 0.8); + transform: scale(1.05); +} + +.xfader::-moz-range-thumb { + width: 60px; + height: 40px; + background: linear-gradient(145deg, #aaa, #666); + border: 3px solid #ccc; + border-radius: 8px; + cursor: grab; + box-shadow: 0 0 25px rgba(255, 255, 255, 0.4); +} + +/* ====== NEW PROFESSIONAL DJ FEATURES ====== */ + +/* Waveform Display */ +.waveform-container { + position: relative; + background: #000; + border: 1px solid #333; + border-radius: 4px; + margin-bottom: 4px; + padding: 3px; +} + +.waveform-canvas { + width: 100%; + height: 40px; + display: block; +} + +.playhead { + position: absolute; + top: 10px; + bottom: 10px; + width: 2px; + background: #ff0; + box-shadow: 0 0 10px #ff0; + pointer-events: none; + left: 0%; + transform: translateX(-1px); + transition: left 0.1s linear; +} + +.time-display { + margin-top: 3px; + display: flex; + justify-content: space-between; + font-family: 'Orbitron', monospace; + font-size: 0.75rem; + color: #0ff; +} + +.bpm-display { + color: #f0f; + font-weight: bold; +} + +/* Hot Cues */ +.hot-cues { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 3px; + margin-bottom: 4px; +} + +.cue-btn { + padding: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid #444; + color: #888; + font-family: 'Orbitron', sans-serif; + font-size: 0.7rem; + cursor: pointer; + transition: all 0.2s; + border-radius: 4px; +} + +.cue-btn:hover { + background: rgba(255, 255, 255, 0.1); + border-color: #666; +} + +.cue-btn.cue-set { + background: rgba(0, 243, 255, 0.2); + border-color: var(--primary-cyan); + color: var(--primary-cyan); + box-shadow: 0 0 10px rgba(0, 243, 255, 0.3); +} + +#deck-B .cue-btn.cue-set { + background: rgba(188, 19, 254, 0.2); + border-color: var(--secondary-magenta); + color: var(--secondary-magenta); + box-shadow: 0 0 10px rgba(188, 19, 254, 0.3); +} + +.cue-btn.cue-triggered { + transform: scale(0.95); + box-shadow: 0 0 20px rgba(0, 243, 255, 0.8) !important; +} + +#deck-B .cue-btn.cue-triggered { + box-shadow: 0 0 20px rgba(188, 19, 254, 0.8) !important; +} + +@keyframes flash { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + transform: scale(1.05); + } +} + +/* Loop Controls */ +.loop-controls { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 3px; + margin-bottom: 4px; +} + +.loop-btn { + padding: 5px; + background: rgba(255, 100, 0, 0.1); + border: 1px solid #883300; + color: #ff6600; + font-family: 'Orbitron', sans-serif; + font-size: 0.7rem; + cursor: pointer; + transition: all 0.2s; + border-radius: 4px; +} + +.loop-btn:hover { + background: rgba(255, 100, 0, 0.2); + border-color: #ff6600; + box-shadow: 0 0 10px rgba(255, 100, 0, 0.3); +} + +.loop-btn:active { + transform: scale(0.95); +} + +/* Auto-Loop Beat Controls */ +.auto-loop-controls { + margin-bottom: 4px; + padding: 6px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.auto-loop-label { + display: block; + font-size: 0.6rem; + color: #666; + font-family: 'Orbitron', sans-serif; + margin-bottom: 4px; + text-align: center; + letter-spacing: 1px; +} + +.auto-loop-buttons { + display: flex; + justify-content: space-between; + gap: 3px; +} + +.auto-loop-btn { + flex: 1; + aspect-ratio: 1; + min-width: 0; + padding: 0; + background: rgba(255, 255, 255, 0.03); + border: 1.5px solid #333; + border-radius: 50%; + color: #666; + font-family: 'Orbitron', sans-serif; + font-size: 0.65rem; + font-weight: bold; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.auto-loop-btn:hover { + background: rgba(255, 255, 255, 0.08); + border-color: #555; + color: #aaa; + transform: scale(1.1); +} + +/* Active state for Deck A */ +#deck-A .auto-loop-btn.active { + background: rgba(0, 243, 255, 0.2); + border-color: var(--primary-cyan); + color: var(--primary-cyan); + box-shadow: 0 0 15px rgba(0, 243, 255, 0.5), + inset 0 0 10px rgba(0, 243, 255, 0.2); + transform: scale(1.05); +} + +#deck-A .auto-loop-btn:hover { + border-color: var(--primary-cyan); + color: var(--primary-cyan); +} + +/* Active state for Deck B */ +#deck-B .auto-loop-btn.active { + background: rgba(188, 19, 254, 0.2); + border-color: var(--secondary-magenta); + color: var(--secondary-magenta); + box-shadow: 0 0 15px rgba(188, 19, 254, 0.5), + inset 0 0 10px rgba(188, 19, 254, 0.2); + transform: scale(1.05); +} + +#deck-B .auto-loop-btn:hover { + border-color: var(--secondary-magenta); + color: var(--secondary-magenta); +} + +.auto-loop-btn:active { + transform: scale(0.95); +} + +/* Pitch Bend Buttons */ +.pitch-bend-buttons { + display: flex; + gap: 4px; + margin-top: 3px; + justify-content: center; +} + +.pitch-bend { + padding: 4px 12px; + background: #222; + border: 1px solid #444; + color: #fff; + font-family: 'Orbitron', sans-serif; + font-size: 0.85rem; + font-weight: bold; + cursor: pointer; + border-radius: 4px; + transition: all 0.1s; +} + +.pitch-bend:hover { + background: #333; + border-color: #666; +} + +.pitch-bend:active { + background: var(--primary-cyan); + color: #000; + box-shadow: 0 0 15px rgba(0, 243, 255, 0.5); +} + +#deck-B .pitch-bend:active { + background: var(--secondary-magenta); + color: #fff; + box-shadow: 0 0 15px rgba(188, 19, 254, 0.5); +} + +/* Sync Button */ +.sync-btn { + background: rgba(0, 255, 0, 0.1) !important; + border: 1px solid #0a0 !important; + color: #0f0 !important; +} + +.sync-btn:hover { + background: rgba(0, 255, 0, 0.2) !important; + box-shadow: 0 0 15px rgba(0, 255, 0, 0.3) !important; +} + +.reset-btn { + background: rgba(255, 140, 0, 0.1) !important; + border: 1px solid #ff8c00 !important; + color: #ffa500 !important; +} + +.reset-btn:hover { + background: rgba(255, 140, 0, 0.2) !important; + box-shadow: 0 0 15px rgba(255, 140, 0, 0.4) !important; + transform: rotate(15deg); +} + +.reset-btn:active { + transform: rotate(360deg) scale(0.95); +} + +/* Transport Buttons - Make room for 4 buttons */ +.transport { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 8px; + margin-top: 10px; +} + +/* Spinning Animation for Refresh */ +@keyframes rotate360 { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +.spinning { + animation: rotate360 0.5s linear; +} + +/* Quality Selector and Download Controls */ +.download-controls { + display: flex; + gap: 8px; +} + +.quality-selector { + background: #222; + border: 1px solid #444; + color: #0ff; + padding: 8px; + border-radius: 4px; + font-family: 'Orbitron', sans-serif; + font-size: 0.85rem; + cursor: pointer; +} + +.quality-selector option { + background: #111; + color: #0ff; +} + +#deck-B .quality-selector { + color: #f0f; +} + +/* Enhanced Crossfader Visibility */ +.mixer-section { + background: linear-gradient(to bottom, #1a1a1a, #0a0a0a) !important; + border: 2px solid #333 !important; + padding: 20px 40px !important; + box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.5) !important; +} + +.xfader { + height: 8px !important; + background: linear-gradient(to right, + var(--primary-cyan) 0%, + #555 50%, + var(--secondary-magenta) 100%) !important; + border-radius: 4px !important; +} + +.xfader::-webkit-slider-thumb { + width: 60px !important; + height: 40px !important; + background: linear-gradient(145deg, #888, #555) !important; + border: 3px solid #aaa !important; + border-radius: 6px !important; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.3), + inset 0 -2px 5px rgba(0, 0, 0, 0.5) !important; + cursor: grab !important; +} + +.xfader::-webkit-slider-thumb:active { + cursor: grabbing !important; + box-shadow: 0 0 30px rgba(255, 255, 255, 0.5) !important; +} + +.xfader::-moz-range-thumb { + width: 60px !important; + height: 40px !important; + background: linear-gradient(145deg, #888, #555) !important; + border: 3px solid #aaa !important; + border-radius: 6px !important; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.3) !important; + cursor: grab !important; +} + +/* Waveform drag cursor */ +.waveform-canvas:active { + cursor: grabbing !important; +} + +/* COMPACT MODE - Make everything fit at 100% zoom */ +.deck { + padding: 15px !important; + gap: 10px !important; +} + +.waveform-container { + margin-bottom: 10px !important; + padding: 5px !important; +} + +.waveform-canvas { + height: 60px !important; +} + +.hot-cues { + gap: 5px !important; + margin-bottom: 10px !important; +} + +.cue-btn { + padding: 6px !important; + font-size: 0.7rem !important; +} + +.loop-controls { + gap: 5px !important; + margin-bottom: 10px !important; +} + +.loop-btn { + padding: 6px !important; + font-size: 0.7rem !important; +} + +.start-group { + margin-bottom: 10px !important; +} + +.search-input { + padding: 8px !important; + font-size: 0.85rem !important; +} + +.download-btn { + padding: 8px 15px !important; + font-size: 0.85rem !important; +} + +.controls-grid { + gap: 10px !important; + margin-bottom: 10px !important; +} + +.eq-container { + gap: 8px !important; +} + +.eq-band input { + height: 80px !important; +} + +.fader-group label { + font-size: 0.75rem !important; + margin-bottom: 5px !important; +} + +.transport { + gap: 8px !important; + margin-top: 8px !important; +} + +.big-btn { + padding: 10px !important; + font-size: 0.9rem !important; +} + +.disk-container { + margin: 10px 0 !important; +} + +.dj-disk { + width: 120px !important; + height: 120px !important; +} + +.mixer-section { + padding: 15px 40px !important; +} + +.library-section { + padding: 15px !important; +} + +.track-row { + padding: 8px !important; + margin-bottom: 6px !important; +} + +.track-name { + font-size: 0.85rem !important; +} + +.load-btn { + padding: 6px 12px !important; + font-size: 0.75rem !important; +} + +#viz-A, +#viz-B { + height: 80px !important; +} + +.pitch-bend-buttons { + margin-top: 3px !important; +} + +.pitch-bend { + padding: 4px 12px !important; + font-size: 0.8rem !important; +} + +.quality-selector { + padding: 6px !important; + font-size: 0.75rem !important; +} + +.download-controls { + gap: 5px !important; +} + +/* === VOLUME FADERS === */ +.volume-fader { + writing-mode: bt-lr; + -webkit-appearance: slider-vertical; + appearance: slider-vertical; + width: 30px !important; + height: 100px !important; + background: linear-gradient(to top, #ff0000, #ffff00, #00ff00); + border-radius: 5px; +} + +/* === FILTER KNOBS === */ +.filter-knobs { + display: flex; + flex-direction: column; + gap: 10px; +} + +.filter-knob { + display: flex; + flex-direction: column; + align-items: center; +} + +.filter-knob label { + font-size: 0.7rem; + margin-bottom: 5px; + color: #ff8800; + font-family: 'Orbitron', sans-serif; +} + +.filter-slider { + width: 100px; + height: 6px; + background: linear-gradient(to right, #ff8800, #ffcc00); + border-radius: 3px; + outline: none; +} + +.filter-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 20px; + height: 20px; + background: #ff8800; + border: 2px solid #ffaa00; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 0 10px rgba(255, 136, 0, 0.5); +} + +.filter-slider::-moz-range-thumb { + width: 20px; + height: 20px; + background: #ff8800; + border: 2px solid #ffaa00; + border-radius: 50%; + cursor: pointer; +} + +/* === SAMPLE PADS === */ +.sample-pads { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 6px; + margin: 10px 0; +} + +.sample-pad { + padding: 10px 5px; + background: linear-gradient(145deg, #333, #1a1a1a); + border: 2px solid #444; + color: #fff; + font-family: 'Orbitron', sans-serif; + font-size: 0.75rem; + cursor: pointer; + border-radius: 6px; + transition: all 0.1s; + box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.3); +} + +.sample-pad:hover { + background: linear-gradient(145deg, #444, #2a2a2a); + border-color: #666; + box-shadow: 0 0 15px rgba(255, 255, 255, 0.2); +} + +.sample-pad:active { + transform: scale(0.9); + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 20px rgba(0, 255, 255, 0.4); +} + +#deck-A .sample-pad:active { + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 20px var(--primary-cyan); +} + +#deck-B .sample-pad:active { + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 20px var(--secondary-magenta); +} + +/* Adjust controls grid for new elements */ +.controls-grid { + display: grid; + grid-template-columns: auto repeat(3, 1fr) auto; + gap: 15px; + align-items: center; +} + +@keyframes glow-pulse { + + 0%, + 100% { + box-shadow: + inset 0 0 30px rgba(0, 243, 255, 0.3), + inset 0 0 60px rgba(188, 19, 254, 0.2), + 0 0 30px rgba(0, 243, 255, 0.4), + 0 0 60px rgba(188, 19, 254, 0.3); + } + + 50% { + box-shadow: + inset 0 0 40px rgba(0, 243, 255, 0.5), + inset 0 0 80px rgba(188, 19, 254, 0.4), + 0 0 40px rgba(0, 243, 255, 0.6), + 0 0 80px rgba(188, 19, 254, 0.5); + } +} + + +/* === DOWNLOAD PROGRESS BAR === */ +.download-status { + min-height: 30px; + margin-top: 8px; + position: relative; + text-align: center; + font-family: 'Orbitron', sans-serif; + color: #0ff; +} + +.progress-container { + width: 100%; + height: 20px; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + overflow: hidden; + border: 1px solid #333; + position: relative; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, + var(--primary-cyan) 0%, + var(--secondary-magenta) 100%); + border-radius: 10px; + transition: width 0.3s ease; + box-shadow: 0 0 10px var(--primary-cyan); + position: relative; + overflow: hidden; +} + +.progress-bar::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, + transparent 0%, + rgba(255, 255, 255, 0.3) 50%, + transparent 100%); + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } +} + +.progress-text { + font-size: 0.8rem; + font-family: 'Orbitron', sans-serif; + color: #fff; + padding: 5px; +} + +#deck-B .progress-bar { + background: linear-gradient(90deg, + var(--secondary-magenta) 0%, + var(--primary-cyan) 100%); + box-shadow: 0 0 10px var(--secondary-magenta); +} + +/* Remove static border, replace with dynamic one */ +/* === ULTRA COMPACT MODE - Ensure crossfader visible at 100% zoom === */ +.deck-header { + padding: 8px !important; + margin-bottom: 8px !important; +} + +.deck-title { + font-size: 1rem !important; +} + +.track-display { + font-size: 0.75rem !important; +} + +.waveform-container { + margin-bottom: 6px !important; + padding: 3px !important; +} + +.waveform-canvas { + height: 50px !important; +} + +.time-display { + font-size: 0.75rem !important; + margin-top: 3px !important; +} + +.disk-container { + margin: 6px 0 !important; +} + +.dj-disk { + width: 100px !important; + height: 100px !important; +} + +.disk-label { + font-size: 1.5rem !important; +} + +#viz-A, +#viz-B { + height: 60px !important; + margin-bottom: 6px !important; +} + +.hot-cues { + gap: 4px !important; + margin-bottom: 6px !important; +} + +.cue-btn { + padding: 4px !important; + font-size: 0.65rem !important; +} + +.loop-controls { + gap: 4px !important; + margin-bottom: 6px !important; +} + +.loop-btn { + padding: 4px !important; + font-size: 0.65rem !important; +} + +.start-group { + margin-bottom: 6px !important; +} + +.search-input { + padding: 6px !important; + font-size: 0.8rem !important; + margin-bottom: 4px !important; +} + +.download-btn { + padding: 6px 12px !important; + font-size: 0.8rem !important; +} + +.quality-selector { + padding: 5px !important; + font-size: 0.7rem !important; +} + +.controls-grid { + gap: 8px !important; + margin-bottom: 6px !important; +} + +.eq-band input { + height: 70px !important; +} + +.eq-band label { + font-size: 0.65rem !important; +} + +.volume-fader { + height: 70px !important; +} + +.fader-group label { + font-size: 0.65rem !important; + margin-bottom: 3px !important; +} + +.filter-knob label { + font-size: 0.6rem !important; + margin-bottom: 3px !important; +} + +.filter-slider { + width: 80px !important; +} + +.sample-pads { + gap: 4px !important; + margin: 6px 0 !important; +} + +.sample-pad { + padding: 6px 3px !important; + font-size: 0.65rem !important; +} + +.transport { + gap: 6px !important; + margin-top: 6px !important; +} + +.big-btn { + padding: 8px !important; + font-size: 0.85rem !important; +} + +.pitch-bend { + padding: 3px 10px !important; + font-size: 0.75rem !important; +} + +.mixer-section { + padding: 12px 40px !important; + min-height: auto !important; +} + +.mixer-section::before, +.mixer-section::after { + font-size: 1.2rem !important; +} + +.library-section { + padding: 10px !important; +} + +.library-header { + padding: 8px !important; + margin-bottom: 8px !important; +} + +.track-row { + padding: 6px !important; + margin-bottom: 4px !important; +} + +.track-name { + font-size: 0.8rem !important; +} + +.load-btn { + padding: 5px 10px !important; + font-size: 0.7rem !important; +} + +/* Make main container fit better */ +#app-container { + padding: 10px !important; + gap: 10px !important; +} + +/* === SETTINGS PANEL === */ +/* === MOBILE RESPONSIVE & TABS === */ +.mobile-tabs { + display: none; + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + width: 90%; + max-width: 400px; + background: rgba(15, 15, 25, 0.85); + border: 1px solid rgba(0, 243, 255, 0.3); + border-radius: 50px; + z-index: 10003; + justify-content: space-around; + padding: 8px; + backdrop-filter: blur(15px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 20px rgba(0, 243, 255, 0.1); +} + +.tab-btn { + background: none; + border: none; + color: var(--text-dim); + font-family: 'Orbitron', sans-serif; + font-size: 0.65rem; + padding: 10px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + flex: 1; + border-radius: 40px; +} + +.tab-icon { + font-size: 1.2rem; + margin-bottom: 2px; +} + +.tab-btn.active { + color: var(--primary-cyan); + background: rgba(0, 243, 255, 0.1); + text-shadow: 0 0 10px var(--primary-cyan); + box-shadow: inset 0 0 10px rgba(0, 243, 255, 0.2); +} + +@media (max-width: 1024px) { + body { + height: 100vh; + overflow: hidden; + /* Prevent body scroll, use container scroll */ + } + + body::before { + border-width: 2px; + } + + .app-container { + grid-template-columns: 1fr; + grid-template-rows: 1fr; + gap: 0; + padding: 5px; + padding-bottom: 90px; + height: 100vh; + overflow-y: auto; + } + + .mobile-tabs { + display: flex; + } + + .library-section, + #deck-A, + #deck-B, + .mixer-section { + display: none; + width: 100%; + height: 100%; + } + + .app-container.show-library .library-section { + display: flex; + } + + .app-container.show-deck-A #deck-A { + display: flex; + } + + .app-container.show-deck-B #deck-B { + display: flex; + } + + /* Mixer integration: Show mixer combined with active deck in a scrollable view */ + .app-container.show-deck-A .mixer-section, + .app-container.show-deck-B .mixer-section { + display: flex; + padding: 20px !important; + margin-top: 20px; + border-radius: 15px; + background: rgba(0, 0, 0, 0.3); + } + + .library-section { + height: calc(100vh - 120px); + } + + .deck { + padding: 15px; + min-height: min-content; + } + + .dj-disk { + width: 180px; + height: 180px; + margin: 20px auto; + } + + .waveform-container { + height: 100px; + } + + .controls-grid { + grid-template-columns: 1fr 1fr; + gap: 15px; + } + + .big-btn { + padding: 15px !important; + font-size: 1rem !important; + } + + .settings-btn { + bottom: 100px; + right: 20px; + width: 50px; + height: 50px; + } + + .volume-fader { + height: 220px !important; + } + + /* Library specific mobile fixes */ + .track-row { + flex-direction: row; + justify-content: space-between; + padding: 12px !important; + background: rgba(255, 255, 255, 0.03); + margin-bottom: 8px; + border-radius: 8px; + } + + .load-actions { + gap: 5px; + } + + .load-btn { + font-size: 0.6rem !important; + padding: 5px 8px !important; + } + + .track-name { + font-size: 0.9rem !important; + max-width: 50%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mixer-controls { + gap: 20px !important; + } + + .settings-btn { + bottom: 120px !important; + } + + .lib-header { + padding: 10px !important; + flex-direction: column; + gap: 8px !important; + } + + .lib-header input { + width: 100%; + background: rgba(0, 0, 0, 0.5) !important; + border: 1px solid var(--primary-cyan) !important; + box-shadow: 0 0 10px rgba(0, 243, 255, 0.1) inset; + } +} + +/* ========== LIVE STREAMING CONTROLS ========== */ + +/* Streaming Button */ +.streaming-btn { + position: fixed; + bottom: 25px; + right: 100px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(145deg, #222, #111); + border: 2px solid var(--secondary-magenta); + color: var(--secondary-magenta); + font-size: 1.8rem; + cursor: pointer; + z-index: 10000; + box-shadow: 0 0 20px rgba(188, 19, 254, 0.4); + transition: all 0.3s; + display: flex; + align-items: center; + justify-content: center; +} + +.streaming-btn:hover { + transform: scale(1.1); + box-shadow: 0 0 30px rgba(188, 19, 254, 0.6); +} + +.streaming-btn:active { + transform: scale(0.95); +} + +.upload-btn { + position: fixed; + bottom: 25px; + right: 170px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(145deg, #222, #111); + border: 2px solid var(--primary-cyan); + color: var(--primary-cyan); + font-size: 1.8rem; + cursor: pointer; + z-index: 10000; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.4); + transition: all 0.3s; + display: flex; + align-items: center; + justify-content: center; +} + +.upload-btn:hover { + transform: scale(1.1); + box-shadow: 0 0 30px rgba(0, 255, 255, 0.6); +} + +.upload-btn:active { + transform: scale(0.95); +} + +/* Streaming Panel */ +.streaming-panel { + position: fixed; + top: 0; + right: -400px; + height: 100vh; + width: 380px; + background: rgba(10, 10, 20, 0.98); + border-left: 2px solid var(--secondary-magenta); + padding: 30px; + z-index: 10001; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(15px); + overflow-y: auto; +} + +.streaming-panel.active { + right: 0 !important; +} + +.streaming-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + border-bottom: 1px solid rgba(188, 19, 254, 0.3); + padding-bottom: 15px; + font-family: 'Orbitron', sans-serif; + font-size: 1.2rem; + color: var(--secondary-magenta); +} + +.close-streaming { + background: none; + border: none; + color: var(--text-dim); + font-size: 1.5rem; + cursor: pointer; + transition: all 0.3s; +} + +.close-streaming:hover { + color: var(--secondary-magenta); + transform: rotate(90deg); +} + +.streaming-content { + display: flex; + flex-direction: column; + gap: 25px; +} + +/* Broadcast Controls */ +.broadcast-controls { + display: flex; + flex-direction: column; + gap: 15px; +} + +.broadcast-btn { + padding: 20px; + background: linear-gradient(145deg, #1a1a1a, #0a0a0a); + border: 2px solid var(--secondary-magenta); + color: var(--secondary-magenta); + font-family: 'Orbitron', sans-serif; + font-size: 1.1rem; + font-weight: bold; + cursor: pointer; + border-radius: 10px; + transition: all 0.3s; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + box-shadow: 0 0 20px rgba(188, 19, 254, 0.2); +} + +.broadcast-btn:hover { + background: linear-gradient(145deg, #2a2a2a, #1a1a1a); + box-shadow: 0 0 30px rgba(188, 19, 254, 0.4); + transform: translateY(-2px); +} + +.broadcast-btn.active { + background: var(--secondary-magenta); + color: #000; + box-shadow: 0 0 40px rgba(188, 19, 254, 0.6); + animation: pulse-broadcast 2s ease-in-out infinite; +} + +@keyframes pulse-broadcast { + + 0%, + 100% { + box-shadow: 0 0 40px rgba(188, 19, 254, 0.6); + } + + 50% { + box-shadow: 0 0 60px rgba(188, 19, 254, 0.9); + } +} + +.broadcast-icon { + font-size: 1.3rem; +} + +.broadcast-btn.active .broadcast-icon { + animation: blink-red 1.5s ease-in-out infinite; +} + +@keyframes blink-red { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.3; + } +} + +.broadcast-status { + text-align: center; + font-family: 'Rajdhani', sans-serif; + font-size: 0.9rem; + color: var(--text-dim); + padding: 8px; + background: rgba(0, 0, 0, 0.3); + border-radius: 5px; +} + +.broadcast-status.live { + color: var(--secondary-magenta); + font-weight: bold; + text-shadow: 0 0 10px var(--secondary-magenta); +} + +/* Listener Info */ +.listener-info { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(188, 19, 254, 0.3); + border-radius: 10px; + padding: 20px; +} + +.listener-count { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + font-family: 'Orbitron', sans-serif; + font-size: 2rem; + color: var(--secondary-magenta); +} + +.count-icon { + font-size: 2.5rem; +} + +.count-label { + font-size: 0.9rem; + color: var(--text-dim); + font-family: 'Rajdhani', sans-serif; +} + +/* Stream URL Section */ +.stream-url-section { + display: flex; + flex-direction: column; + gap: 10px; +} + +.stream-url-section label { + font-size: 0.9rem; + color: var(--text-dim); +} + +.url-copy-group { + display: flex; + gap: 5px; +} + +.url-copy-group input { + flex: 1; + padding: 10px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(188, 19, 254, 0.3); + color: var(--text-main); + border-radius: 5px; + font-family: 'Rajdhani', monospace; + font-size: 0.85rem; +} + +.copy-btn { + padding: 10px 15px; + background: rgba(188, 19, 254, 0.2); + border: 1px solid var(--secondary-magenta); + color: var(--secondary-magenta); + border-radius: 5px; + cursor: pointer; + font-size: 1.2rem; + transition: all 0.3s; +} + +.copy-btn:hover { + background: rgba(188, 19, 254, 0.3); + box-shadow: 0 0 15px rgba(188, 19, 254, 0.4); +} + +.copy-btn:active { + transform: scale(0.95); +} + +/* Stream Settings */ +.stream-settings { + padding: 15px; + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; +} + +.stream-settings label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + font-size: 1rem; +} + +.stream-settings input[type="checkbox"] { + width: 20px; + height: 20px; + cursor: pointer; +} + +/* Quality Selector */ +.quality-selector-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid rgba(188, 19, 254, 0.2); +} + +.quality-selector-group label { + font-size: 0.9rem; + color: var(--text-dim); + margin-bottom: 5px; +} + +.stream-quality-select { + padding: 10px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid var(--secondary-magenta); + color: var(--text-main); + border-radius: 5px; + font-family: 'Rajdhani', sans-serif; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s; +} + +.stream-quality-select:hover { + background: rgba(0, 0, 0, 0.7); + border-color: #ff00ff; + box-shadow: 0 0 10px rgba(188, 19, 254, 0.3); +} + +.stream-quality-select:focus { + outline: none; + box-shadow: 0 0 15px rgba(188, 19, 254, 0.5); +} + +.quality-hint { + font-size: 0.75rem; + color: var(--text-dim); + font-style: italic; + opacity: 0.7; +} + +/* ========== LISTENER MODE ========== */ + +.listener-mode { + position: fixed; + inset: 0; + background: linear-gradient(135deg, #0a0a12 0%, #1a0a1a 100%); + z-index: 10010; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px; +} + +.listener-header { + text-align: center; + margin-bottom: 40px; +} + +.listener-header h1 { + font-family: 'Orbitron', sans-serif; + font-size: 3rem; + color: var(--secondary-magenta); + text-shadow: 0 0 30px var(--secondary-magenta); + margin: 0 0 20px 0; +} + +.glow-text { + color: #fff; + text-shadow: 0 0 10px var(--secondary-magenta), 0 0 20px var(--secondary-magenta); + animation: text-glow-pulse 2s infinite ease-in-out; +} + +@keyframes text-glow-pulse { + + 0%, + 100% { + opacity: 0.8; + text-shadow: 0 0 10px var(--secondary-magenta); + } + + 50% { + opacity: 1; + text-shadow: 0 0 15px var(--secondary-magenta), 0 0 30px var(--secondary-magenta); + } +} + +.live-indicator { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 10px 20px; + background: rgba(188, 19, 254, 0.2); + border: 2px solid var(--secondary-magenta); + border-radius: 25px; + font-family: 'Orbitron', sans-serif; + font-size: 1.2rem; + color: var(--secondary-magenta); + box-shadow: 0 0 20px rgba(188, 19, 254, 0.4); +} + +.pulse-dot { + width: 12px; + height: 12px; + background: var(--secondary-magenta); + border-radius: 50%; + animation: pulse-dot 1.5s ease-in-out infinite; +} + +@keyframes pulse-dot { + + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + + 50% { + transform: scale(1.3); + opacity: 0.7; + } +} + +.listener-content { + max-width: 600px; + width: 100%; + background: rgba(10, 10, 20, 0.8); + border: 2px solid var(--secondary-magenta); + border-radius: 20px; + padding: 40px; + box-shadow: 0 0 40px rgba(188, 19, 254, 0.3); + backdrop-filter: blur(10px); +} + +.now-playing { + text-align: center; + font-family: 'Orbitron', sans-serif; + font-size: 1.5rem; + color: var(--text-main); + margin-bottom: 30px; + padding: 20px; + background: rgba(0, 0, 0, 0.3); + border-radius: 10px; + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.volume-control { + margin-bottom: 20px; +} + +.volume-control label { + display: block; + margin-bottom: 10px; + font-size: 1.1rem; + color: var(--text-dim); +} + +.volume-control input[type="range"] { + width: 100%; + height: 8px; +} + +.connection-status { + text-align: center; + font-family: 'Rajdhani', sans-serif; + font-size: 0.9rem; + color: var(--text-dim); + padding: 10px; + background: rgba(0, 0, 0, 0.3); + border-radius: 5px; +} + +.connection-status.connected { + color: #00ff00; + text-shadow: 0 0 10px #00ff00; +} + +.connection-status.disconnected { + color: #ff4444; +} + +/* Enable Audio Button */ +.enable-audio-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + padding: 30px 40px; + margin: 30px 0; + background: linear-gradient(145deg, #1a1a1a, #0a0a0a); + border: 3px solid var(--secondary-magenta); + border-radius: 15px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 0 30px rgba(188, 19, 254, 0.3); + font-family: 'Orbitron', sans-serif; +} + +.enable-audio-btn:hover { + background: linear-gradient(145deg, #2a2a2a, #1a1a1a); + box-shadow: 0 0 50px rgba(188, 19, 254, 0.6); + transform: translateY(-3px); + border-color: #ff00ff; +} + +.enable-audio-btn:active { + transform: translateY(-1px); + box-shadow: 0 0 40px rgba(188, 19, 254, 0.5); +} + +.enable-audio-btn .audio-icon { + font-size: 3rem; + animation: pulse-icon 2s ease-in-out infinite; +} + +@keyframes pulse-icon { + + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + + 50% { + transform: scale(1.1); + opacity: 0.8; + } +} + +.enable-audio-btn .audio-text { + font-size: 1.5rem; + font-weight: bold; + color: var(--secondary-magenta); + text-shadow: 0 0 10px var(--secondary-magenta); +} + +.enable-audio-btn .audio-subtitle { + font-size: 0.9rem; + color: var(--text-dim); + font-family: 'Rajdhani', sans-serif; +} + +/* Mobile Responsiveness for Listener Mode */ +@media (max-width: 768px) { + .listener-mode { + padding: 20px; + justify-content: flex-start; + padding-top: 60px; + } + + .listener-header h1 { + font-size: 2.2rem; + margin-bottom: 10px; + } + + .live-indicator { + font-size: 0.9rem; + padding: 6px 15px; + } + + .listener-content { + padding: 25px; + margin-top: 10px; + border-radius: 15px; + } + + .now-playing { + font-size: 1.1rem; + min-height: 80px; + line-height: 1.4; + margin-bottom: 20px; + } + + .volume-control label { + font-size: 0.9rem; + } +} + +/* Hide landscape prompt globally when listening-active class is present */ +body.listening-active .landscape-prompt { + display: none !important; +} + +/* Base Settings Button Fix */ +.keyboard-btn { + position: fixed; + bottom: 25px; + right: 100px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(145deg, #222, #111); + border: 2px solid #ffbb00; + color: #ffbb00; + font-size: 1.8rem; + cursor: pointer; + z-index: 10000; + box-shadow: 0 0 20px rgba(255, 187, 0, 0.4); + transition: all 0.3s; + display: flex; + align-items: center; + justify-content: center; +} + +.keyboard-btn:hover { + transform: scale(1.1) rotate(5deg); + box-shadow: 0 0 30px rgba(255, 187, 0, 0.6); +} + +.settings-btn { + position: fixed; + bottom: 25px; + right: 25px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(145deg, #222, #111); + border: 2px solid var(--primary-cyan); + color: var(--primary-cyan); + font-size: 1.8rem; + cursor: pointer; + z-index: 10000; + box-shadow: 0 0 20px rgba(0, 243, 255, 0.4); + transition: all 0.3s; + display: flex; + align-items: center; + justify-content: center; +} + +.settings-panel { + position: fixed; + top: 0; + right: -350px; + height: 100vh; + width: 320px; + background: rgba(10, 10, 20, 0.98); + border-left: 1px solid var(--primary-cyan); + padding: 30px; + z-index: 10001; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(15px); +} + +.settings-panel.active { + right: 0 !important; +} + +.settings-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + border-bottom: 1px solid rgba(0, 243, 255, 0.3); + padding-bottom: 15px; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 20px; +} + +.setting-item label { + display: flex; + align-items: center; + gap: 15px; + font-size: 1.1rem; + cursor: pointer; +} + +/* Pulsing animations for edge border */ +@keyframes pulse-cyan { + + 0%, + 100% { + box-shadow: + inset 0 0 0 10px var(--primary-cyan), + inset 0 0 80px rgba(0, 243, 255, 1), + inset 0 0 150px rgba(0, 243, 255, 0.7); + } + + 50% { + box-shadow: + inset 0 0 0 12px var(--primary-cyan), + inset 0 0 120px rgba(0, 243, 255, 1), + inset 0 0 200px rgba(0, 243, 255, 0.9); + } +} + +@keyframes pulse-magenta { + + 0%, + 100% { + box-shadow: + inset 0 0 0 10px var(--secondary-magenta), + inset 0 0 80px rgba(188, 19, 254, 1), + inset 0 0 150px rgba(188, 19, 254, 0.7); + } + + 50% { + box-shadow: + inset 0 0 0 12px var(--secondary-magenta), + inset 0 0 120px rgba(188, 19, 254, 1), + inset 0 0 200px rgba(188, 19, 254, 0.9); + } +} + +@keyframes pulse-both { + + 0%, + 100% { + box-shadow: + inset 0 0 0 10px var(--primary-cyan), + inset 0 0 80px rgba(0, 243, 255, 0.9), + inset 0 0 120px rgba(188, 19, 254, 0.9); + } + + 50% { + box-shadow: + inset 0 0 0 12px var(--primary-cyan), + inset 0 0 120px rgba(0, 243, 255, 1), + inset 0 0 180px rgba(188, 19, 254, 1); + } +} + +/* Enhanced Mobile Improvements */ +@media (max-width: 1024px) { + + /* Larger tap targets */ + button, + .cue-btn, + .loop-btn { + min-height: 44px; + min-width: 44px; + font-size: 0.9rem; + } + + /* Better spacing */ + .hot-cues, + .loop-controls { + gap: 8px; + } + + /* Larger sliders */ + input[type="range"] { + height: 40px; + } + + /* Bigger disk for easier touch */ + .dj-disk { + width: 180px; + height: 180px; + } + + .disk-label { + width: 60px; + height: 60px; + font-size: 1.5rem; + } + + /* Better waveform touch area */ + .waveform-canvas { + min-height: 100px; + } + + /* Larger text */ + .track-display { + font-size: 1rem; + } + + .time-display { + font-size: 1rem; + } + + /* Better tab buttons */ + .mobile-tabs { + padding: 12px 0; + } + + .tab-btn { + padding: 12px 20px; + font-size: 1rem; + } + + /* Settings panel improvements */ + .setting-item { + padding: 12px; + font-size: 1rem; + } + + /* Better library items */ + .track-row { + padding: 12px; + min-height: 60px; + } + + .track-name { + font-size: 1rem; + } + + .load-btn { + padding: 10px 16px; + font-size: 0.9rem; + } +} + +@media (max-width: 768px) { + + /* Even larger for phones */ + .dj-disk { + width: 200px; + height: 200px; + } + + .disk-label { + width: 70px; + height: 70px; + font-size: 1.8rem; + } + + /* Stack controls vertically */ + .controls { + flex-direction: column; + gap: 15px; + } + + /* Larger crossfader */ + .crossfader-section { + padding: 20px; + } + + #crossfader { + height: 50px; + } + + /* Better EQ controls */ + .eq-controls, + .filter-controls { + gap: 15px; + } + + .eq-knob, + .filter-knob { + min-width: 80px; + } + + /* Larger speed control */ + .speed-control { + padding: 15px; + } + + .speed-slider { + height: 50px; + } + + /* Better pitch bend buttons */ + .pitch-bend-controls button { + min-width: 60px; + min-height: 50px; + font-size: 1.2rem; + } +} + +@media (max-width: 480px) { + + /* Extra small phones */ + body { + font-size: 14px; + } + + .deck-title { + font-size: 1rem; + } + + /* Compact hot cues */ + .hot-cues { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + + .cue-btn { + padding: 12px; + } + + /* Compact loop controls */ + .loop-controls { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 6px; + } + + /* Smaller VU meters */ + canvas#viz-A, + canvas#viz-B { + height: 60px; + } + + /* Compact library */ + .lib-header input { + font-size: 0.9rem; + } + + .track-row { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .load-actions { + width: 100%; + justify-content: space-between; + } +} + +/* ========== LANDSCAPE MODE OPTIMIZATIONS (Phone Sideways) ========== */ +@media (max-width: 1024px) and (orientation: landscape) { + + /* Override the portrait tab system - show both decks */ + .mobile-tabs { + display: none !important; + } + + .app-container { + display: grid !important; + grid-template-columns: 1fr 1fr !important; + grid-template-rows: 1fr auto !important; + gap: 8px !important; + padding: 8px !important; + padding-bottom: 8px !important; + height: 100vh; + overflow: hidden; + } + + /* Hide library in landscape - focus on DJing */ + .library-section { + display: none !important; + } + + /* Show both decks side by side */ + #deck-A, + #deck-B { + display: flex !important; + flex-direction: column; + padding: 8px !important; + overflow-y: auto; + height: 100%; + border-radius: 8px; + } + + /* Show crossfader at bottom spanning both decks */ + .mixer-section { + display: flex !important; + grid-column: 1 / 3 !important; + grid-row: 2 !important; + padding: 8px 30px !important; + min-height: 60px !important; + height: 60px !important; + } + + /* Compact deck headers */ + .deck-header { + padding: 6px 8px !important; + margin-bottom: 6px !important; + flex-shrink: 0; + } + + .deck-title { + font-size: 0.9rem !important; + } + + .track-display { + font-size: 0.7rem !important; + max-width: 150px !important; + } + + /* Compact waveform */ + .waveform-container { + margin-bottom: 6px !important; + padding: 6px !important; + min-height: auto !important; + } + + .waveform-canvas { + height: 50px !important; + min-height: 50px !important; + } + + .time-display { + font-size: 0.7rem !important; + margin-top: 3px !important; + } + + /* Smaller disk */ + .disk-container { + margin-bottom: 6px !important; + } + + .dj-disk { + width: 80px !important; + height: 80px !important; + } + + .disk-label { + width: 30px !important; + height: 30px !important; + font-size: 0.9rem !important; + } + + /* Hide VU meters to save space */ + canvas#viz-A, + canvas#viz-B { + display: none !important; + } + + /* Compact hot cues */ + .hot-cues { + grid-template-columns: repeat(4, 1fr) !important; + gap: 4px !important; + margin-bottom: 6px !important; + } + + .cue-btn { + padding: 6px 4px !important; + font-size: 0.65rem !important; + min-height: 32px !important; + } + + /* Compact loop controls */ + .loop-controls { + gap: 4px !important; + margin-bottom: 6px !important; + } + + .loop-btn { + padding: 6px 4px !important; + font-size: 0.65rem !important; + min-height: 32px !important; + } + + /* Hide download section to save space */ + .start-group { + display: none !important; + } + + /* Compact controls grid */ + .controls-grid { + grid-template-columns: auto auto auto auto !important; + gap: 6px !important; + margin-top: 6px !important; + } + + /* Compact faders */ + .fader-group { + min-width: 50px; + } + + .fader-group label { + font-size: 0.6rem !important; + margin-bottom: 2px !important; + } + + .volume-fader { + height: 80px !important; + width: 8px !important; + } + + /* Compact EQ */ + .eq-container { + margin: 0 !important; + gap: 4px; + } + + .eq-band { + width: auto !important; + } + + .eq-band input { + height: 80px !important; + width: 8px !important; + } + + .eq-band label { + font-size: 0.6rem !important; + } + + /* Compact filters */ + .filter-knobs { + display: flex; + flex-direction: column; + gap: 4px; + } + + .filter-knob { + min-width: auto !important; + } + + .filter-knob label { + font-size: 0.55rem !important; + margin-bottom: 2px !important; + } + + .filter-slider { + width: 60px !important; + height: 6px !important; + } + + /* Compact pitch control */ + .speed-slider { + height: 6px !important; + } + + .pitch-bend-buttons { + gap: 4px; + margin-top: 4px; + } + + .pitch-bend { + padding: 4px 8px !important; + font-size: 0.7rem !important; + min-height: 28px !important; + } + + /* Compact transport buttons */ + .transport { + gap: 4px !important; + margin-top: 6px !important; + } + + .big-btn { + padding: 8px 6px !important; + font-size: 0.75rem !important; + min-height: 36px !important; + } + + /* Prominent crossfader */ + .xfader { + height: 16px !important; + } + + .xfader::-webkit-slider-thumb { + width: 50px !important; + height: 40px !important; + } + + .xfader::-moz-range-thumb { + width: 50px !important; + height: 40px !important; + } + + .mixer-section::before, + .mixer-section::after { + font-size: 1.3rem !important; + top: 50%; + transform: translateY(-50%); + } + + .mixer-section::before { + left: 10px !important; + } + + .mixer-section::after { + right: 10px !important; + } + + /* Settings button adjustment */ + .settings-btn { + bottom: 70px !important; + right: 10px !important; + width: 40px !important; + height: 40px !important; + font-size: 1.2rem !important; + } + + /* Reduce edge border effect intensity */ + body::before { + border: 2px solid rgba(80, 80, 80, 0.3) !important; + } + + body.playing-A::before { + border: 10px solid var(--primary-cyan) !important; + box-shadow: + 0 0 60px rgba(0, 243, 255, 1), + inset 0 0 60px rgba(0, 243, 255, 0.7) !important; + } + + body.playing-B::before { + border: 10px solid var(--secondary-magenta) !important; + box-shadow: + 0 0 60px rgba(188, 19, 254, 1), + inset 0 0 60px rgba(188, 19, 254, 0.7) !important; + } + + body.playing-A.playing-B::before { + border: 10px solid var(--primary-cyan) !important; + box-shadow: + 0 0 60px rgba(0, 243, 255, 1), + 0 0 80px rgba(188, 19, 254, 1), + inset 0 0 60px rgba(0, 243, 255, 0.6), + inset 0 0 80px rgba(188, 19, 254, 0.6) !important; + } +} + +/* Extra compact for very small landscape screens (phones) */ +@media (max-width: 768px) and (orientation: landscape) { + .deck-header { + flex-direction: column; + align-items: flex-start; + gap: 2px; + } + + .track-display { + max-width: 100% !important; + } + + /* Even more compact controls */ + .hot-cues { + grid-template-columns: repeat(2, 1fr) !important; + } + + .controls-grid { + grid-template-columns: auto auto !important; + } + + .filter-knobs { + display: none !important; + } +} + +/* Tiny landscape screens */ +@media (max-width: 640px) and (orientation: landscape) { + .app-container { + gap: 4px !important; + padding: 4px !important; + } + + #deck-A, + #deck-B { + padding: 4px !important; + } + + .waveform-canvas { + height: 40px !important; + } + + .dj-disk { + width: 60px !important; + height: 60px !important; + } + + .disk-label { + width: 24px !important; + height: 24px !important; + font-size: 0.7rem !important; + } + + .hot-cues, + .loop-controls { + gap: 2px !important; + } + + .cue-btn, + .loop-btn { + font-size: 0.55rem !important; + padding: 4px 2px !important; + min-height: 28px !important; + } +} + +/* Touch-friendly hover states */ +@media (hover: none) { + + button:hover, + .cue-btn:hover, + .loop-btn:hover { + transform: none; + } + + button:active, + .cue-btn:active, + .loop-btn:active { + transform: scale(0.95); + opacity: 0.8; + } +} + +/* Landscape orientation prompt */ +.landscape-prompt { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.95); + border: 2px solid var(--primary-cyan); + border-radius: 15px; + padding: 30px; + z-index: 10005; + text-align: center; + max-width: 80%; + box-shadow: 0 0 40px rgba(0, 243, 255, 0.5); + backdrop-filter: blur(20px); +} + +.landscape-prompt h2 { + font-family: 'Orbitron', sans-serif; + color: var(--primary-cyan); + margin: 0 0 15px 0; + font-size: 1.3rem; + text-shadow: 0 0 10px var(--primary-cyan); +} + +.landscape-prompt p { + color: var(--text-main); + margin: 0 0 20px 0; + font-size: 1rem; + line-height: 1.5; +} + +.landscape-prompt .rotate-icon { + font-size: 3rem; + margin-bottom: 15px; + animation: rotate-hint 2s ease-in-out infinite; +} + +@keyframes rotate-hint { + + 0%, + 100% { + transform: rotate(0deg); + } + + 50% { + transform: rotate(90deg); + } +} + +.landscape-prompt button { + background: var(--primary-cyan); + color: #000; + border: none; + padding: 12px 30px; + font-family: 'Orbitron', sans-serif; + font-size: 1rem; + border-radius: 5px; + cursor: pointer; + box-shadow: 0 0 20px rgba(0, 243, 255, 0.4); + transition: all 0.3s; +} + +.landscape-prompt button:hover { + box-shadow: 0 0 30px rgba(0, 243, 255, 0.6); + transform: translateY(-2px); +} + +/* Show prompt on mobile portrait */ +@media (max-width: 1024px) and (orientation: portrait) { + .landscape-prompt { + display: block; + } + + .landscape-prompt.dismissed { + display: none; + } +} + +/* ========================================== + QUEUE SYSTEM STYLING + ========================================== */ + +.queue-panel { + background: rgba(10, 10, 20, 0.9); + border: 2px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + margin-top: 15px; + padding: 10px; + max-height: 200px; + display: flex; + flex-direction: column; +} + +.queue-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.queue-title { + font-family: 'Orbitron', sans-serif; + font-size: 0.9rem; + font-weight: bold; + color: #0ff; +} + +#queue-panel-B .queue-title { + color: #f0f; +} + +.queue-clear-btn { + background: rgba(255, 0, 0, 0.1); + border: 1px solid #f00; + color: #f00; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.2s; +} + +.queue-clear-btn:hover { + background: rgba(255, 0, 0, 0.2); + box-shadow: 0 0 10px rgba(255, 0, 0, 0.3); +} + +.queue-list { + flex: 1; + overflow-y: auto; + min-height: 50px; +} + +.queue-empty { + text-align: center; + padding: 20px; + color: #666; + font-size: 0.85rem; + font-style: italic; +} + +.queue-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + margin-bottom: 5px; + background: rgba(255, 255, 255, 0.03); + border-radius: 4px; + border-left: 3px solid #0ff; + cursor: move; + transition: all 0.2s; +} + +#queue-panel-B .queue-item { + border-left-color: #f0f; +} + +.queue-item:hover { + background: rgba(255, 255, 255, 0.08); + transform: translateX(3px); +} + +.queue-item.dragging { + opacity: 0.5; +} + +.queue-number { + font-family: 'Orbitron', sans-serif; + font-size: 0.8rem; + color: #0ff; + min-width: 20px; +} + +#queue-panel-B .queue-number { + color: #f0f; +} + +.queue-track-title { + flex: 1; + font-size: 0.85rem; + color: #fff; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.queue-actions { + display: flex; + gap: 4px; +} + +.queue-load-btn, +.queue-remove-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + padding: 4px 8px; + border-radius: 3px; + cursor: pointer; + font-size: 0.8rem; + transition: all 0.2s; +} + +.queue-load-btn:hover { + background: rgba(0, 255, 0, 0.2); + border-color: #0f0; + color: #0f0; +} + +.queue-remove-btn:hover { + background: rgba(255, 0, 0, 0.2); + border-color: #f00; + color: #f00; +} + +/* Queue buttons in library */ +.queue-btn-a, +.queue-btn-b { + font-size: 0.65rem !important; + padding: 4px 6px !important; +} + +.queue-btn-a { + background: rgba(0, 243, 255, 0.15) !important; + border-color: rgba(0, 243, 255, 0.4) !important; +} + +.queue-btn-b { + background: rgba(188, 19, 254, 0.15) !important; + border-color: rgba(188, 19, 254, 0.4) !important; +} + +.queue-btn-a:hover, +.queue-btn-b:hover { + opacity: 1 !important; + transform: scale(1.05); +} + +/* Update load-actions to fit 4 buttons */ +.load-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; +} + +/* ========================================== + KEYBOARD SETTINGS PANEL + ========================================== */ + +.keyboard-mapping-item { + display: flex; + align-items: center; + gap: 15px; + padding: 12px; + margin-bottom: 8px; + background: rgba(255, 255, 255, 0.03); + border-radius: 6px; + border-left: 3px solid transparent; + transition: all 0.2s; +} + +.keyboard-mapping-item:hover { + background: rgba(255, 255, 255, 0.06); + border-left-color: #0ff; +} + +.keyboard-mapping-item.listening { + background: rgba(255, 255, 0, 0.1); + border-left-color: #ff0; + animation: pulse 1s infinite; +} + +.key-display { + font-family: 'Orbitron', monospace; + font-size: 0.9rem; + font-weight: bold; + color: #0ff; + background: rgba(0, 243, 255, 0.1); + padding: 6px 12px; + border-radius: 4px; + border: 1px solid rgba(0, 243, 255, 0.3); + min-width: 60px; + text-align: center; +} + +.key-arrow { + color: #666; + font-size: 1.2rem; +} + +.action-label { + flex: 1; + color: #ccc; + font-size: 0.9rem; +} + +.key-reassign-btn { + background: rgba(0, 243, 255, 0.1); + border: 1px solid #0ff; + color: #0ff; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + font-family: 'Orbitron', sans-serif; + transition: all 0.2s; +} + +.key-reassign-btn:hover { + background: rgba(0, 243, 255, 0.2); + box-shadow: 0 0 10px rgba(0, 243, 255, 0.3); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + font-family: 'Orbitron', sans-serif; + font-size: 0.85rem; + transition: all 0.2s; +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.1); + border-color: #0ff; + color: #0ff; +} + +#keyboard-settings-panel { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.9); + width: 90%; + max-width: 600px; + max-height: 80vh; + background: rgba(10, 10, 20, 0.98); + border: 2px solid #0ff; + border-radius: 10px; + box-shadow: 0 0 50px rgba(0, 243, 255, 0.3); + z-index: 10000; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; + display: flex; + flex-direction: column; +} + +#keyboard-settings-panel.active { + opacity: 1; + pointer-events: all; + transform: translate(-50%, -50%) scale(1); +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.panel-header h2 { + margin: 0; + font-family: 'Orbitron', sans-serif; + color: #0ff; + font-size: 1.3rem; +} + +.panel-header button { + background: rgba(255, 0, 0, 0.1); + border: 1px solid #f00; + color: #f00; + width: 30px; + height: 30px; + border-radius: 50%; + cursor: pointer; + font-size: 1.2rem; + transition: all 0.2s; +} + +.panel-header button:hover { + background: rgba(255, 0, 0, 0.2); + transform: rotate(90deg); +} + +.panel-content { + padding: 20px; + overflow-y: auto; + flex: 1; +} + +#keyboard-mappings-list { + max-height: 400px; + overflow-y: auto; +} + +/* Scrollbar styling */ +#keyboard-mappings-list::-webkit-scrollbar { + width: 8px; +} + +#keyboard-mappings-list::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; +} + +#keyboard-mappings-list::-webkit-scrollbar-thumb { + background: rgba(0, 243, 255, 0.3); + border-radius: 4px; +} + +#keyboard-mappings-list::-webkit-scrollbar-thumb:hover { + background: rgba(0, 243, 255, 0.5); +} + +/* ========================================== + LIBRARY TRACK HIGHLIGHTING + ========================================== */ + +/* Track loaded on Deck A (Cyan) */ +.track-row.loaded-deck-a { + border-left: 4px solid var(--primary-cyan); + background: linear-gradient(90deg, rgba(0, 243, 255, 0.15) 0%, rgba(0, 243, 255, 0.03) 100%); +} + +.track-row.loaded-deck-a .track-name { + color: var(--primary-cyan); + font-weight: bold; +} + +.track-row.loaded-deck-a::before { + content: '▶ A'; + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.7rem; + font-weight: bold; + color: var(--primary-cyan); + font-family: 'Orbitron', sans-serif; +} + +/* Track loaded on Deck B (Magenta) */ +.track-row.loaded-deck-b { + border-left: 4px solid var(--secondary-magenta); + background: linear-gradient(90deg, rgba(188, 19, 254, 0.15) 0%, rgba(188, 19, 254, 0.03) 100%); +} + +.track-row.loaded-deck-b .track-name { + color: var(--secondary-magenta); + font-weight: bold; +} + +.track-row.loaded-deck-b::before { + content: '▶ B'; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.7rem; + font-weight: bold; + color: var(--secondary-magenta); + font-family: 'Orbitron', sans-serif; +} + +/* Track loaded on BOTH decks */ +.track-row.loaded-both { + border-left: 4px solid var(--primary-cyan); + border-right: 4px solid var(--secondary-magenta); + background: linear-gradient(90deg, + rgba(0, 243, 255, 0.15) 0%, + rgba(188, 19, 254, 0.15) 100%); +} + +.track-row.loaded-both .track-name { + background: linear-gradient(90deg, var(--primary-cyan), var(--secondary-magenta)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: bold; +} + +.track-row.loaded-both::before { + content: '▶ A'; + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.7rem; + font-weight: bold; + color: var(--primary-cyan); + font-family: 'Orbitron', sans-serif; +} + +.track-row.loaded-both::after { + content: '▶ B'; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.7rem; + font-weight: bold; + color: var(--secondary-magenta); + font-family: 'Orbitron', sans-serif; +} + +/* Ensure track-row is positioned for absolute children */ +.track-row { + position: relative; + padding-left: 40px; + padding-right: 40px; +} \ No newline at end of file