diff --git a/playground/a-immersive.html b/playground/a-immersive.html
index 79ad01f..4ccddb1 100644
--- a/playground/a-immersive.html
+++ b/playground/a-immersive.html
@@ -110,6 +110,7 @@
+ Synth
+
+
+ FX
+
+
+
+
0 examples · untrained
@@ -371,6 +379,8 @@
| Keyboard |
| 1 | Negative feedback (−) |
| 2 | Positive feedback (+) |
+ | 3 | FX negative feedback (Linked mode) |
+ | 4 | FX positive feedback (Linked mode) |
| Z | Undo |
| Gamepad (Steam Deck, Xbox, etc.) |
| Left stick | Joystick control |
diff --git a/playground/css/a-immersive.css b/playground/css/a-immersive.css
index 98ee391..f3be6c1 100644
--- a/playground/css/a-immersive.css
+++ b/playground/css/a-immersive.css
@@ -634,6 +634,67 @@ html, body {
animation: rl-flash 0.25s ease-out;
}
+/* ---- RL label (shown in Linked mode to distinguish Synth vs FX) ---- */
+.rl-label {
+ grid-column: 1 / -1;
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ opacity: 0.6;
+ text-align: center;
+ color: var(--text);
+}
+.rl-label.hidden { display: none; }
+
+/* ---- EOC RL buttons (FX feedback, Linked mode) ---- */
+.eoc-rl-buttons {
+ position: fixed;
+ bottom: 36px;
+ left: calc(50% + 100px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ z-index: 45;
+ transition: opacity 0.3s;
+}
+.eoc-rl-buttons.hidden { display: none; }
+
+.eoc-rl-label {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ opacity: 0.6;
+ color: var(--text);
+}
+
+.eoc-rl-btn {
+ width: 48px;
+ height: 48px;
+}
+
+.eoc-rl-btn.rl-up {
+ border-color: rgba(78, 205, 196, 0.4);
+ color: #4ecdc4;
+ background: rgba(78, 205, 196, 0.08);
+}
+
+.eoc-rl-btn.rl-up:active {
+ background: rgba(78, 205, 196, 0.2);
+ box-shadow: 0 0 20px rgba(78, 205, 196, 0.3);
+}
+
+.eoc-rl-btn.rl-down {
+ border-color: rgba(78, 205, 196, 0.3);
+ color: #e07040;
+ background: rgba(224, 112, 64, 0.08);
+}
+
+.eoc-rl-btn.rl-down:active {
+ background: rgba(224, 112, 64, 0.2);
+ box-shadow: 0 0 20px rgba(224, 112, 64, 0.3);
+}
+
/* ---- Status line (floating, minimal) ---- */
.status-line {
position: fixed;
@@ -1956,6 +2017,11 @@ html, body {
font-size: 32px;
}
+ .eoc-rl-btn {
+ width: 54px;
+ height: 54px;
+ }
+
.drawer-stack {
width: 280px;
}
@@ -1985,6 +2051,15 @@ html, body {
.rl-icon {
font-size: 24px;
}
+
+ .eoc-rl-btn {
+ width: 42px;
+ height: 42px;
+ }
+
+ .eoc-rl-buttons {
+ left: calc(50% + 80px);
+ }
}
/* ---- MIDI CC mode ---- */
diff --git a/playground/js/a-app.js b/playground/js/a-app.js
index 5453317..9230478 100644
--- a/playground/js/a-app.js
+++ b/playground/js/a-app.js
@@ -1112,6 +1112,13 @@ async function init() {
await initEocIML();
}
}
+
+ // Show/hide EOC RL buttons and Synth label based on linked mode
+ const isLinked = eocChain.nispsMode === 'linked';
+ const eocRlContainer = document.getElementById('eoc-rl-buttons');
+ if (eocRlContainer) eocRlContainer.classList.toggle('hidden', !isLinked);
+ const rlLabel = document.getElementById('rl-label');
+ if (rlLabel) rlLabel.classList.toggle('hidden', !isLinked);
});
// Debug probe — exposed on window when ?debug=1 is in the URL.
@@ -2003,6 +2010,28 @@ function wireControls() {
$btnThumbsUp.addEventListener('click', onThumbsUp);
$btnThumbsDown.addEventListener('click', onThumbsDown);
+ // EOC RL buttons (Linked mode — target EOC IML directly)
+ const eocRlPlus = document.getElementById('eoc-rl-plus');
+ const eocRlMinus = document.getElementById('eoc-rl-minus');
+ if (eocRlPlus && eocRlMinus) {
+ eocRlPlus.addEventListener('click', () => {
+ if (!imlEoc) return;
+ const prevTarget = eocTrainingTarget;
+ eocTrainingTarget = 'eoc';
+ onThumbsUp();
+ eocTrainingTarget = prevTarget;
+ flash('eoc-rl-plus');
+ });
+ eocRlMinus.addEventListener('click', () => {
+ if (!imlEoc) return;
+ const prevTarget = eocTrainingTarget;
+ eocTrainingTarget = 'eoc';
+ onThumbsDown();
+ eocTrainingTarget = prevTarget;
+ flash('eoc-rl-minus');
+ });
+ }
+
// Undo button
document.getElementById('btn-undo').addEventListener('click', onUndo);
@@ -3056,6 +3085,24 @@ function wireKeyboard() {
} else if (e.key === '2' || e.code === 'Numpad2') {
e.preventDefault();
onThumbsUp();
+ } else if (e.key === '3' || e.code === 'Numpad3') {
+ e.preventDefault();
+ if (imlEoc && eocChain?.nispsMode === 'linked') {
+ const prevTarget = eocTrainingTarget;
+ eocTrainingTarget = 'eoc';
+ onThumbsDown();
+ eocTrainingTarget = prevTarget;
+ flash('eoc-rl-minus');
+ }
+ } else if (e.key === '4' || e.code === 'Numpad4') {
+ e.preventDefault();
+ if (imlEoc && eocChain?.nispsMode === 'linked') {
+ const prevTarget = eocTrainingTarget;
+ eocTrainingTarget = 'eoc';
+ onThumbsUp();
+ eocTrainingTarget = prevTarget;
+ flash('eoc-rl-plus');
+ }
} else if (e.key === 'z' || e.key === 'Z') {
e.preventDefault();
onUndo();