Spaces:
Running
Running
| <html lang="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Void Glider - Neural Code Randomizer</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/Flip.min.js"></script> | |
| <style> | |
| :root { | |
| --obsidian: #0a0b0c; | |
| --sulfur: #e0f000; | |
| --cyan: #00ffff; | |
| --purple: #7000ff; | |
| --pink: #ff0055; | |
| --glass-bg: rgba(20, 20, 35, 0.7); | |
| --glass-border: rgba(255, 255, 255, 0.1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| background: var(--obsidian); | |
| color: #e0e0e0; | |
| font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| background: radial-gradient(ellipse at top, #1a1a2e 0%, #0a0a12 50%, #050508 100%); | |
| } | |
| /* Animated Background Orbs */ | |
| .bg-orbs { | |
| position: fixed; | |
| top: 0; left: 0; width: 100vw; height: 100vh; | |
| z-index: -1; | |
| overflow: hidden; | |
| pointer-events: none; | |
| } | |
| .orb { | |
| position: absolute; | |
| border-radius: 50%; | |
| filter: blur(100px); | |
| opacity: 0.3; | |
| animation: float 25s infinite alternate ease-in-out; | |
| } | |
| .orb:nth-child(1) { width: 500px; height: 500px; background: var(--purple); top: -20%; left: -10%; } | |
| .orb:nth-child(2) { width: 400px; height: 400px; background: var(--cyan); bottom: -15%; right: -5%; animation-delay: -8s; } | |
| .orb:nth-child(3) { width: 300px; height: 300px; background: var(--pink); top: 50%; left: 30%; animation-delay: -15s; } | |
| @keyframes float { | |
| 0% { transform: translate(0, 0) scale(1) rotate(0deg); } | |
| 100% { transform: translate(80px, 60px) scale(1.15) rotate(15deg); } | |
| } | |
| /* Main Container */ | |
| .app-container { | |
| width: 95%; | |
| max-width: 1200px; | |
| margin: 30px auto; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(25px); | |
| -webkit-backdrop-filter: blur(25px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 28px; | |
| padding: 30px; | |
| box-shadow: 0 30px 60px -15px rgba(0, 0, 0, 0.8), inset 0 0 30px rgba(255, 255, 255, 0.02); | |
| } | |
| /* Header Section */ | |
| .header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| margin-bottom: 25px; | |
| padding-bottom: 20px; | |
| border-bottom: 1px solid var(--glass-border); | |
| } | |
| .logo { | |
| font-family: 'Courier New', monospace; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| background: linear-gradient(90deg, var(--cyan), var(--purple)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| text-shadow: 0 0 30px rgba(0, 255, 255, 0.3); | |
| } | |
| .logo span { | |
| color: var(--sulfur); | |
| -webkit-text-fill-color: var(--sulfur); | |
| } | |
| /* Context Buttons */ | |
| .context-selector { | |
| display: flex; | |
| gap: 8px; | |
| background: rgba(0, 0, 0, 0.4); | |
| padding: 6px; | |
| border-radius: 14px; | |
| border: 1px solid var(--glass-border); | |
| } | |
| .context-btn { | |
| background: transparent; | |
| border: none; | |
| color: #777; | |
| padding: 10px 22px; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .context-btn::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(135deg, var(--cyan), var(--purple)); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .context-btn.active { | |
| color: #fff; | |
| box-shadow: 0 0 20px rgba(0, 240, 255, 0.3), 0 0 20px rgba(112, 0, 255, 0.2); | |
| } | |
| .context-btn.active::before { | |
| opacity: 0.15; | |
| } | |
| /* Action Buttons */ | |
| .action-buttons { | |
| display: flex; | |
| gap: 12px; | |
| } | |
| .btn { | |
| padding: 12px 28px; | |
| border-radius: 12px; | |
| border: none; | |
| font-weight: bold; | |
| letter-spacing: 1px; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| font-size: 0.8rem; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn-shuffle { | |
| background: linear-gradient(135deg, var(--purple), #4a00e0); | |
| color: white; | |
| box-shadow: 0 0 25px rgba(112, 0, 255, 0.4); | |
| } | |
| .btn-shuffle:hover { | |
| box-shadow: 0 0 40px rgba(112, 0, 255, 0.7); | |
| transform: translateY(-3px); | |
| } | |
| .btn-timer { | |
| background: transparent; | |
| color: var(--cyan); | |
| border: 2px solid var(--cyan); | |
| box-shadow: 0 0 15px rgba(0, 240, 255, 0.2) inset; | |
| } | |
| .btn-timer.active { | |
| background: var(--cyan); | |
| color: #000; | |
| box-shadow: 0 0 30px rgba(0, 240, 255, 0.6); | |
| } | |
| /* Main Content Grid */ | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 25px; | |
| } | |
| @media (max-width: 900px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| /* Camera Section */ | |
| .camera-section { | |
| position: relative; | |
| aspect-ratio: 9/16; | |
| max-height: 500px; | |
| border-radius: 20px; | |
| overflow: hidden; | |
| background: #000; | |
| border: 1px solid var(--glass-border); | |
| } | |
| #webcam { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| filter: contrast(1.15) brightness(0.95) saturate(1.1); | |
| } | |
| /* Radar Canvas Overlay */ | |
| #hud-canvas { | |
| position: absolute; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| /* Camera Controls */ | |
| .cam-controls { | |
| position: absolute; | |
| bottom: 15px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| gap: 10px; | |
| z-index: 20; | |
| } | |
| .cam-btn { | |
| width: 45px; | |
| height: 45px; | |
| border-radius: 50%; | |
| border: 2px solid var(--cyan); | |
| background: rgba(0, 0, 0, 0.6); | |
| color: var(--cyan); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.2rem; | |
| transition: all 0.3s; | |
| } | |
| .cam-btn:hover { | |
| background: var(--cyan); | |
| color: #000; | |
| box-shadow: 0 0 20px var(--cyan); | |
| } | |
| /* Status Overlay */ | |
| .status-overlay { | |
| position: absolute; | |
| top: 15px; | |
| left: 15px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.75rem; | |
| color: var(--cyan); | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| z-index: 20; | |
| line-height: 1.6; | |
| } | |
| .status-overlay .freq { | |
| color: var(--sulfur); | |
| } | |
| /* Jogwheel */ | |
| .jogwheel-container { | |
| position: absolute; | |
| right: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| z-index: 100; | |
| } | |
| #jogwheel { | |
| width: 70px; | |
| height: 70px; | |
| border: 2px solid var(--sulfur); | |
| border-radius: 50%; | |
| background: rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| touch-action: none; | |
| cursor: grab; | |
| } | |
| #jogwheel:active { | |
| cursor: grabbing; | |
| } | |
| #knob { | |
| width: 16px; | |
| height: 16px; | |
| background: var(--cyan); | |
| border-radius: 50%; | |
| box-shadow: 0 0 15px var(--cyan), 0 0 30px var(--cyan); | |
| transition: transform 0.1s; | |
| } | |
| /* Lines Container */ | |
| .lines-section { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| } | |
| .lines-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| min-height: 400px; | |
| max-height: 500px; | |
| overflow-y: auto; | |
| padding-right: 10px; | |
| } | |
| .lines-container::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .lines-container::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 3px; | |
| } | |
| .lines-container::-webkit-scrollbar-thumb { | |
| background: var(--cyan); | |
| border-radius: 3px; | |
| } | |
| .line-item { | |
| background: rgba(255, 255, 255, 0.025); | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| padding: 14px 20px; | |
| border-radius: 10px; | |
| font-family: 'Fira Code', 'Courier New', monospace; | |
| font-size: 0.95rem; | |
| color: #fff; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| position: relative; | |
| overflow: hidden; | |
| transition: all 0.3s; | |
| } | |
| .line-item:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-color: rgba(0, 240, 255, 0.3); | |
| } | |
| .line-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| height: 100%; | |
| width: 3px; | |
| background: linear-gradient(180deg, var(--cyan), var(--purple)); | |
| opacity: 0.6; | |
| } | |
| .line-text { | |
| flex: 1; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .line-index { | |
| font-size: 0.7rem; | |
| color: #555; | |
| background: rgba(0, 0, 0, 0.5); | |
| padding: 4px 8px; | |
| border-radius: 5px; | |
| margin-left: 10px; | |
| } | |
| /* Status Bar */ | |
| .status-bar { | |
| margin-top: 15px; | |
| padding-top: 15px; | |
| border-top: 1px solid var(--glass-border); | |
| font-size: 0.8rem; | |
| color: #666; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .formula-info { | |
| color: var(--purple); | |
| } | |
| #timer-indicator { | |
| color: var(--cyan); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| #timer-indicator.visible { | |
| opacity: 1; | |
| animation: pulse 1.5s infinite; | |
| } | |
| #timer-indicator::before { | |
| content: '●'; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 0.5; } | |
| 50% { opacity: 1; } | |
| } | |
| /* Built with badge */ | |
| .built-with { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| font-size: 0.75rem; | |
| color: #444; | |
| text-decoration: none; | |
| transition: color 0.3s; | |
| z-index: 1000; | |
| } | |
| .built-with:hover { | |
| color: var(--cyan); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-orbs"> | |
| <div class="orb"></div> | |
| <div class="orb"></div> | |
| <div class="orb"></div> | |
| </div> | |
| <div class="app-container"> | |
| <div class="header"> | |
| <div class="logo">VOID<span>GLIDER</span></div> | |
| <div class="context-selector"> | |
| <button class="context-btn active" data-context="html">HTML</button> | |
| <button class="context-btn" data-context="css">CSS</button> | |
| <button class="context-btn" data-context="js">JS</button> | |
| <button class="context-btn" data-context="py">Python</button> | |
| </div> | |
| <div class="action-buttons"> | |
| <button class="btn btn-shuffle" id="btn-shuffle">Shuffle</button> | |
| <button class="btn btn-timer" id="btn-timer">Auto Timer</button> | |
| </div> | |
| </div> | |
| <div class="main-content"> | |
| <!-- Camera Section --> | |
| <div class="camera-section"> | |
| <video id="webcam" autoplay playsinline></video> | |
| <canvas id="hud-canvas"></canvas> | |
| <div class="status-overlay"> | |
| <div>Freq: <span class="freq">7.83Hz</span></div> | |
| <div>Mode: AI-Morph</div> | |
| <div id="nfc-status">NFC: Standby</div> | |
| </div> | |
| <div class="jogwheel-container"> | |
| <div id="jogwheel"> | |
| <div id="knob"></div> | |
| </div> | |
| </div> | |
| <div class="cam-controls"> | |
| <button class="cam-btn" id="cam-toggle" title="Toggle Camera">📷</button> | |
| <button class="cam-btn" id="filter-toggle" title="Toggle Filter">◐</button> | |
| <button class="cam-btn" id="radar-toggle" title="Toggle Radar">◎</button> | |
| </div> | |
| </div> | |
| <!-- Lines Section --> | |
| <div class="lines-section"> | |
| <div class="lines-container" id="lines-container"> | |
| <!-- Lines will be injected here --> | |
| </div> | |
| <div class="status-bar"> | |
| <span class="formula-info" id="formula-info">Algorithm: Fisher-Yates</span> | |
| <span id="timer-indicator">Timer Active</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with" target="_blank">Built with anycoder</a> | |
| <script> | |
| gsap.registerPlugin(Flip); | |
| // ==================== DATA CONTEXTS ==================== | |
| const dataContexts = { | |
| html: [ | |
| "<div class='container'>", | |
| "<header id='main-header'>", | |
| "<section class='hero-banner'>", | |
| "<article data-id='1042'>", | |
| "<canvas id='webgl-output'></canvas>", | |
| "<svg viewBox='0 0 100 100'>", | |
| "<footer class='site-footer'>", | |
| "<nav class='main-nav'>", | |
| "<input type='checkbox' id='toggle'>" | |
| ], | |
| css: [ | |
| "display: grid; grid-template-columns: 1fr 1fr;", | |
| "backdrop-filter: blur(16px) saturate(180%);", | |
| "transform: translate3d(0, 0, 0) rotate(45deg);", | |
| "box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);", | |
| "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;", | |
| "background: linear-gradient(135deg, #ff00cc, #333399);", | |
| "mix-blend-mode: color-dodge;", | |
| "transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);", | |
| "border-radius: 24px; overflow: hidden;" | |
| ], | |
| js: [ | |
| "const scene = new THREE.Scene();", | |
| "requestAnimationFrame(renderLoop);", | |
| "let data = await fetch('/api/v1/stream').then(r => r.json());", | |
| "gsap.to('.element', { duration: 1, x: 100, ease: 'power3.out' });", | |
| "const worker = new Worker('processor.js');", | |
| "document.getElementById('app').addEventListener('click', handler);", | |
| "export default class AppCore extends HTMLElement { }", | |
| "const tensor = tf.tensor([1, 2, 3, 4]);", | |
| "navigator.mediaDevices.getUserMedia({ video: true });" | |
| ], | |
| py: [ | |
| "import torch from transformers import AutoModel", | |
| "model = AutoModel.from_pretrained('Qwen3.5-2B')", | |
| "data = dataset.map(preprocess).with_format('torch')", | |
| "trainer = Trainer(model=model, args=training_args)", | |
| "outputs = model.generate(**inputs, max_new_tokens=400)", | |
| "loss = criterion(logits, labels).backward()", | |
| "optimizer.step(); scheduler.step()" | |
| ] | |
| }; | |
| // ==================== STATE ==================== | |
| let currentContext = 'html'; | |
| let currentLines = [...dataContexts[currentContext]]; | |
| let timerInterval = null; | |
| let useChaosFormula = false; | |
| let cameraActive = true; | |
| let radarActive = true; | |
| let filterActive = false; | |
| const container = document.getElementById('lines-container'); | |
| const formulaInfo = document.getElementById('formula-info'); | |
| const video = document.getElementById('webcam'); | |
| const canvas = document.getElementById('hud-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // ==================== SHUFFLE ALGORITHMS ==================== | |
| function shuffleFisherYates(array) { | |
| let arr = [...array]; | |
| for (let i = arr.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [arr[i], arr[j]] = [arr[j], arr[i]]; | |
| } | |
| return arr; | |
| } | |
| function shuffleChaos(array) { | |
| let arr = [...array]; | |
| const timeSeed = Date.now(); | |
| arr.sort((a, b) => { | |
| const hashA = Math.abs(a.length * Math.sin(timeSeed * 0.001)); | |
| const hashB = Math.abs(b.length * Math.cos(timeSeed * 0.001)); | |
| return (hashA + Math.random() * 0.5) - (hashB + Math.random() * 0.5); | |
| }); | |
| return arr; | |
| } | |
| // ==================== RENDER FUNCTION ==================== | |
| function renderLines(animate = true) { | |
| const state = Flip.getState(".line-item"); | |
| container.innerHTML = ''; | |
| currentLines.forEach((lineText, index) => { | |
| const div = document.createElement('div'); | |
| div.className = 'line-item'; | |
| div.dataset.flipId = lineText; | |
| div.innerHTML = ` | |
| <span class="line-text">${lineText}</span> | |
| <span class="line-index">#${index + 1}</span> | |
| `; | |
| container.appendChild(div); | |
| }); | |
| if (animate && state.targets.length > 0) { | |
| Flip.from(state, { | |
| duration: 0.7, | |
| ease: "elastic.out(1, 0.7)", | |
| absolute: true, | |
| stagger: 0.04 | |
| }); | |
| } else if (animate) { | |
| gsap.from(".line-item", { | |
| y: 25, | |
| opacity: 0, | |
| duration: 0.5, | |
| stagger: 0.08, | |
| ease: "power3.out" | |
| }); | |
| } | |
| } | |
| function triggerReorder() { | |
| useChaosFormula = Math.random() > 0.5; | |
| if (useChaosFormula) { | |
| currentLines = shuffleChaos(currentLines); | |
| formulaInfo.innerText = "Algorithm: Chaos-Sinus"; | |
| } else { | |
| currentLines = shuffleFisherYates(currentLines); | |
| formulaInfo.innerText = "Algorithm: Fisher-Yates"; | |
| } | |
| renderLines(true); | |
| } | |
| // ==================== CAMERA FUNCTIONS ==================== | |
| async function startCamera() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| video: { facingMode: 'environment' } | |
| }); | |
| video.srcObject = stream; | |
| resizeCanvas(); | |
| animateRadar(); | |
| document.getElementById('nfc-status').innerText = "NFC: Active"; | |
| } catch (err) { | |
| console.error("Camera error:", err); | |
| document.getElementById('nfc-status').innerText = "NFC: Error"; | |
| } | |
| } | |
| function stopCamera() { | |
| if (video.srcObject) { | |
| video.srcObject.getTracks().forEach(track => track.stop()); | |
| video.srcObject = null; | |
| } | |
| document.getElementById('nfc-status').innerText = "NFC: Standby"; | |
| } | |
| function resizeCanvas() { | |
| canvas.width = canvas.offsetWidth; | |
| canvas.height = canvas.offsetHeight; | |
| } | |
| // ==================== RADAR ANIMATION ==================== | |
| function animateRadar() { | |
| if (!radarActive) return; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const time = Date.now() * 0.001; | |
| const cx = canvas.width / 2; | |
| const cy = canvas.height / 2; | |
| // Outer ring | |
| ctx.strokeStyle = 'rgba(0, 255, 255, 0.15)'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.arc(cx, cy, 120, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Middle ring | |
| ctx.beginPath(); | |
| ctx.arc(cx, cy, 80, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Inner ring | |
| ctx.beginPath(); | |
| ctx.arc(cx, cy, 40, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Scan line | |
| ctx.strokeStyle = 'rgba(0, 255, 255, 0.8)'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(cx, cy); | |
| ctx.lineTo(cx + Math.cos(time * 2) * 120, cy + Math.sin(time * 2) * 120); | |
| ctx.stroke(); | |
| // Glow effect | |
| const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, 120); | |
| gradient.addColorStop(0, 'rgba(0, 255, 255, 0.1)'); | |
| gradient.addColorStop(1, 'rgba(0, 255, 255, 0)'); | |
| ctx.fillStyle = gradient; | |
| ctx.beginPath(); | |
| ctx.arc(cx, cy, 120, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Grid lines | |
| ctx.strokeStyle = 'rgba(0, 255, 255, 0.1)'; | |
| for (let i = 0; i < 8; i++) { | |
| const angle = (i / 8) * Math.PI * 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(cx, cy); | |
| ctx.lineTo(cx + Math.cos(angle) * 120, cy + Math.sin(angle) * 120); | |
| ctx.stroke(); | |
| } | |
| // Random blips | |
| if (Math.random() > 0.95) { | |
| const blipAngle = Math.random() * Math.PI * 2; | |
| const blipDist = 30 + Math.random() * 90; | |
| ctx.fillStyle = 'rgba(224, 240, 0, 0.8)'; | |
| ctx.beginPath(); | |
| ctx.arc(cx + Math.cos(blipAngle) * blipDist, cy + Math.sin(blipAngle) * blipDist, 3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| requestAnimationFrame(animateRadar); | |
| } | |
| // ==================== JOGWHEEL ==================== | |
| const jogwheel = document.getElementById('jogwheel'); | |
| const knob = document.getElementById('knob'); | |
| let isDragging = false; | |
| let startAngle = 0; | |
| jogwheel.addEventListener('mousedown', startRotate); | |
| jogwheel.addEventListener('touchstart', startRotate); | |
| function startRotate(e) { | |
| e.preventDefault(); | |
| isDragging = true; | |
| const rect = jogwheel.getBoundingClientRect(); | |
| const centerX = rect.left + rect.width / 2; | |
| const centerY = rect.top + rect.height / 2; | |
| const clientX = e.touches ? e.touches[0].clientX : e.clientX; | |
| const clientY = e.touches ? e.touches[0].clientY : e.clientY; | |
| startAngle = Math.atan2(clientY - centerY, clientX - centerX); | |
| } | |
| document.addEventListener('mousemove', rotate); | |
| document.addEventListener('touchmove', rotate); | |
| function rotate(e) { | |
| if (!isDragging) return; | |
| const rect = jogwheel.getBoundingClientRect(); | |
| const centerX = rect.left + rect.width / 2; | |
| const centerY = rect.top + rect.height / 2; | |
| const clientX = e.touches ? e.touches[0].clientX : e.clientX; | |
| const clientY = e.touches ? e.touches[0].clientY : e.clientY; | |
| const angle = Math.atan2(clientY - centerY, clientX - centerX); | |
| const rotation = (angle - startAngle) * (180 / Math.PI); | |
| knob.style.transform = `rotate(${rotation}deg)`; | |
| // Trigger shuffle on significant rotation | |
| if (Math.abs(rotation) > 30) { | |
| triggerReorder(); | |
| startAngle = angle; | |
| } | |
| } | |
| document.addEventListener('mouseup', () => isDragging = false); | |
| document.addEventListener('touchend', () => isDragging = false); | |
| // ==================== EVENT LISTENERS ==================== | |
| // Shuffle Button | |
| document.getElementById('btn-shuffle').addEventListener('click', () => { | |
| triggerReorder(); | |
| gsap.fromTo('#btn-shuffle', | |
| { scale: 0.92 }, | |
| { scale: 1, duration: 0.4, ease: "back.out(2.5)" } | |
| ); | |
| }); | |
| // Context Switching | |
| document.querySelectorAll('.context-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| document.querySelectorAll('.context-btn').forEach(b => b.classList.remove('active')); | |
| e.target.classList.add('active'); | |
| currentContext = e.target.dataset.context; | |
| currentLines = [...dataContexts[currentContext]]; | |
| renderLines(true); | |
| }); | |
| }); | |
| // Timer Button | |
| const btnTimer = document.getElementById('btn-timer'); | |
| const timerIndicator = document.getElementById('timer-indicator'); | |
| btnTimer.addEventListener('click', () => { | |
| if (timerInterval) { | |
| clearInterval(timerInterval); | |
| timerInterval = null; | |
| btnTimer.classList.remove('active'); | |
| timerIndicator.classList.remove('visible'); | |
| btnTimer.innerText = "Auto Timer"; | |
| } else { | |
| btnTimer.classList.add('active'); | |
| timerIndicator.classList.add('visible'); | |
| btnTimer.innerText = "Stop Timer"; | |
| const runTimer = () => { | |
| if (!timerInterval) return; | |
| triggerReorder(); | |
| const nextTick = Math.floor(Math.random() * 3000) + 2000; | |
| clearInterval(timerInterval); | |
| timerInterval = setTimeout(runTimer, nextTick); | |
| }; | |
| timerInterval = setTimeout(runTimer, 1500); | |
| } | |
| }); | |
| // Camera Controls | |
| document.getElementById('cam-toggle').addEventListener('click', () => { | |
| cameraActive = !cameraActive; | |
| if (cameraActive) { | |
| startCamera(); | |
| } else { | |
| stopCamera(); | |
| } | |
| }); | |
| document.getElementById('filter-toggle').addEventListener('click', () => { | |
| filterActive = !filterActive; | |
| video.style.filter = filterActive ? | |
| 'contrast(1.3) brightness(1.1) hue-rotate(90deg) saturate(1.5)' : | |
| 'contrast(1.15) brightness(0.95) saturate(1.1)'; | |
| }); | |
| document.getElementById('radar-toggle').addEventListener('click', () => { | |
| radarActive = !radarActive; | |
| if (radarActive) { | |
| animateRadar(); | |
| canvas.style.opacity = '1'; | |
| } else { | |
| canvas.style.opacity = '0'; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| } | |
| }); | |
| // ==================== INITIALIZATION ==================== | |
| window.addEventListener('resize', resizeCanvas); | |
| // Start system | |
| startCamera(); | |
| renderLines(true); | |
| </script> | |
| </body> | |
| </html> |