2026-02-11 13:17:17 +01:00
|
|
|
/* NISPS Playground - Dark theme, mobile-first */
|
|
|
|
|
|
|
|
|
|
:root {
|
|
|
|
|
--bg: #0d0d0d;
|
|
|
|
|
--bg-surface: #1a1a1a;
|
|
|
|
|
--bg-elevated: #252525;
|
|
|
|
|
--text: #ccc;
|
|
|
|
|
--text-dim: #666;
|
|
|
|
|
--accent: #00ff88;
|
|
|
|
|
--accent-dim: #00cc6a;
|
|
|
|
|
--danger: #ff3366;
|
|
|
|
|
--good: #00ff88;
|
|
|
|
|
--bad: #ff6644;
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
--synth-accent: #ff6b35;
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
|
|
|
|
|
|
html, body {
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
touch-action: manipulation;
|
|
|
|
|
-webkit-user-select: none;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Layout --- */
|
|
|
|
|
.app {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-rows: auto 1fr auto auto auto;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
height: 100dvh;
|
|
|
|
|
max-width: 600px;
|
|
|
|
|
margin: 0 auto;
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
position: relative;
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 09:47:47 +01:00
|
|
|
.header,
|
|
|
|
|
.visual-container,
|
|
|
|
|
.param-container,
|
|
|
|
|
.controls-area,
|
|
|
|
|
.presets,
|
|
|
|
|
.expand-visual-btn {
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
/* Header */
|
|
|
|
|
.header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border-bottom: 1px solid #222;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header h1 {
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
.header-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
.header button {
|
|
|
|
|
background: none;
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
.mode-badge {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
padding: 3px 8px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
background: rgba(0, 255, 136, 0.12);
|
|
|
|
|
border: 1px solid var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mode-badge.synth {
|
|
|
|
|
background: rgba(255, 107, 53, 0.12);
|
|
|
|
|
border-color: var(--synth-accent);
|
|
|
|
|
color: var(--synth-accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ==================== Side Panel ==================== */
|
|
|
|
|
.side-panel-toggle {
|
|
|
|
|
position: fixed;
|
|
|
|
|
left: 0;
|
|
|
|
|
top: 50%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
z-index: 50;
|
|
|
|
|
background: var(--bg-surface);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
border-left: none;
|
|
|
|
|
border-radius: 0 8px 8px 0;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
padding: 12px 6px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
transition: background 0.15s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-toggle:hover {
|
|
|
|
|
background: var(--bg-elevated);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel {
|
|
|
|
|
position: fixed;
|
|
|
|
|
left: 0;
|
|
|
|
|
top: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
width: 260px;
|
|
|
|
|
background: var(--bg-surface);
|
|
|
|
|
border-right: 1px solid #333;
|
|
|
|
|
z-index: 60;
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
transition: transform 0.25s ease;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel.open {
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-backdrop {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
z-index: 55;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 14px 16px 10px;
|
|
|
|
|
border-bottom: 1px solid #222;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-close {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
padding: 0 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.side-panel-tabs {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 0;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border-bottom: 1px solid #222;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-tab {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
background: var(--bg-elevated);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.15s;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-tab:first-child {
|
|
|
|
|
border-radius: 6px 0 0 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-tab:last-child {
|
|
|
|
|
border-radius: 0 6px 6px 0;
|
|
|
|
|
border-left: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-tab.active[data-mode="visual"] {
|
|
|
|
|
background: rgba(0, 255, 136, 0.12);
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-tab.active[data-mode="synth"] {
|
|
|
|
|
background: rgba(255, 107, 53, 0.12);
|
|
|
|
|
border-color: var(--synth-accent);
|
|
|
|
|
color: var(--synth-accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sp-hint {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Synth controls */
|
|
|
|
|
.synth-controls {
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-section {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-label {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-label span {
|
|
|
|
|
color: var(--synth-accent);
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-slider {
|
|
|
|
|
-webkit-appearance: none;
|
|
|
|
|
appearance: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 6px;
|
|
|
|
|
background: #333;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
outline: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-slider::-webkit-slider-thumb {
|
|
|
|
|
-webkit-appearance: none;
|
|
|
|
|
appearance: none;
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
background: var(--synth-accent);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-slider::-moz-range-thumb {
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
background: var(--synth-accent);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-select {
|
|
|
|
|
background: var(--bg-elevated);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-start-btn {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-arp-btn {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-arp-btn.playing {
|
|
|
|
|
background: rgba(255, 107, 53, 0.15);
|
|
|
|
|
border-color: var(--synth-accent);
|
|
|
|
|
color: var(--synth-accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.synth-status {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
/* Visual canvas */
|
|
|
|
|
.visual-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 09:47:47 +01:00
|
|
|
.expand-visual-btn {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 8px;
|
|
|
|
|
right: 8px;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
background: rgba(26, 26, 26, 0.82);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: #9f9f9f;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
backdrop-filter: blur(4px);
|
|
|
|
|
-webkit-backdrop-filter: blur(4px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.expand-visual-btn:active,
|
|
|
|
|
.expand-visual-btn[aria-pressed="true"] {
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
background: rgba(0, 255, 136, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
.visual-container canvas {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Preset pills */
|
|
|
|
|
.presets {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 8px;
|
|
|
|
|
left: 8px;
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preset-pill {
|
|
|
|
|
background: rgba(26, 26, 26, 0.8);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
backdrop-filter: blur(4px);
|
|
|
|
|
-webkit-backdrop-filter: blur(4px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preset-pill:active {
|
|
|
|
|
background: rgba(0, 255, 136, 0.15);
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parameter display */
|
|
|
|
|
.param-container {
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
background: var(--bg-surface);
|
|
|
|
|
border-top: 1px solid #222;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-label {
|
|
|
|
|
width: 42px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
.param-container.synth-mode .param-label {
|
|
|
|
|
width: 72px;
|
|
|
|
|
color: #886644;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
.param-track {
|
|
|
|
|
flex: 1;
|
|
|
|
|
height: 8px;
|
|
|
|
|
background: #222;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.draggable .param-track {
|
|
|
|
|
cursor: ew-resize;
|
|
|
|
|
height: 14px;
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.draggable .param-track:active {
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-fill {
|
|
|
|
|
height: 100%;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
transition: width 0.05s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-value {
|
|
|
|
|
width: 32px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
text-align: right;
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Controls area */
|
|
|
|
|
.controls-area {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
background: var(--bg-surface);
|
|
|
|
|
border-top: 1px solid #222;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 09:47:47 +01:00
|
|
|
.app.expanded {
|
|
|
|
|
grid-template-rows: auto minmax(0, 1fr) 58px 102px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .header {
|
|
|
|
|
padding-top: 6px;
|
|
|
|
|
padding-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .header h1 {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #9a9a9a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .presets {
|
|
|
|
|
top: 6px;
|
|
|
|
|
left: 6px;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .preset-pill {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
padding: 3px 7px;
|
|
|
|
|
color: #868686;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .param-container {
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
opacity: 0.78;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .param-row {
|
|
|
|
|
height: 17px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .param-track {
|
|
|
|
|
height: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .draggable .param-track {
|
|
|
|
|
height: 9px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .param-label,
|
|
|
|
|
.app.expanded .param-value {
|
|
|
|
|
font-size: 9px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .controls-area {
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
opacity: 0.82;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded #joystick-container {
|
|
|
|
|
width: 94px;
|
|
|
|
|
height: 94px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded #joystick-container canvas {
|
|
|
|
|
transform: scale(0.58);
|
|
|
|
|
transform-origin: top left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .controls-mode-toggle,
|
|
|
|
|
.app.expanded .controls-actions {
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .btn,
|
|
|
|
|
.app.expanded .mode-btn {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
padding: 5px 10px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .btn-good,
|
|
|
|
|
.app.expanded .btn-bad {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
padding: 5px 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app.expanded .controls-status,
|
|
|
|
|
.app.expanded .metrics-grid,
|
|
|
|
|
.app.expanded .loss-plot-wrap {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
#joystick-container {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#controls-container {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.controls-mode-toggle {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mode-btn {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 6px 0;
|
|
|
|
|
background: var(--bg-elevated);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.15s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.mode-btn.active {
|
|
|
|
|
background: rgba(0, 255, 136, 0.1);
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.controls-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
padding: 8px 14px;
|
|
|
|
|
background: var(--bg-elevated);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.1s;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn:active {
|
|
|
|
|
transform: scale(0.96);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary {
|
|
|
|
|
background: rgba(0, 255, 136, 0.12);
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary:active {
|
|
|
|
|
background: rgba(0, 255, 136, 0.25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-danger {
|
|
|
|
|
color: var(--danger);
|
|
|
|
|
border-color: #552233;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-danger:active {
|
|
|
|
|
background: rgba(255, 51, 102, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-good {
|
|
|
|
|
background: rgba(0, 255, 136, 0.12);
|
|
|
|
|
border-color: var(--accent-dim);
|
|
|
|
|
color: var(--good);
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
padding: 8px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-good:active {
|
|
|
|
|
background: rgba(0, 255, 136, 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-bad {
|
|
|
|
|
background: rgba(255, 102, 68, 0.1);
|
|
|
|
|
border-color: #553322;
|
|
|
|
|
color: var(--bad);
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
padding: 8px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-bad:active {
|
|
|
|
|
background: rgba(255, 102, 68, 0.25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flash {
|
|
|
|
|
animation: flash-anim 0.2s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes flash-anim {
|
|
|
|
|
0% { box-shadow: 0 0 12px var(--accent); }
|
|
|
|
|
100% { box-shadow: none; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.controls-status {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-family: monospace;
|
2026-02-11 16:56:24 +01:00
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.metrics-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: auto 1fr;
|
|
|
|
|
gap: 3px 10px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.metric-label {
|
|
|
|
|
color: #707070;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.metric-value {
|
|
|
|
|
color: #b5b5b5;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loss-plot-wrap {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border: 1px solid #2a2a2a;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#loss-plot {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 86px;
|
|
|
|
|
display: block;
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hidden { display: none !important; }
|
|
|
|
|
|
|
|
|
|
/* Help overlay */
|
|
|
|
|
.help-overlay {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
background: rgba(0, 0, 0, 0.85);
|
|
|
|
|
z-index: 100;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
-webkit-backdrop-filter: blur(8px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.help-content {
|
|
|
|
|
background: var(--bg-surface);
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
max-width: 360px;
|
|
|
|
|
max-height: 80vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.help-content h2 {
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.help-content h3 {
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.help-content p, .help-content li {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.help-content ol {
|
|
|
|
|
padding-left: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Responsive: wider screens (foldable inner, tablet) */
|
|
|
|
|
@media (min-width: 500px) {
|
|
|
|
|
.app {
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.controls-area {
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-container {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
}
|
|
|
|
|
}
|