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:
w1n5t0n 2026-03-22 01:36:00 +02:00
parent b80a9e4e86
commit d9be808aaa
18 changed files with 553 additions and 415 deletions

View file

@ -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">&minus; <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">&minus;</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 -->

View file

@ -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">&minus; 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">&minus; 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>

View file

@ -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">&#128077;</span> Want something different? <span class="thumb-icon">&#128078;</span></p>
<div class="rl-buttons">
<button class="rl-btn down" id="btn-thumbs-down" title="Explore more">&#128078;</button>
<button class="rl-btn up" id="btn-thumbs-up" title="Keep this">&#128077;</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">&#128078;</span><span class="key-hint">1</span></button>
<button class="rl-btn up" id="btn-thumbs-up" title="Keep this"><span class="rl-icon">&#128077;</span><span class="key-hint">2</span></button>
</div>
</div>

View file

@ -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 {

View file

@ -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 */

View file

@ -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;
}
/* ============================================================

View file

@ -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;

View file

@ -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 &rarr;</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 &rarr;</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 &rarr;</a>
<p>Three phases that dissolve as you master the mapping. The UI fades away.</p>
</div>
</div>
</a>
</div>
</div>
</body>

View file

@ -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">&times;</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">&#9776;</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 (&#9776;) 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> &mdash; the network learns to interpolate between your examples</li>
</ol>
<h3>RL Feedback Mode</h3>
<ol>
<li>Move the joystick &mdash; the network produces outputs</li>
<li>See something you like? Press <strong>+</strong> (thumbs up)</li>
<li>Don't like it? Press <strong>&minus;</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>

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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++) {

View file

@ -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);

View file

@ -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) {