style(playground): unify dark theme with orange accents and improve RL button layout
- Replace fragmented green/blue/orange accent scheme with unified hacker orange (#ff6a00) across all CSS, JS canvas drawing, and param palettes - Redesign designs.html: monospace font, clickable cards, grid background, remove A/B/C labels; copy to index.html as landing page - Add JetBrains Mono font across all views for consistent techy aesthetic - Sharpen border-radius (pills 20px→6px, back buttons→6px) for angular feel - Restructure RL buttons: key numbers inside buttons (larger, white), teal-green for thumbs-up, dark red-orange for thumbs-down - Fix RL button z-index (25→45) so they render above the bottom sheet - Add Xavier-scaled weight randomization (spread parameter)
This commit is contained in:
parent
b80a9e4e86
commit
d9be808aaa
18 changed files with 553 additions and 415 deletions
|
|
@ -6,6 +6,9 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>NISPS Immersive</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/a-immersive.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -49,8 +52,8 @@
|
|||
|
||||
<!-- RL floating buttons -->
|
||||
<div class="rl-buttons" id="rl-buttons">
|
||||
<button class="rl-btn rl-down" id="btn-thumbsdown" title="Explore more">− <span class="key-num">1</span></button>
|
||||
<button class="rl-btn rl-up" id="btn-thumbsup" title="Keep this">+ <span class="key-num">2</span></button>
|
||||
<button class="rl-btn rl-down" id="btn-thumbsdown" title="Explore more"><span class="rl-icon">−</span><span class="key-num">1</span></button>
|
||||
<button class="rl-btn rl-up" id="btn-thumbsup" title="Keep this"><span class="rl-icon">+</span><span class="key-num">2</span></button>
|
||||
</div>
|
||||
|
||||
<!-- Bottom sheet -->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>NISPS Workbench</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/b-workbench.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -98,10 +101,8 @@
|
|||
|
||||
<!-- RL mode controls -->
|
||||
<div class="wb-action-group" id="rl-controls">
|
||||
<button class="wb-btn wb-btn-bad" id="btn-thumbsdown">− Explore</button>
|
||||
<span class="wb-key-hint">1</span>
|
||||
<button class="wb-btn wb-btn-good" id="btn-thumbsup">+ Good</button>
|
||||
<span class="wb-key-hint">2</span>
|
||||
<button class="wb-btn wb-btn-bad" id="btn-thumbsdown"><span class="wb-btn-label">− Explore</span><span class="wb-key-hint">1</span></button>
|
||||
<button class="wb-btn wb-btn-good" id="btn-thumbsup"><span class="wb-btn-label">+ Good</span><span class="wb-key-hint">2</span></button>
|
||||
<button class="wb-btn" id="btn-randomize-rl">Randomize</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>NISPS Journey</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/c-journey.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -122,12 +125,8 @@
|
|||
<div class="teach-content" id="teach-rl">
|
||||
<p class="rl-hint" id="rl-hint">Like what you see? <span class="thumb-icon">👍</span> Want something different? <span class="thumb-icon">👎</span></p>
|
||||
<div class="rl-buttons">
|
||||
<button class="rl-btn down" id="btn-thumbs-down" title="Explore more">👎</button>
|
||||
<button class="rl-btn up" id="btn-thumbs-up" title="Keep this">👍</button>
|
||||
</div>
|
||||
<div class="rl-key-hints">
|
||||
<span class="key-hint">1</span>
|
||||
<span class="key-hint">2</span>
|
||||
<button class="rl-btn down" id="btn-thumbs-down" title="Explore more"><span class="rl-icon">👎</span><span class="key-hint">1</span></button>
|
||||
<button class="rl-btn up" id="btn-thumbs-up" title="Keep this"><span class="rl-icon">👍</span><span class="key-hint">2</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
--glass-bg: rgba(13, 13, 13, 0.65);
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-blur: 16px;
|
||||
--accent: #00ff88;
|
||||
--accent-dim: rgba(0, 255, 136, 0.25);
|
||||
--accent: #ff6a00;
|
||||
--accent-dim: rgba(255, 106, 0, 0.25);
|
||||
--danger: #ff4466;
|
||||
--text: #e0e0e0;
|
||||
--text-dim: #888;
|
||||
|
|
@ -28,8 +28,8 @@ html, body {
|
|||
overflow: hidden;
|
||||
background: #0d0d0d;
|
||||
color: var(--text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
@ -47,7 +47,7 @@ html, body {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border-radius: 18px;
|
||||
border-radius: 6px;
|
||||
background: rgba(13, 13, 13, 0.65);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
|
|
@ -55,11 +55,12 @@ html, body {
|
|||
color: var(--text);
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
transition: background 0.15s;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.back-btn:active {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
.back-btn:hover, .back-btn:active {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ---- Fullscreen canvas ---- */
|
||||
|
|
@ -191,7 +192,7 @@ html, body {
|
|||
inset: -8px;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(circle, rgba(0,255,136,0.06) 0%, transparent 70%);
|
||||
background: radial-gradient(circle, rgba(255,106,0,0.06) 0%, transparent 70%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
|
@ -223,8 +224,8 @@ html, body {
|
|||
transform: translateX(-50%);
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
border: 1px solid rgba(0, 255, 136, 0.4);
|
||||
background: rgba(255, 106, 0, 0.15);
|
||||
border: 1px solid rgba(255, 106, 0, 0.4);
|
||||
color: var(--accent);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
|
|
@ -239,34 +240,27 @@ html, body {
|
|||
|
||||
/* Joystick follow-mode pulsing border on canvas */
|
||||
.joystick-container.follow-active #joy-map {
|
||||
box-shadow: 0 0 0 2px rgba(0, 255, 136, 0.5);
|
||||
box-shadow: 0 0 0 2px rgba(255, 106, 0, 0.5);
|
||||
animation: follow-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes follow-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 2px rgba(0, 255, 136, 0.3); }
|
||||
50% { box-shadow: 0 0 12px 3px rgba(0, 255, 136, 0.5); }
|
||||
0%, 100% { box-shadow: 0 0 0 2px rgba(255, 106, 0, 0.3); }
|
||||
50% { box-shadow: 0 0 12px 3px rgba(255, 106, 0, 0.5); }
|
||||
}
|
||||
|
||||
/* ---- RL floating buttons ---- */
|
||||
.rl-buttons {
|
||||
position: fixed;
|
||||
bottom: 170px;
|
||||
bottom: 92px; /* just above 88px collapsed sheet */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
z-index: 25;
|
||||
gap: 32px;
|
||||
z-index: 45;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.key-num {
|
||||
font-size: 10px;
|
||||
opacity: 0.5;
|
||||
margin-left: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.rl-buttons.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
|
@ -281,13 +275,28 @@ html, body {
|
|||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
color: var(--text);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 2px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.rl-icon {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.key-num {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-family: inherit;
|
||||
line-height: 1;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.rl-btn:active {
|
||||
|
|
@ -295,23 +304,25 @@ html, body {
|
|||
}
|
||||
|
||||
.rl-up {
|
||||
border-color: rgba(0, 255, 136, 0.3);
|
||||
color: var(--accent);
|
||||
border-color: rgba(0, 200, 160, 0.4);
|
||||
color: #00c8a0;
|
||||
background: rgba(0, 200, 160, 0.08);
|
||||
}
|
||||
|
||||
.rl-up:active {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
|
||||
background: rgba(0, 200, 160, 0.2);
|
||||
box-shadow: 0 0 20px rgba(0, 200, 160, 0.3);
|
||||
}
|
||||
|
||||
.rl-down {
|
||||
border-color: rgba(255, 68, 102, 0.3);
|
||||
color: var(--danger);
|
||||
border-color: rgba(220, 60, 20, 0.4);
|
||||
color: #dc3c14;
|
||||
background: rgba(220, 60, 20, 0.08);
|
||||
}
|
||||
|
||||
.rl-down:active {
|
||||
background: rgba(255, 68, 102, 0.15);
|
||||
box-shadow: 0 0 20px rgba(255, 68, 102, 0.3);
|
||||
background: rgba(220, 60, 20, 0.2);
|
||||
box-shadow: 0 0 20px rgba(220, 60, 20, 0.3);
|
||||
}
|
||||
|
||||
.rl-btn.flash {
|
||||
|
|
@ -342,7 +353,7 @@ html, body {
|
|||
/* Float button (randomize in floating bar) */
|
||||
.float-btn {
|
||||
padding: 4px 12px;
|
||||
border-radius: 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--text-dim);
|
||||
|
|
@ -352,13 +363,13 @@ html, body {
|
|||
}
|
||||
|
||||
.float-btn:active {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
background: rgba(255, 106, 0, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.follow-pill {
|
||||
padding: 4px 12px;
|
||||
border-radius: 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--text-dim);
|
||||
|
|
@ -368,8 +379,8 @@ html, body {
|
|||
}
|
||||
|
||||
.follow-pill.active {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
border-color: rgba(0, 255, 136, 0.4);
|
||||
background: rgba(255, 106, 0, 0.15);
|
||||
border-color: rgba(255, 106, 0, 0.4);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +462,7 @@ html, body {
|
|||
/* Pill toggle */
|
||||
.pill-toggle {
|
||||
display: inline-flex;
|
||||
border-radius: 20px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--glass-border);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
|
|
@ -484,7 +495,7 @@ html, body {
|
|||
|
||||
.action-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--glass-border);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text);
|
||||
|
|
@ -499,13 +510,13 @@ html, body {
|
|||
}
|
||||
|
||||
.action-btn.accent {
|
||||
border-color: rgba(0, 255, 136, 0.3);
|
||||
border-color: rgba(255, 106, 0, 0.3);
|
||||
color: var(--accent);
|
||||
background: rgba(0, 255, 136, 0.08);
|
||||
background: rgba(255, 106, 0, 0.08);
|
||||
}
|
||||
|
||||
.action-btn.accent:active {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
background: rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
|
||||
.action-btn.dim {
|
||||
|
|
@ -518,7 +529,7 @@ html, body {
|
|||
}
|
||||
|
||||
@keyframes btn-flash {
|
||||
0% { background: rgba(0, 255, 136, 0.3); }
|
||||
0% { background: rgba(255, 106, 0, 0.3); }
|
||||
100% { background: rgba(255, 255, 255, 0.06); }
|
||||
}
|
||||
|
||||
|
|
@ -542,7 +553,7 @@ html, body {
|
|||
|
||||
.preset-chip {
|
||||
padding: 4px 12px;
|
||||
border-radius: 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--text-dim);
|
||||
|
|
@ -553,7 +564,7 @@ html, body {
|
|||
}
|
||||
|
||||
.preset-chip:active {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
background: rgba(255, 106, 0, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
|
|
@ -709,9 +720,9 @@ html, body {
|
|||
}
|
||||
|
||||
.play-btn.playing {
|
||||
border-color: rgba(0, 255, 136, 0.4);
|
||||
border-color: rgba(255, 106, 0, 0.4);
|
||||
color: var(--accent);
|
||||
background: rgba(0, 255, 136, 0.12);
|
||||
background: rgba(255, 106, 0, 0.12);
|
||||
}
|
||||
|
||||
.play-drawer {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
--text: #e0e0e0;
|
||||
--text-dim: #888;
|
||||
--text-muted: #555;
|
||||
--accent-visual: #00ff88;
|
||||
--accent-synth: #ff6b35;
|
||||
--accent-visual: #ff6a00;
|
||||
--accent-synth: #ff8c00;
|
||||
--accent: var(--accent-visual);
|
||||
--danger: #ff4444;
|
||||
--good: #00cc66;
|
||||
--bad: #ff6644;
|
||||
--good: #ff8c00;
|
||||
--bad: #ff4444;
|
||||
--radius: 8px;
|
||||
--radius-sm: 4px;
|
||||
--transition: 0.2s ease;
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
||||
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
overflow-x: hidden;
|
||||
|
|
@ -575,29 +575,48 @@ body.synth-mode {
|
|||
.wb-btn-danger:hover { background: var(--danger); color: var(--bg); }
|
||||
|
||||
.wb-btn-good {
|
||||
background: var(--good);
|
||||
color: var(--bg);
|
||||
border-color: var(--good);
|
||||
background: rgba(0, 200, 160, 0.12);
|
||||
color: #00c8a0;
|
||||
border-color: rgba(0, 200, 160, 0.4);
|
||||
font-weight: 600;
|
||||
}
|
||||
.wb-btn-good:hover {
|
||||
background: rgba(0, 200, 160, 0.25);
|
||||
}
|
||||
.wb-btn-bad {
|
||||
background: var(--bad);
|
||||
color: var(--bg);
|
||||
border-color: var(--bad);
|
||||
background: rgba(220, 60, 20, 0.12);
|
||||
color: #dc3c14;
|
||||
border-color: rgba(220, 60, 20, 0.4);
|
||||
font-weight: 600;
|
||||
}
|
||||
.wb-btn-bad:hover {
|
||||
background: rgba(220, 60, 20, 0.25);
|
||||
}
|
||||
|
||||
.wb-btn.flash {
|
||||
filter: brightness(1.8);
|
||||
}
|
||||
|
||||
.wb-btn-bad,
|
||||
.wb-btn-good {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.wb-btn-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wb-key-hint {
|
||||
font-size: 10px;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
||||
color: #555;
|
||||
font-size: 11px;
|
||||
font-family: inherit;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Synth controls */
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@
|
|||
--glass-bg: rgba(13, 13, 13, 0.6);
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-blur: 16px;
|
||||
--accent: #00ff88;
|
||||
--accent-dim: rgba(0, 255, 136, 0.2);
|
||||
--accent-glow: rgba(0, 255, 136, 0.4);
|
||||
--accent: #ff6a00;
|
||||
--accent-dim: rgba(255, 106, 0, 0.2);
|
||||
--accent-glow: rgba(255, 106, 0, 0.4);
|
||||
--danger: #ff4466;
|
||||
--text: #e0e0e0;
|
||||
--text-dim: rgba(255, 255, 255, 0.45);
|
||||
--text-muted: rgba(255, 255, 255, 0.25);
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--radius-pill: 20px;
|
||||
--radius-pill: 6px;
|
||||
--transition: 400ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--transition-fast: 200ms ease-out;
|
||||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||
|
|
@ -34,8 +34,8 @@ html, body {
|
|||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
@ -292,7 +292,7 @@ html, body {
|
|||
}
|
||||
|
||||
.cta-pill.primary:hover, .cta-pill.primary:active {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
background: rgba(255, 106, 0, 0.3);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
|
@ -565,7 +565,7 @@ html, body {
|
|||
.rl-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
gap: 48px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
|
|
@ -577,22 +577,41 @@ html, body {
|
|||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 2px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.rl-icon {
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rl-btn.up {
|
||||
border-color: rgba(0, 200, 160, 0.4);
|
||||
color: #00c8a0;
|
||||
background: rgba(0, 200, 160, 0.08);
|
||||
}
|
||||
|
||||
.rl-btn.up:hover, .rl-btn.up:active {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
background: rgba(0, 200, 160, 0.2);
|
||||
box-shadow: 0 0 20px rgba(0, 200, 160, 0.3);
|
||||
}
|
||||
|
||||
.rl-btn.down {
|
||||
border-color: rgba(220, 60, 20, 0.4);
|
||||
color: #dc3c14;
|
||||
background: rgba(220, 60, 20, 0.08);
|
||||
}
|
||||
|
||||
.rl-btn.down:hover, .rl-btn.down:active {
|
||||
border-color: var(--danger);
|
||||
background: rgba(255, 68, 102, 0.15);
|
||||
background: rgba(220, 60, 20, 0.2);
|
||||
box-shadow: 0 0 20px rgba(220, 60, 20, 0.3);
|
||||
}
|
||||
|
||||
/* Synth section */
|
||||
|
|
@ -680,7 +699,7 @@ html, body {
|
|||
}
|
||||
|
||||
.done-btn:hover, .done-btn:active {
|
||||
background: #33ffaa;
|
||||
background: #ff8833;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
|
@ -710,17 +729,17 @@ html, body {
|
|||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
background: rgba(255, 106, 0, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.accent {
|
||||
background: rgba(0, 255, 136, 0.12);
|
||||
border-color: rgba(0, 255, 136, 0.3);
|
||||
background: rgba(255, 106, 0, 0.12);
|
||||
border-color: rgba(255, 106, 0, 0.3);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.action-btn.accent:hover {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
background: rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
|
||||
.action-btn.dim {
|
||||
|
|
@ -882,7 +901,7 @@ html, body {
|
|||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 50%;
|
||||
border-radius: 6px;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
|
|
@ -891,26 +910,20 @@ html, body {
|
|||
}
|
||||
|
||||
.back-btn:hover {
|
||||
border-color: var(--text-dim);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RL key hints
|
||||
============================================================ */
|
||||
.rl-key-hints {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.key-hint {
|
||||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
width: 64px;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
--bg-elevated: #252525;
|
||||
--text: #ccc;
|
||||
--text-dim: #666;
|
||||
--accent: #00ff88;
|
||||
--accent-dim: #00cc6a;
|
||||
--accent: #ff6a00;
|
||||
--accent-dim: #cc5500;
|
||||
--danger: #ff3366;
|
||||
--good: #00ff88;
|
||||
--bad: #ff6644;
|
||||
--synth-accent: #ff6b35;
|
||||
--good: #ff8c00;
|
||||
--bad: #ff4444;
|
||||
--synth-accent: #ff8c00;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
|
@ -19,8 +19,8 @@
|
|||
html, body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
touch-action: manipulation;
|
||||
|
|
|
|||
|
|
@ -5,79 +5,115 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NISPS Playground</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0a0a0a;
|
||||
--surface: #111;
|
||||
--border: #1e1e1e;
|
||||
--accent: #ff6a00;
|
||||
--accent-dim: rgba(255, 106, 0, 0.12);
|
||||
--text: #c8c8c8;
|
||||
--text-dim: #606060;
|
||||
--mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background: #0d0d0d;
|
||||
color: #ccc;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.page {
|
||||
max-width: 780px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 24px 64px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #00ff88;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.tagline {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 32px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.intro {
|
||||
list-style: none;
|
||||
margin-bottom: 48px;
|
||||
padding: 0;
|
||||
}
|
||||
.intro li {
|
||||
font-size: 15px;
|
||||
color: #aaa;
|
||||
line-height: 1.6;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.intro li::before {
|
||||
|
||||
/* Faint grid background */
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #333;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,106,0,0.02) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,106,0,0.02) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 56px 24px 80px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--mono);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: var(--text-dim);
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: #444;
|
||||
background: #1f1f1f;
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 20px rgba(255, 106, 0, 0.06), inset 0 0 20px rgba(255, 106, 0, 0.02);
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
border-radius: 16px 16px 0 0;
|
||||
height: 220px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #111;
|
||||
background: #080808;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.preview iframe {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
|
|
@ -86,106 +122,89 @@
|
|||
border: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 24px 28px 28px;
|
||||
position: relative;
|
||||
}
|
||||
.card-label {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.card:nth-child(1) .card-label { color: #00ff88; }
|
||||
.card:nth-child(2) .card-label { color: #3a86ff; }
|
||||
.card:nth-child(3) .card-label { color: #ff6b35; }
|
||||
.card-body h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #eee;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.card-body p {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-link {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #00ff88;
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.card:nth-child(2) .card-link { color: #3a86ff; }
|
||||
.card:nth-child(3) .card-link { color: #ff6b35; }
|
||||
.card-link:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.page { max-width: 1100px; }
|
||||
.card-body h2 {
|
||||
font-family: var(--mono);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card:hover .card-body h2 {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Desktop: horizontal row */
|
||||
@media (min-width: 860px) {
|
||||
.cards {
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
.card { flex: 1; }
|
||||
.card {
|
||||
flex: 1;
|
||||
}
|
||||
.preview {
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large: more breathing room */
|
||||
@media (min-width: 1100px) {
|
||||
.page { max-width: 1100px; }
|
||||
.preview { height: 280px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>NISPS Playground</h1>
|
||||
<p class="tagline">Neural Interactive Shaping of Parameter Spaces</p>
|
||||
|
||||
<ul class="intro">
|
||||
<li>Map a 2D joystick to 20 visual parameters or 126 synthesizer parameters through a neural network</li>
|
||||
<li>Two learning modes: show examples of what you want, or give thumbs up/down feedback</li>
|
||||
<li>The network learns to interpolate — move the joystick and the entire parameter space responds</li>
|
||||
<li>You don't need to understand the parameters — just explore and teach</li>
|
||||
</ul>
|
||||
<div class="header">
|
||||
<h1>NISPS Playground
|
||||
<span>Neural Interactive Shaping of Parameter Spaces</span>
|
||||
</h1>
|
||||
<p class="intro">Map a 2D joystick to 126 synthesizer parameters through a neural network. Two learning modes: show examples of what you want, or give thumbs up/down feedback. The network learns to interpolate the space between them.</p>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<a class="card" href="a-immersive.html">
|
||||
<div class="preview">
|
||||
<iframe src="a-immersive.html" loading="lazy"></iframe>
|
||||
<iframe src="a-immersive.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="card-label">A</span>
|
||||
<h2>Immersive</h2>
|
||||
<p>Full-screen canvas with floating controls. The visualization IS the interface — everything else gets out of the way.</p>
|
||||
<a href="a-immersive.html" class="card-link">Try it →</a>
|
||||
<p>Full-screen canvas with floating controls. The visualization is the interface.</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="card">
|
||||
<a class="card" href="b-workbench.html">
|
||||
<div class="preview">
|
||||
<iframe src="b-workbench.html" loading="lazy"></iframe>
|
||||
<iframe src="b-workbench.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="card-label">B</span>
|
||||
<h2>Workbench</h2>
|
||||
<p>Dashboard layout with a 2D heatmap showing what the network has learned. Built for understanding the mapping.</p>
|
||||
<a href="b-workbench.html" class="card-link">Try it →</a>
|
||||
<p>Dashboard layout with a 2D heatmap. Built for understanding the mapping.</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="card">
|
||||
<a class="card" href="c-journey.html">
|
||||
<div class="preview">
|
||||
<iframe src="c-journey.html" loading="lazy"></iframe>
|
||||
<iframe src="c-journey.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="card-label">C</span>
|
||||
<h2>Journey</h2>
|
||||
<p>Three phases — Explore, Teach, Perform — that dissolve as you master the mapping. The UI fades away after training.</p>
|
||||
<a href="c-journey.html" class="card-link">Try it →</a>
|
||||
<p>Three phases that dissolve as you master the mapping. The UI fades away.</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,152 +2,210 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NISPS Playground</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0a0a0a;
|
||||
--surface: #111;
|
||||
--border: #1e1e1e;
|
||||
--accent: #ff6a00;
|
||||
--accent-dim: rgba(255, 106, 0, 0.12);
|
||||
--text: #c8c8c8;
|
||||
--text-dim: #606060;
|
||||
--mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Faint grid background */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,106,0,0.02) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,106,0,0.02) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 56px 24px 80px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--mono);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: var(--text-dim);
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 20px rgba(255, 106, 0, 0.06), inset 0 0 20px rgba(255, 106, 0, 0.02);
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #080808;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.preview iframe {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top left;
|
||||
border: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
.card-body h2 {
|
||||
font-family: var(--mono);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card:hover .card-body h2 {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Desktop: horizontal row */
|
||||
@media (min-width: 860px) {
|
||||
.cards {
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
.card {
|
||||
flex: 1;
|
||||
}
|
||||
.preview {
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large: more breathing room */
|
||||
@media (min-width: 1100px) {
|
||||
.page { max-width: 1100px; }
|
||||
.preview { height: 280px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
|
||||
<!-- Side Panel (collapsible) -->
|
||||
<div class="side-panel" id="side-panel">
|
||||
<div class="side-panel-header">
|
||||
<span class="side-panel-title">Mode</span>
|
||||
<button class="side-panel-close" id="side-panel-close">×</button>
|
||||
</div>
|
||||
|
||||
<div class="side-panel-tabs">
|
||||
<button class="sp-tab active" data-mode="visual">Visual</button>
|
||||
<button class="sp-tab" data-mode="synth">Synth</button>
|
||||
</div>
|
||||
|
||||
<!-- Synth controls (shown when synth mode active) -->
|
||||
<div class="synth-controls hidden" id="synth-controls">
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Engine</label>
|
||||
<button class="btn btn-primary synth-start-btn" id="synth-start">Start Audio</button>
|
||||
<div class="synth-status" id="synth-status">Stopped</div>
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Volume</label>
|
||||
<input type="range" class="synth-slider" id="synth-volume" min="0" max="1" step="0.01" value="0.5">
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Arpeggiator</label>
|
||||
<button class="btn synth-arp-btn" id="arp-toggle">Play</button>
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Progression</label>
|
||||
<select class="synth-select" id="arp-progression">
|
||||
<option value="I-vi-IV-V">I-vi-IV-V</option>
|
||||
<option value="I-IV-vi-V">I-IV-vi-V</option>
|
||||
<option value="i-VI-III-VII">i-VI-III-VII</option>
|
||||
<option value="I-V-vi-IV">I-V-vi-IV</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Tempo <span id="tempo-val">120</span> BPM</label>
|
||||
<input type="range" class="synth-slider" id="arp-tempo" min="40" max="240" step="1" value="120">
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Octaves <span id="octaves-val">2</span></label>
|
||||
<input type="range" class="synth-slider" id="arp-octaves" min="1" max="5" step="1" value="2">
|
||||
</div>
|
||||
|
||||
<div class="synth-section">
|
||||
<label class="synth-label">Oct Offset <span id="offset-val">0</span></label>
|
||||
<input type="range" class="synth-slider" id="arp-offset" min="-2" max="3" step="1" value="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual mode info (shown when visual mode active) -->
|
||||
<div class="visual-info" id="visual-info">
|
||||
<p class="sp-hint">Move the joystick and train the neural network to map positions to visual parameters.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side panel toggle tab (always visible) -->
|
||||
<button class="side-panel-toggle" id="side-panel-toggle" title="Open mode panel">☰</button>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<h1>NISPS Playground</h1>
|
||||
<div class="header-right">
|
||||
<span class="mode-badge" id="mode-badge">Visual</span>
|
||||
<button id="help-btn" title="Help">?</button>
|
||||
</div>
|
||||
<h1>NISPS Playground
|
||||
<span>Neural Interactive Shaping of Parameter Spaces</span>
|
||||
</h1>
|
||||
<p class="intro">Map a 2D joystick to 126 synthesizer parameters through a neural network. Two learning modes: show examples of what you want, or give thumbs up/down feedback. The network learns to interpolate the space between them.</p>
|
||||
</div>
|
||||
|
||||
<!-- Visual output -->
|
||||
<div class="visual-container">
|
||||
<canvas id="visual-canvas"></canvas>
|
||||
<div class="presets" id="presets-visual">
|
||||
<button class="preset-pill" onclick="loadPreset('calm-to-chaotic')">Calm/Chaos</button>
|
||||
<button class="preset-pill" onclick="loadPreset('rainbow-sweep')">Rainbow</button>
|
||||
<button class="preset-pill" onclick="loadPreset('vortex')">Vortex</button>
|
||||
</div>
|
||||
<button id="expand-visual-btn" class="expand-visual-btn" type="button" title="Expand visual area" aria-pressed="false">Expand</button>
|
||||
<div class="cards">
|
||||
<a class="card" href="a-immersive.html">
|
||||
<div class="preview">
|
||||
<iframe src="a-immersive.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h2>Immersive</h2>
|
||||
<p>Full-screen canvas with floating controls. The visualization is the interface.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="card" href="b-workbench.html">
|
||||
<div class="preview">
|
||||
<iframe src="b-workbench.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h2>Workbench</h2>
|
||||
<p>Dashboard layout with a 2D heatmap. Built for understanding the mapping.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="card" href="c-journey.html">
|
||||
<div class="preview">
|
||||
<iframe src="c-journey.html" loading="lazy" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h2>Journey</h2>
|
||||
<p>Three phases that dissolve as you master the mapping. The UI fades away.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Parameter bars -->
|
||||
<div class="param-container" id="param-display"></div>
|
||||
|
||||
<!-- Joystick + Controls -->
|
||||
<div class="controls-area">
|
||||
<div id="joystick-container"></div>
|
||||
<div id="controls-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- Side panel backdrop -->
|
||||
<div class="side-panel-backdrop hidden" id="side-panel-backdrop"></div>
|
||||
|
||||
<!-- Help overlay -->
|
||||
<div class="help-overlay hidden" id="help-overlay">
|
||||
<div class="help-content" onclick="event.stopPropagation()">
|
||||
<h2>NISPS Playground</h2>
|
||||
<p>Train a neural network to map joystick positions to output parameters using interactive machine learning.</p>
|
||||
|
||||
<h3>Modes</h3>
|
||||
<p>Open the side panel (☰) to switch between <strong>Visual</strong> and <strong>Synth</strong> modes.</p>
|
||||
|
||||
<h3>Visual Mode</h3>
|
||||
<p>20 outputs control a flow-field particle system.</p>
|
||||
|
||||
<h3>Synth Mode</h3>
|
||||
<p>20 outputs control a C15 synthesizer engine. Use the arpeggiator for automatic note playback.</p>
|
||||
|
||||
<h3>Examples Mode</h3>
|
||||
<ol>
|
||||
<li>Move the joystick to a position</li>
|
||||
<li>Drag the parameter bars to set desired output</li>
|
||||
<li>Press <strong>Add Example</strong> to save this mapping</li>
|
||||
<li>Repeat for different joystick positions</li>
|
||||
<li>Press <strong>Train</strong> — the network learns to interpolate between your examples</li>
|
||||
</ol>
|
||||
|
||||
<h3>RL Feedback Mode</h3>
|
||||
<ol>
|
||||
<li>Move the joystick — the network produces outputs</li>
|
||||
<li>See something you like? Press <strong>+</strong> (thumbs up)</li>
|
||||
<li>Don't like it? Press <strong>−</strong> (thumbs down) to explore more</li>
|
||||
<li>Double-click the joystick to toggle <strong>Follow</strong> mode</li>
|
||||
</ol>
|
||||
|
||||
<h3>Tips</h3>
|
||||
<p>Your training data auto-saves to your browser. Gamepad supported (LB/RB for RL feedback).</p>
|
||||
<p style="margin-top: 16px; text-align: center; color: #444;">Tap anywhere outside to close</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const VISUAL_PARAM_NAMES = [
|
|||
'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate',
|
||||
];
|
||||
const VISUAL_PARAM_COLORS = [
|
||||
'#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff',
|
||||
'#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f',
|
||||
'#ffc15f', '#ff8a3d',
|
||||
|
|
@ -67,6 +67,7 @@ let arpeggiator = null;
|
|||
let learningMode = 'rl';
|
||||
let outputMode = 'visual';
|
||||
let tameLevel = 0;
|
||||
let spreadLevel = 0.6;
|
||||
let noiseLevel = 0.05;
|
||||
const rlExplorationDecay = 0.97;
|
||||
|
||||
|
|
@ -254,7 +255,7 @@ class SynthVisualizer {
|
|||
const dpr = window.devicePixelRatio || 1;
|
||||
const fontSize = 9 * dpr;
|
||||
ctx.save();
|
||||
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`;
|
||||
ctx.font = `${fontSize}px 'JetBrains Mono', monospace`;
|
||||
ctx.fillStyle = color + '88';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
|
|
@ -360,6 +361,9 @@ function init() {
|
|||
// Parse ?tame URL param
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
tameLevel = parseFloat(urlParams.get('tame') ?? '0');
|
||||
spreadLevel = parseFloat(urlParams.get('spread') ?? '0');
|
||||
if (isNaN(spreadLevel)) spreadLevel = 0;
|
||||
spreadLevel = Math.max(0, Math.min(1, spreadLevel));
|
||||
|
||||
// IML — fresh random weights each boot, no state restoration
|
||||
iml = new IML(N_INPUTS, N_OUTPUTS, [32, 48, 64], 1000, 1.0, 0.00001);
|
||||
|
|
@ -557,9 +561,9 @@ function drawJoyMap() {
|
|||
const py = (1 - joyY) * h;
|
||||
|
||||
// Glow
|
||||
ctx.shadowColor = 'rgba(0, 255, 136, 0.6)';
|
||||
ctx.shadowColor = 'rgba(255, 106, 0, 0.6)';
|
||||
ctx.shadowBlur = 12;
|
||||
ctx.fillStyle = 'rgba(0, 255, 136, 0.9)';
|
||||
ctx.fillStyle = 'rgba(255, 106, 0, 0.9)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 8, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
|
@ -572,7 +576,7 @@ function drawJoyMap() {
|
|||
ctx.fill();
|
||||
|
||||
// Crosshair
|
||||
ctx.strokeStyle = 'rgba(0, 255, 136, 0.2)';
|
||||
ctx.strokeStyle = 'rgba(255, 106, 0, 0.2)';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.beginPath();
|
||||
|
|
@ -864,7 +868,7 @@ function onTrain() {
|
|||
}
|
||||
|
||||
function onRandomize() {
|
||||
iml.randomiseWeights();
|
||||
iml.randomiseWeights(spreadLevel);
|
||||
const outputs = iml.getOutputs();
|
||||
routeOutputs(outputs);
|
||||
updateHeatmap(outputs);
|
||||
|
|
@ -982,7 +986,7 @@ function drawLossPlot() {
|
|||
const maxLoss = Math.max(...history.slice(-200)) || 1;
|
||||
const points = history.slice(-200);
|
||||
|
||||
ctx.strokeStyle = 'rgba(0, 255, 136, 0.6)';
|
||||
ctx.strokeStyle = 'rgba(255, 106, 0, 0.6)';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const N_OUTPUTS = N_SYNTH_OUTPUTS; // MLP always produces full output; visual us
|
|||
|
||||
// Visual mode param display config
|
||||
const VISUAL_PARAM_NAMES = ['Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate'];
|
||||
const VISUAL_PARAM_COLORS = ['#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
|
||||
const VISUAL_PARAM_COLORS = ['#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
|
||||
|
||||
// --- State ---
|
||||
let iml;
|
||||
|
|
@ -45,9 +45,13 @@ let arpeggiator = null;
|
|||
|
||||
// Devmode: tame level (0 = no mitigation, 1 = strongest)
|
||||
// Set via URL ?tame=0.7 or window.setTameLevel(0.7)
|
||||
let tameLevel = parseFloat(new URLSearchParams(location.search).get('tame') ?? '0.7');
|
||||
const _urlParams = new URLSearchParams(location.search);
|
||||
let tameLevel = parseFloat(_urlParams.get('tame') ?? '0.7');
|
||||
if (isNaN(tameLevel)) tameLevel = 0.7;
|
||||
tameLevel = Math.max(0, Math.min(1, tameLevel));
|
||||
let spreadLevel = parseFloat(_urlParams.get('spread') ?? '0');
|
||||
if (isNaN(spreadLevel)) spreadLevel = 0.6;
|
||||
spreadLevel = Math.max(0, Math.min(1, spreadLevel));
|
||||
window.setTameLevel = (v) => { tameLevel = Math.max(0, Math.min(1, v)); console.log(`[NISPS] tame=${tameLevel}`); };
|
||||
window.getTameLevel = () => tameLevel;
|
||||
|
||||
|
|
@ -340,7 +344,7 @@ function onTrain() {
|
|||
}
|
||||
|
||||
function onRandomize() {
|
||||
iml.randomiseWeights();
|
||||
iml.randomiseWeights(spreadLevel);
|
||||
const outputs = iml.getOutputs();
|
||||
routeOutputs(outputs);
|
||||
paramDisplay.update(outputs);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const VISUAL_PARAM_NAMES = [
|
|||
'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate'
|
||||
];
|
||||
const VISUAL_PARAM_COLORS = [
|
||||
'#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff',
|
||||
'#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f',
|
||||
'#ffc15f', '#ff8a3d'
|
||||
|
|
@ -91,6 +91,7 @@ function heatmapColor(t) {
|
|||
// --- Tame URL param ---
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
const tameLevel = parseFloat(_urlParams.get('tame') ?? '0');
|
||||
const spreadLevel = Math.max(0, Math.min(1, parseFloat(_urlParams.get('spread') ?? '0') || 0));
|
||||
|
||||
// --- State ---
|
||||
let iml;
|
||||
|
|
@ -402,7 +403,7 @@ class MappingView {
|
|||
// Current joystick position as crosshair
|
||||
const px = joyX * w;
|
||||
const py = joyY * h;
|
||||
const accent = outputMode === 'synth' ? '#ff6b35' : '#00ff88';
|
||||
const accent = outputMode === 'synth' ? '#ff8c00' : '#ff6a00';
|
||||
|
||||
ctx.strokeStyle = accent;
|
||||
ctx.lineWidth = 1;
|
||||
|
|
@ -498,7 +499,7 @@ function drawLossPlot(canvas, history) {
|
|||
for (const v of history) if (v > maxLoss) maxLoss = v;
|
||||
if (maxLoss === 0) maxLoss = 1;
|
||||
|
||||
const accent = outputMode === 'synth' ? '#ff6b35' : '#00ff88';
|
||||
const accent = outputMode === 'synth' ? '#ff8c00' : '#ff6a00';
|
||||
|
||||
// Draw line
|
||||
ctx.strokeStyle = accent;
|
||||
|
|
@ -734,7 +735,7 @@ function onTrain() {
|
|||
}
|
||||
|
||||
function onRandomize() {
|
||||
iml.randomiseWeights();
|
||||
iml.randomiseWeights(spreadLevel);
|
||||
const outputs = iml.getOutputs();
|
||||
routeOutputs(outputs);
|
||||
updateAllParamBars(outputs);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const VISUAL_PARAM_NAMES = [
|
|||
'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate',
|
||||
];
|
||||
const VISUAL_PARAM_COLORS = [
|
||||
'#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00',
|
||||
'#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff',
|
||||
'#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f',
|
||||
'#ffc15f', '#ff8a3d',
|
||||
|
|
@ -49,6 +49,7 @@ const PRESETS = [
|
|||
// --- Tame URL param ---
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
const tameLevel = parseFloat(_urlParams.get('tame') ?? '0');
|
||||
const spreadLevel = Math.max(0, Math.min(1, parseFloat(_urlParams.get('spread') ?? '0') || 0));
|
||||
|
||||
// ============================================================
|
||||
// State
|
||||
|
|
@ -180,7 +181,7 @@ class SynthVisualizer {
|
|||
// Section label
|
||||
const sectionWidth = barWidth * section.count;
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
|
||||
ctx.font = '9px -apple-system, sans-serif';
|
||||
ctx.font = '9px 'JetBrains Mono', monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(section.name, x + sectionWidth / 2, h - 6);
|
||||
|
||||
|
|
@ -410,7 +411,7 @@ function buildHeatmap() {
|
|||
cell.dataset.index = i;
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'heatmap-bar';
|
||||
bar.style.backgroundColor = currentParamColors[i] || '#00ff88';
|
||||
bar.style.backgroundColor = currentParamColors[i] || '#ff6a00';
|
||||
bar.style.width = '50%';
|
||||
cell.appendChild(bar);
|
||||
container.appendChild(cell);
|
||||
|
|
@ -628,7 +629,7 @@ function wireTeachActions() {
|
|||
|
||||
document.getElementById('btn-clear')?.addEventListener('click', () => {
|
||||
iml.clearDataset();
|
||||
iml.randomiseWeights();
|
||||
iml.randomiseWeights(spreadLevel);
|
||||
selectedPreset = -1;
|
||||
document.querySelectorAll('.preset-thumb').forEach(b => b.classList.remove('selected'));
|
||||
updateTeachStatus();
|
||||
|
|
@ -637,7 +638,7 @@ function wireTeachActions() {
|
|||
});
|
||||
|
||||
document.getElementById('btn-randomize')?.addEventListener('click', () => {
|
||||
iml.randomiseWeights();
|
||||
iml.randomiseWeights(spreadLevel);
|
||||
iml.setInput(0, joystickX);
|
||||
iml.setInput(1, joystickY);
|
||||
iml.process();
|
||||
|
|
@ -912,16 +913,16 @@ function drawJoystick() {
|
|||
|
||||
ctx.beginPath();
|
||||
ctx.arc(knobX, knobY, knobRadius, 0, TWO_PI);
|
||||
ctx.fillStyle = 'rgba(0, 255, 136, 0.7)';
|
||||
ctx.fillStyle = 'rgba(255, 106, 0, 0.7)';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(0, 255, 136, 0.4)';
|
||||
ctx.strokeStyle = 'rgba(255, 106, 0, 0.4)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Glow
|
||||
const glow = ctx.createRadialGradient(knobX, knobY, 0, knobX, knobY, knobRadius * 2.5);
|
||||
glow.addColorStop(0, 'rgba(0, 255, 136, 0.15)');
|
||||
glow.addColorStop(1, 'rgba(0, 255, 136, 0)');
|
||||
glow.addColorStop(0, 'rgba(255, 106, 0, 0.15)');
|
||||
glow.addColorStop(1, 'rgba(255, 106, 0, 0)');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath();
|
||||
ctx.arc(knobX, knobY, knobRadius * 2.5, 0, TWO_PI);
|
||||
|
|
@ -999,7 +1000,7 @@ function drawMinimap() {
|
|||
if (!f || f.length < 2) continue;
|
||||
const x = f[0] * w;
|
||||
const y = f[1] * h;
|
||||
ctx.fillStyle = 'rgba(0, 255, 136, 0.8)';
|
||||
ctx.fillStyle = 'rgba(255, 106, 0, 0.8)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 3, 0, TWO_PI);
|
||||
ctx.fill();
|
||||
|
|
|
|||
|
|
@ -144,9 +144,9 @@ export class IML {
|
|||
this.log('Dataset cleared.');
|
||||
}
|
||||
|
||||
randomiseWeights() {
|
||||
randomiseWeights(spread = 0) {
|
||||
this.storedWeights = this.mlp.getWeights();
|
||||
this.mlp.drawWeights();
|
||||
this.mlp.drawWeights(spread);
|
||||
this.weightsRandomised = true;
|
||||
|
||||
// Run inference to show effect
|
||||
|
|
|
|||
|
|
@ -204,13 +204,18 @@ export class MLP {
|
|||
}
|
||||
}
|
||||
|
||||
// DrawWeights - randomize all weights uniformly in [-1, 1]
|
||||
drawWeights(scale = 1) {
|
||||
for (const layer of this.layers) {
|
||||
for (const node of layer.nodes) {
|
||||
// DrawWeights - randomize all weights
|
||||
// spread: 0 = uniform [-1,1] (polarised sigmoid outputs), 1 = Xavier-scaled (centered outputs)
|
||||
drawWeights(spread = 0) {
|
||||
for (let l = 0; l < this.layers.length; l++) {
|
||||
const fanIn = this.layersNodes[l];
|
||||
const xavierScale = 1 / Math.sqrt(fanIn);
|
||||
const scale = 1 * (1 - spread) + xavierScale * spread;
|
||||
for (const node of this.layers[l].nodes) {
|
||||
for (let j = 0; j < node.weights.length; j++) {
|
||||
node.weights[j] = (Math.random() * 2 - 1) * scale;
|
||||
}
|
||||
node.bias = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
playground/js/ui/controls.js
vendored
4
playground/js/ui/controls.js
vendored
|
|
@ -160,14 +160,14 @@ export class Controls {
|
|||
const plotW = width - leftPad - rightPad;
|
||||
const plotH = height - topPad - bottomPad;
|
||||
|
||||
ctx.strokeStyle = '#243523';
|
||||
ctx.strokeStyle = '#352418';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(leftPad, topPad + plotH / 2);
|
||||
ctx.lineTo(width - rightPad, topPad + plotH / 2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.strokeStyle = '#00ff88';
|
||||
ctx.strokeStyle = '#ff6a00';
|
||||
ctx.lineWidth = 1.6;
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export class VirtualJoystick {
|
|||
// Background circle
|
||||
ctx.beginPath();
|
||||
ctx.arc(r, r, r - 4, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = this.followMode ? '#00cc6a' : '#333';
|
||||
ctx.strokeStyle = this.followMode ? '#cc5500' : '#333';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
|
|
@ -159,21 +159,21 @@ export class VirtualJoystick {
|
|||
// Glow
|
||||
if (this.touching) {
|
||||
const grad = ctx.createRadialGradient(tx, ty, 0, tx, ty, 24);
|
||||
grad.addColorStop(0, 'rgba(0, 255, 136, 0.3)');
|
||||
grad.addColorStop(1, 'rgba(0, 255, 136, 0)');
|
||||
grad.addColorStop(0, 'rgba(255, 106, 0, 0.3)');
|
||||
grad.addColorStop(1, 'rgba(255, 106, 0, 0)');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath();
|
||||
ctx.arc(tx, ty, 24, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.fillStyle = this.touching ? '#00ff88' : '#666';
|
||||
ctx.fillStyle = this.touching ? '#ff6a00' : '#666';
|
||||
ctx.beginPath();
|
||||
ctx.arc(tx, ty, 14, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Position text
|
||||
ctx.fillStyle = this.followMode ? '#00cc6a' : '#555';
|
||||
ctx.fillStyle = this.followMode ? '#cc5500' : '#555';
|
||||
ctx.font = '10px monospace';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(this.followMode ? 'FOLLOW' : 'HOLD', 8, s - 6);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Shows output parameters as horizontal bars, draggable in examples mode
|
||||
|
||||
const DEFAULT_PARAM_NAMES = ['Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate'];
|
||||
const DEFAULT_PARAM_COLORS = ['#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
|
||||
const DEFAULT_PARAM_COLORS = ['#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
|
||||
|
||||
export class ParamDisplay {
|
||||
constructor(container, numParams = 20) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue