feat(playground): add help/intro modal shown on first visit
- Glass-style modal explains NISPS, how learning works, and what to expect - Documents all controls: touch, keyboard, and gamepad bindings - Shows automatically on first visit (localStorage flag) - Dismissible via close button, "Got it", overlay click, or Escape - Reopenable via ? button at bottom right
This commit is contained in:
parent
de75a3f0e4
commit
33580f0ae0
3 changed files with 300 additions and 0 deletions
|
|
@ -157,6 +157,76 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Help button -->
|
||||||
|
<button class="help-btn" id="help-btn" title="Help">?</button>
|
||||||
|
|
||||||
|
<!-- Help modal -->
|
||||||
|
<div class="help-overlay hidden" id="help-overlay">
|
||||||
|
<div class="help-modal" id="help-modal">
|
||||||
|
<button class="help-close" id="help-close">×</button>
|
||||||
|
<h2>Welcome to NISPS</h2>
|
||||||
|
<p class="help-subtitle">Neural Interactive Shaping of Parameter Spaces</p>
|
||||||
|
|
||||||
|
<div class="help-section">
|
||||||
|
<h3>What is this?</h3>
|
||||||
|
<p>NISPS lets you <strong>teach a neural network</strong> how to map a 2D joystick position to a rich set of visual or audio parameters. Move the joystick, shape the outputs you want, and the network learns your preferences in real time.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-section">
|
||||||
|
<h3>How it works</h3>
|
||||||
|
<p>A small neural network takes your joystick X/Y position as input and produces dozens of output parameters. You teach it by either:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Examples</strong> — set the parameter sliders to what you want at a given joystick position, add the example, then hit Train. Do this a few times from different positions and the network interpolates between them.</li>
|
||||||
|
<li><strong>Feedback (RL)</strong> — move the joystick around. If you like what you see/hear, press <strong>+</strong> (keep this). If you don't, press <strong>−</strong> (explore more). The network gradually learns what you prefer.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-section">
|
||||||
|
<h3>What to expect</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Moving the joystick</strong> changes all outputs in real time through the network</li>
|
||||||
|
<li><strong>Randomize</strong> shuffles the network weights — instant new mapping</li>
|
||||||
|
<li><strong>Train</strong> fits the network to your saved examples</li>
|
||||||
|
<li><strong>+ / − feedback</strong> nudges the network: + reinforces the current mapping, − adds exploration noise</li>
|
||||||
|
<li>Switch between <strong>Visual</strong> (particle flow field) and <strong>Synth</strong> (C15 synthesizer) output modes</li>
|
||||||
|
<li>The <strong>heatmap bar</strong> at the top shows all output parameters at a glance</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-section">
|
||||||
|
<h3>Controls</h3>
|
||||||
|
<table class="help-keys">
|
||||||
|
<tr><th colspan="2">Touch / Mouse</th></tr>
|
||||||
|
<tr><td>Drag joystick</td><td>Move through parameter space</td></tr>
|
||||||
|
<tr><td>+ / − buttons</td><td>Positive / negative feedback</td></tr>
|
||||||
|
<tr><td>Bottom bar</td><td>Train, Randomize, Clear, Follow mode</td></tr>
|
||||||
|
<tr><th colspan="2">Keyboard</th></tr>
|
||||||
|
<tr><td><kbd>1</kbd></td><td>Negative feedback (−)</td></tr>
|
||||||
|
<tr><td><kbd>2</kbd></td><td>Positive feedback (+)</td></tr>
|
||||||
|
<tr><th colspan="2">Gamepad (Steam Deck, Xbox, etc.)</th></tr>
|
||||||
|
<tr><td>Left stick</td><td>Joystick control</td></tr>
|
||||||
|
<tr><td>LB (left bumper)</td><td>Negative feedback</td></tr>
|
||||||
|
<tr><td>RB (right bumper)</td><td>Positive feedback</td></tr>
|
||||||
|
<tr><td>A</td><td>Train</td></tr>
|
||||||
|
<tr><td>X</td><td>Randomize</td></tr>
|
||||||
|
<tr><td>B</td><td>Clear examples</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-section">
|
||||||
|
<h3>Tips</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Start with <strong>Randomize</strong> a few times to hear/see different mappings, then use feedback to refine</li>
|
||||||
|
<li>Use <strong>Follow</strong> mode to let the joystick wander automatically while you give feedback</li>
|
||||||
|
<li>In Synth mode, press the <strong>play button</strong> (top left) to start audio, then explore</li>
|
||||||
|
<li>Tap a heatmap cell to see which parameter it controls</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="help-got-it" id="help-got-it">Got it</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="module" src="js/a-app.js"></script>
|
<script type="module" src="js/a-app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1093,6 +1093,216 @@ html, body {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Help button ---- */
|
||||||
|
.help-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 96px;
|
||||||
|
right: 12px;
|
||||||
|
z-index: 45;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(var(--glass-blur));
|
||||||
|
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-btn:hover, .help-btn:active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Help modal ---- */
|
||||||
|
.help-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
animation: helpFadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-overlay.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes helpFadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal {
|
||||||
|
position: relative;
|
||||||
|
max-width: 520px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: rgba(18, 18, 18, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 28px 24px 20px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 2px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-subtitle {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
margin: 0 0 16px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-close:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-section h3 {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
margin: 0 0 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-section p {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-section ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-section li {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-keys {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-keys th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 0 4px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-keys td {
|
||||||
|
padding: 3px 0;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-keys td:first-child {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-keys kbd {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-got-it {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(255, 106, 0, 0.3);
|
||||||
|
background: rgba(255, 106, 0, 0.1);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-got-it:active {
|
||||||
|
background: rgba(255, 106, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Help modal scrollbar */
|
||||||
|
.help-modal::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Media queries ---- */
|
/* ---- Media queries ---- */
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.joystick-container {
|
.joystick-container {
|
||||||
|
|
|
||||||
|
|
@ -620,6 +620,7 @@ function init() {
|
||||||
wireKeyboard();
|
wireKeyboard();
|
||||||
wireQuickPlayControls();
|
wireQuickPlayControls();
|
||||||
wireGroupDrawer();
|
wireGroupDrawer();
|
||||||
|
wireHelp();
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize);
|
||||||
|
|
@ -1805,6 +1806,25 @@ function onResize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Help modal ----
|
||||||
|
function wireHelp() {
|
||||||
|
const overlay = document.getElementById('help-overlay');
|
||||||
|
const btnOpen = document.getElementById('help-btn');
|
||||||
|
const btnClose = document.getElementById('help-close');
|
||||||
|
const btnGotIt = document.getElementById('help-got-it');
|
||||||
|
|
||||||
|
function show() { overlay.classList.remove('hidden'); }
|
||||||
|
function hide() { overlay.classList.add('hidden'); localStorage.setItem('nisps-help-seen', '1'); }
|
||||||
|
|
||||||
|
btnOpen.addEventListener('click', show);
|
||||||
|
btnClose.addEventListener('click', hide);
|
||||||
|
btnGotIt.addEventListener('click', hide);
|
||||||
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) hide(); });
|
||||||
|
|
||||||
|
// Show on first visit
|
||||||
|
if (!localStorage.getItem('nisps-help-seen')) show();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Animation ----
|
// ---- Animation ----
|
||||||
function animate() {
|
function animate() {
|
||||||
if (gamepad) gamepad.poll();
|
if (gamepad) gamepad.poll();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue