backnforth/index.html
2025-11-25 18:55:21 +10:00

639 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bilateral Stimulation Simulator - Admin</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6' fill='%231f6feb'/%3E%3C/svg%3E">
<link rel="stylesheet" href="css/admin.css">
</head>
<body>
<header>
<div class="header-content">
<h1>Bilateral Stimulation Simulator</h1>
<div class="simple-toggle" style="margin:0 auto; display:flex; align-items:center; gap:8px;">
<label class="control-label" style="color:inherit;">Simple View</label>
<label class="toggle">
<input type="checkbox" id="simpleViewEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="header-actions">
<button id="openPatient" class="btn-secondary">
<span class="btn-icon">👁️</span>
Open Patient View
</button>
</div>
</div>
</header>
<div id="lockBanner" class="lock-banner">
<span class="lock-icon">🔒</span>
LOCKED • Hold U to unlock
</div>
<main>
<!-- Left Column: Controls -->
<aside class="sidebar">
<section class="panel collapsible" id="visualPanel">
<h2 role="button" tabindex="0">
<span class="panel-icon">🎨</span>
Visual Settings
<span class="panel-actions">
<label class="toggle" title="Toggle Dark/Light">
<input type="checkbox" id="themeToggle">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</span>
</h2>
<div class="control-group">
<label class="control-label">Shape</label>
<div class="button-group" id="shapeControls">
<button data-shape="circle" class="active">⚪ Circle</button>
<button data-shape="square">⬛ Square</button>
<button data-shape="triangle">🔺 Triangle</button>
</div>
</div>
<div class="control-group">
<label class="control-label">Shape Color</label>
<div class="color-swatches" id="shapeSwatches">
<button class="swatch swatch-green" data-color="#00ff00" title="Green"></button>
<button class="swatch swatch-blue" data-color="#0066ff" title="Blue"></button>
<button class="swatch swatch-yellow" data-color="#ffd400" title="Yellow"></button>
<button class="swatch swatch-red" data-color="#ff2a2a" title="Red"></button>
<button class="swatch swatch-white" data-color="#ffffff" title="White"></button>
<button class="swatch swatch-custom" id="shapeCustom" title="Custom Color"></button>
</div>
</div>
<div class="control-group">
<label class="control-label">Background</label>
<div class="color-swatches" id="bgSwatches">
<button class="swatch swatch-black" data-color="#000000" title="Black"></button>
<button class="swatch swatch-blue" data-color="#0066ff" title="Blue"></button>
<button class="swatch swatch-yellow" data-color="#ffd400" title="Yellow"></button>
<button class="swatch swatch-red" data-color="#ff2a2a" title="Red"></button>
<button class="swatch swatch-white" data-color="#ffffff" title="White"></button>
<button class="swatch swatch-custom" id="bgCustom" title="Custom Color"></button>
</div>
</div>
<div class="control-group">
<label class="control-label">Movement Direction</label>
<div class="button-group" id="directionControls">
<button data-direction="lr" class="active">HORIZ ↔️</button>
<button data-direction="ud">VERT ↕️</button>
<button data-direction="diag_down">DIAG L ↘️</button>
<button data-direction="diag_up">DIAG R ↗️</button>
</div>
</div>
<div class="control-group">
<label class="control-label">Display Width</label>
<div class="button-group" id="widthControls">
<button data-width="full" class="active">Full</button>
<button data-width="reduced">Reduced</button>
</div>
</div>
<div class="control-group">
<label class="control-label">Size: <span id="sizeLabel">28 px</span></label>
<input type="range" id="size" min="10" max="100" step="0.5" value="28" class="slider">
</div>
<div class="control-group">
<label class="control-label">Vertical Position: <span id="yPercentLabel">50%</span></label>
<div class="slider-with-reset">
<input type="range" id="yPercent" min="0" max="100" step="1" value="50" class="slider">
<button id="yReset" class="btn-small">Reset</button>
</div>
<div class="control-row" style="margin-top:8px;">
<div class="control-group">
<label class="control-label" style="margin:0;">Vertical Oscillation</label>
<label class="toggle">
<input type="checkbox" id="yOscillateEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="control-group">
<label class="control-label">Gradual Speed Ramp</label>
<label class="toggle">
<input type="checkbox" id="rampEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Ramp Duration: <span id="rampLabel">10 s</span></label>
<input type="range" id="rampSeconds" min="1" max="60" step="1" value="10" class="slider">
</div>
</div>
</section>
<section class="panel collapsible" id="networkPanel">
<h2 role="button" tabindex="0">
<span class="panel-icon"></span>
Effects
</h2>
<div class="control-group">
<label class="control-label">Glow Pulse Effect</label>
<label class="toggle">
<input type="checkbox" id="glowEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="control-group control-indent">
<label class="control-label">Intensity: <span id="glowIntensityLabel">100%</span></label>
<input type="range" id="glowIntensity" min="0" max="200" step="5" value="100" class="slider">
</div>
<div class="control-group control-indent">
<label class="control-label">Rate: <span id="glowRateLabel">0.5 Hz</span></label>
<input type="range" id="glowRate" min="0.1" max="3" step="0.1" value="0.5" class="slider">
</div>
<div class="control-group">
<label class="control-label">Wiggle Effect</label>
<label class="toggle">
<input type="checkbox" id="wiggleEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="control-group control-indent">
<label class="control-label">Amount: <span id="wiggleLabel">0 px</span></label>
<input type="range" id="wiggleAmplitude" min="0" max="10" step="1" value="0" class="slider">
</div>
<div class="control-group">
<label class="control-label">Movement Style</label>
<div class="button-group" id="easingControls">
<button data-easing="linear" class="active">Linear</button>
<button data-easing="ease">Smooth</button>
</div>
</div>
<div class="control-group">
<label class="control-label">Edge Pause: <span id="edgePauseLabel">0 ms</span></label>
<input type="range" id="edgePause" min="0" max="150" step="10" value="0" class="slider">
</div>
</section>
<section class="panel panel-recording collapsible" id="recordingPanel">
<h2 role="button" tabindex="0">
<span class="panel-icon">🔊</span>
Audio Settings
</h2>
<div class="control-group">
<div class="button-group" id="audioModeButtons">
<button data-mode="programmatic" class="active">Program</button>
<button id="loadAudioBtn">Load Audio</button>
</div>
</div>
<div class="control-group" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
<div>
<label class="control-label">Enable Audio</label>
<label class="toggle">
<input type="checkbox" id="audioEnable">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<button id="testSound" class="btn-small">Test Sound</button>
</div>
<div id="programmaticControls" class="audio-controls">
<div class="control-group">
<div class="button-group" id="soundToneToggle">
<button data-mode="sound" class="active">Sound</button>
<button data-mode="tone">Tone</button>
</div>
</div>
<div id="soundPresetControls" class="control-group">
<label class="control-label">Sound Type</label>
<select id="soundPreset" class="select-input">
<option value="pink">Pink Noise</option>
<option value="hybrid">Hybrid (noise + edge cues)</option>
<option value="click">Click</option>
<option value="ping">Ping</option>
<option value="woodblock">Woodblock</option>
<option value="bell">Bell</option>
<option value="bass">Bass</option>
<option value="kick">Kick</option>
<option value="snare">Snare</option>
<option value="hihat">Hi-Hat</option>
<option value="sweep">Sweep</option>
<option value="zap">Zap</option>
<option value="bubble">Bubble</option>
</select>
</div>
<div id="tonePresetControls" class="control-group">
<label class="control-label">Tone Preset</label>
<select id="tonePreset" class="select-input">
<option value="warm_low">Warm Low — 180 Hz @ 100 ms</option>
<option value="soft_pulse">Soft Pulse — 220 Hz @ 90 ms</option>
<option value="neutral_pulse">Neutral Pulse — 280 Hz @ 90 ms</option>
<option value="bright_pulse">Bright Pulse — 330 Hz @ 80 ms</option>
<option value="soft_tick">Soft Tick — 440 Hz @ 70 ms</option>
<option value="medium_tick">Medium Tick — 520 Hz @ 60 ms</option>
<option value="clear_click">Clear Click — 660 Hz @ 40 ms</option>
<option value="bright_ping">Bright Ping — 880 Hz @ 50 ms</option>
<option value="light_ping">Light Ping — 1000 Hz @ 50 ms</option>
<option value="sharp_cue">Sharp Cue — 1200 Hz @ 30 ms</option>
<option value="accent">Accent — 1500 Hz @ 30 ms</option>
<option value="spark">Spark — 1800 Hz @ 30 ms</option>
<option value="crisp">Crisp — 2000 Hz @ 30 ms</option>
</select>
</div>
<div id="toneFreqRow" class="control-group">
<label class="control-label">Frequency (Hz)</label>
<input type="number" id="toneFreq" min="100" max="2000" step="10" value="440" class="number-input">
</div>
<div id="toneDurRow" class="control-group">
<label class="control-label">Duration: <span id="toneDurationLabel">70 ms</span></label>
<input type="range" id="toneDuration" min="30" max="150" step="5" value="70" class="slider">
</div>
<div id="adsrField" class="adsr-controls">
<details class="adsr-details">
<summary>Advanced: ADSR Envelope</summary>
<div class="adsr-grid">
<div class="adsr-control">
<label>Attack: <span id="adsrAttackLabel">8 ms</span></label>
<input type="range" id="adsrAttack" min="5" max="20" step="1" value="8" class="slider">
</div>
<div class="adsr-control">
<label>Decay: <span id="adsrDecayLabel">80 ms</span></label>
<input type="range" id="adsrDecay" min="40" max="120" step="5" value="80" class="slider">
</div>
<div class="adsr-control">
<label>Sustain: <span id="adsrSustainLabel">0.30</span></label>
<input type="range" id="adsrSustain" min="0" max="1" step="0.05" value="0.3" class="slider">
</div>
<div class="adsr-control">
<label>Release: <span id="adsrReleaseLabel">10 ms</span></label>
<input type="range" id="adsrRelease" min="5" max="50" step="1" value="10" class="slider">
</div>
</div>
</details>
</div>
<div class="control-group">
<label class="control-label">Pan synced to movement</label>
<label class="toggle">
<input type="checkbox" id="syncPan" checked>
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="control-group">
<label class="control-label">Pan Rate: <span id="panRateLabel">0.5 Hz</span></label>
<input type="range" id="panRate" min="0.1" max="2" step="0.1" value="0.5" class="slider">
</div>
<div class="control-group">
<label class="control-label">Cue Rate: <span id="cueRateLabel">1.2 Hz</span></label>
<input type="range" id="cueRate" min="0.1" max="3" step="0.1" value="1.2" class="slider">
</div>
<div class="control-group">
<label class="control-label">Volume: <span id="volumeLabel">60%</span></label>
<input type="range" id="volume" min="0" max="100" step="5" value="60" class="slider">
</div>
</div>
</section>
<section class="panel collapsible" id="presetsPanel">
<h2 role="button" tabindex="0">PRESETS</h2>
<div class="preset-controls">
<div class="control-row">
<input type="text" id="presetName" placeholder="Preset name..." class="text-input flex-1">
<button id="savePreset" class="btn-secondary">Save</button>
</div>
<div class="control-row">
<select id="presetList" class="select-input flex-1"></select>
<button id="applyPreset" class="btn-secondary">Apply</button>
<button id="deletePreset" class="btn-danger-outline">Delete</button>
</div>
<div class="control-row">
<button id="exportPresets" class="btn-secondary">Export JSON</button>
<button id="importPresets" class="btn-secondary">Import JSON</button>
</div>
</div>
</section>
<section id="controllersPanel" class="panel collapsible">
<h2 role="button" tabindex="0">CONTROL</h2>
<div class="controller-content">
<div class="status-display">
<span id="ctlStatusDot" class="status-dot"></span>
<span id="ctlStatusText" class="status-value">No device</span>
</div>
<div class="control-group">
<label class="control-label">MIDI Input</label>
<select id="midiSelect" class="select-input"></select>
</div>
<div class="mapping-section">
<h3>Map Actions</h3>
<div class="mapping-buttons">
<button data-map-action="start" class="btn-map">Start Session</button>
<button data-map-action="stop" class="btn-map">End Session</button>
<button data-map-action="speed_up" class="btn-map">Speed +</button>
<button data-map-action="speed_down" class="btn-map">Speed </button>
<button data-map-action="volume_up" class="btn-map">Volume +</button>
<button data-map-action="volume_down" class="btn-map">Volume </button>
<button data-map-action="audio_toggle" class="btn-map">Audio Toggle</button>
<button data-map-action="glow_toggle" class="btn-map">Glow Toggle</button>
<button data-map-action="wiggle_toggle" class="btn-map">Wiggle Toggle</button>
<button data-map-action="lock_toggle" class="btn-map">Lock Toggle</button>
<button data-map-action="record_start" class="btn-map">Record Start</button>
<button data-map-action="record_stop" class="btn-map">Record Stop</button>
<button data-map-action="replay" class="btn-map">Replay</button>
<button data-map-action="shape_next" class="btn-map">Cycle Shape</button>
<button data-map-action="direction_next" class="btn-map">Cycle Direction</button>
<button data-map-action="easing_next" class="btn-map">Cycle Smoothness</button>
<button data-map-action="program_next" class="btn-map">Cycle Audio Preset</button>
</div>
</div>
<div class="mapping-section">
<h3>Current Mappings</h3>
<div id="ctlMappings" class="mappings-list"></div>
</div>
<div class="mapping-section">
<h3>Hardware Controller</h3>
<div class="control-row">
<button id="connectHID" class="btn-secondary">Connect HID Device</button>
<span id="deviceStatus" class="status-value">Not connected</span>
</div>
<div class="control-group">
<label class="control-label">Sensitivity: <span id="knobSensLabel">x5</span></label>
<input type="range" id="knobSens" min="1" max="50" step="1" value="5" class="slider">
</div>
<div class="control-group" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
<div>
<label class="control-label">Enable Lock Hotkey (L to lock, U to unlock)</label>
<label class="toggle">
<input type="checkbox" id="lockHotkeyEnable" checked>
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
</div>
</div>
</div>
</section>
</aside>
<!-- Right Column: Session & Preview -->
<div class="main-content">
<section class="panel panel-session collapsible">
<h2 role="button" tabindex="0">
<span class="panel-icon"></span>
Session Control
</h2>
<div class="preview-container">
<canvas id="previewCanvas" class="preview-canvas" width="640" height="360"></canvas>
</div>
<div class="session-actions" style="margin-top:10px;">
<button id="startSession" class="btn-primary btn-large">
<span class="btn-icon">▶️</span>
Start Session
</button>
<button id="endSession" class="btn-danger btn-large">
<span class="btn-icon">⏹️</span>
End Session
</button>
</div>
<div class="session-controls">
<div class="control-row">
<div class="control-group">
<label class="control-label">Speed: <span id="speedLabel">250 px/s</span></label>
<div class="speed-control">
<button id="speedDown" class="btn-adjust"></button>
<input type="range" id="speed" min="50" max="1800" step="1" value="250" class="slider slider-wide">
<button id="speedUp" class="btn-adjust">+</button>
</div>
</div>
</div>
<div class="timer-section">
<div id="patientViewportShort" class="status-value" style="margin-bottom:8px; font-size:12px; opacity:0.8;"></div>
<div class="timer-display">
<div class="timer-box">
<label>Duration (minutes)</label>
<input type="number" id="timerMinutes" min="0" value="10" class="timer-input">
</div>
<div class="timer-box">
<label>Remaining</label>
<div id="timerDisplay" class="timer-value">10:00</div>
</div>
<div class="timer-box">
<label>Elapsed</label>
<div id="elapsedText" class="timer-value">00:00</div>
</div>
</div>
<div class="status-bar">
<span class="status-label">Status:</span>
<span id="statusText" class="status-value">Idle</span>
</div>
</div>
</div>
</section>
<section class="panel panel-recording collapsible">
<h2 role="button" tabindex="0">
<span class="panel-icon"><EFBFBD></span>
Recording & Playback
</h2>
<div class="recording-controls">
<div class="control-row">
<button id="recStart" class="btn-record">
<span class="record-dot"></span>
Record
</button>
<button id="recStop" class="btn-secondary">Stop & Save</button>
</div>
<div class="control-row">
<select id="recList" class="select-input flex-1"></select>
<button id="recPlay" class="btn-secondary">Replay</button>
<button id="recExport" class="btn-secondary">Export</button>
<button id="recImport" class="btn-secondary">Import</button>
</div>
<div class="control-row">
<button id="recDelete" class="btn-danger-outline">Delete</button>
<button id="recClear" class="btn-danger-outline">Clear All</button>
</div>
</div>
</section>
<section class="panel collapsible">
<h2 role="button" tabindex="0">
<span class="panel-icon">👤</span>
Patient Management
</h2>
<div class="patient-controls">
<div class="control-row">
<input type="text" id="patientName" placeholder="Patient name..." class="text-input flex-1">
<button id="savePatient" class="btn-secondary">Save</button>
</div>
<div class="control-row">
<select id="patientList" class="select-input flex-1"></select>
<button id="loadPatient" class="btn-secondary">Load</button>
<button id="deletePatient" class="btn-danger-outline">Delete</button>
</div>
<div class="patient-stats">
<div class="stat-item">
<span class="stat-label">Total Sessions:</span>
<span id="sessionCount" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Last Session:</span>
<span id="lastSessionInfo" class="stat-value"></span>
</div>
</div>
<div class="control-group">
<label class="control-label">Session History</label>
<div id="sessionHistory" class="session-history"></div>
<button id="clearSessions" class="btn-danger-outline btn-small">Clear All Sessions</button>
</div>
<div class="control-group">
<label class="control-label">Session Notes</label>
<div class="control-row">
<select id="sessionSelect" class="select-input flex-1"></select>
<button id="loadSession" class="btn-secondary btn-small">Load</button>
<button id="saveSessionNotes" class="btn-secondary btn-small">Save Notes</button>
<button id="deleteSession" class="btn-danger-outline btn-small">Delete Session</button>
</div>
<textarea id="sessionNotes" class="textarea-input" placeholder="Enter session notes..."></textarea>
</div>
</div>
</section>
<section class="panel collapsible" id="networkPanelSection">
<h2 id="networkHeading" role="button" tabindex="0">
<span class="panel-icon">🌐</span>
Network Connection
</h2>
<div class="network-controls">
<div class="control-group">
<label class="control-label">Connection Mode</label>
<select id="netMode" class="select-input">
<option value="relay">Relay (WebSocket)</option>
<option value="webrtc">WebRTC (P2P)</option>
</select>
</div>
<div id="webrtcControls" class="webrtc-section">
<div class="control-group">
<button id="rtcCreateOffer" class="btn-secondary">Create Offer</button>
<input type="text" id="rtcOffer" placeholder="Offer (base64)" class="text-input flex-1">
</div>
<div class="qr-container">
<img id="rtcOfferQR" alt="Offer QR Code" class="qr-code">
</div>
<div class="control-group">
<button id="rtcAcceptAnswer" class="btn-secondary">Accept Answer</button>
<input type="text" id="rtcAnswer" placeholder="Answer (base64)" class="text-input flex-1">
</div>
<div class="status-display">
<span class="status-label">WebRTC Status:</span>
<span id="rtcStatus" class="status-value">Idle</span>
</div>
</div>
<div class="relay-section">
<div class="status-display">
<span id="relayStatusDot" class="status-dot"></span>
<span id="relayStatusText" class="status-value">Not running</span>
</div>
<div class="control-row">
<button id="startRelayBtn" class="btn-secondary">Copy Start Command</button>
<button id="stopRelayBtn" class="btn-danger-outline">Stop Server</button>
</div>
<div class="control-group">
<label class="control-label">Connection Details</label>
<div class="connection-inputs">
<input type="text" id="wsUrl" placeholder="ws://192.168.x.x:8787" class="text-input">
<input type="text" id="wsSession" placeholder="room-1" class="text-input">
</div>
</div>
<div class="control-row">
<button id="detectIP" class="btn-secondary">Detect Local IP</button>
<button id="wsConnect" class="btn-secondary">Connect</button>
<button id="wsDisconnect" class="btn-danger-outline">Disconnect</button>
</div>
<div class="status-display">
<span class="status-label">Status:</span>
<span id="wsStatus" class="status-value">Disconnected</span>
</div>
<div class="control-group">
<label class="control-label">Patient Join Link</label>
<div class="control-row">
<input type="text" id="joinLink" readonly class="text-input flex-1">
<button id="copyJoinLink" class="btn-secondary">Copy</button>
</div>
</div>
<div id="osHelp" class="help-text"></div>
</div>
</div>
</section>
</div>
</main>
<script src="js/bls.js"></script>
<script src="js/audio.js"></script>
<script src="js/webrtc.js"></script>
<script src="js/recorder.js"></script>
<script src="js/recording-store.js"></script>
<script src="js/controllers.js"></script>
<script src="js/admin.js"></script>
</body>
</html>