Skip to content

Commit 4559c1c

Browse files
jcgglclaude
andcommitted
feat: add V2 emotion control panel to guide page
- 5 emotion sliders (neutral, joy, anger, sadness, surprise) with 0-100% - Preset buttons for one-click emotion selection - Real-time setEmotion() call on slider input - Panel shown only when V2 engine is selected - Resets to [0,0,0,0,0] on engine switch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5a883ce commit 4559c1c

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

examples/guide/index.html

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,35 @@
352352
}
353353
.btn-danger:hover { background: rgba(239,68,68,0.2); }
354354

355+
/* ── Emotion Control ── */
356+
.emotion-panel { display: flex; flex-direction: column; gap: 6px; }
357+
.emotion-row {
358+
display: flex; align-items: center; gap: 8px; font-size: 0.78rem;
359+
}
360+
.emotion-label { width: 80px; color: #94a3b8; font-size: 0.72rem; }
361+
.emotion-slider {
362+
flex: 1; height: 4px; -webkit-appearance: none; appearance: none;
363+
background: rgba(255,255,255,0.08); border-radius: 2px; outline: none;
364+
cursor: pointer;
365+
}
366+
.emotion-slider::-webkit-slider-thumb {
367+
-webkit-appearance: none; width: 14px; height: 14px;
368+
border-radius: 50%; background: #4cc9f0; cursor: pointer;
369+
}
370+
.emotion-slider::-moz-range-thumb {
371+
width: 14px; height: 14px; border: none;
372+
border-radius: 50%; background: #4cc9f0; cursor: pointer;
373+
}
374+
.emotion-val { width: 32px; text-align: right; color: #e2e8f0; font-size: 0.72rem; font-variant-numeric: tabular-nums; }
375+
.emotion-presets { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
376+
.emotion-presets button {
377+
padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1);
378+
background: rgba(255,255,255,0.04); color: #94a3b8;
379+
font-size: 0.7rem; cursor: pointer; transition: all 0.15s;
380+
}
381+
.emotion-presets button:hover { background: rgba(76,201,240,0.12); color: #4cc9f0; border-color: rgba(76,201,240,0.3); }
382+
.emotion-presets button.active { background: rgba(76,201,240,0.15); color: #4cc9f0; border-color: #4cc9f0; }
383+
355384
/* ── Buttons ── */
356385
.btn {
357386
display: inline-flex; align-items: center; gap: 8px;
@@ -816,6 +845,30 @@ <h2 class="step-title">Add Real-time Microphone</h2>
816845

817846
<div class="demo-divider"></div>
818847

848+
<!-- Emotion Control (V2 only) -->
849+
<div class="demo-step-group" id="emotion-group" style="display:none">
850+
<div class="demo-step-label">
851+
<span style="font-size:0.78rem;color:#4cc9f0">Emotion Control</span>
852+
<span style="font-size:0.65rem;color:#64748b;margin-left:6px">V2 only</span>
853+
</div>
854+
<div class="emotion-panel" id="emotion-panel">
855+
<div class="emotion-row"><span class="emotion-label">Neutral</span><input type="range" class="emotion-slider" id="emo-neutral" min="0" max="100" value="0"><span class="emotion-val" id="emo-neutral-val">0%</span></div>
856+
<div class="emotion-row"><span class="emotion-label">Joy</span><input type="range" class="emotion-slider" id="emo-joy" min="0" max="100" value="0"><span class="emotion-val" id="emo-joy-val">0%</span></div>
857+
<div class="emotion-row"><span class="emotion-label">Anger</span><input type="range" class="emotion-slider" id="emo-anger" min="0" max="100" value="0"><span class="emotion-val" id="emo-anger-val">0%</span></div>
858+
<div class="emotion-row"><span class="emotion-label">Sadness</span><input type="range" class="emotion-slider" id="emo-sadness" min="0" max="100" value="0"><span class="emotion-val" id="emo-sadness-val">0%</span></div>
859+
<div class="emotion-row"><span class="emotion-label">Surprise</span><input type="range" class="emotion-slider" id="emo-surprise" min="0" max="100" value="0"><span class="emotion-val" id="emo-surprise-val">0%</span></div>
860+
</div>
861+
<div class="emotion-presets">
862+
<button data-preset="neutral">Neutral</button>
863+
<button data-preset="joy">Happy</button>
864+
<button data-preset="anger">Angry</button>
865+
<button data-preset="sadness">Sad</button>
866+
<button data-preset="surprise">Surprised</button>
867+
</div>
868+
</div>
869+
870+
<div class="demo-divider" id="emotion-divider" style="display:none"></div>
871+
819872
<!-- Step 6: Mic -->
820873
<div class="demo-step-group" id="demo-group-6">
821874
<div class="demo-step-label">
@@ -1097,6 +1150,11 @@ <h2 class="step-title">Add Real-time Microphone</h2>
10971150
completeStep(3);
10981151
setStatus(`${label} engine ready. Upload a VRM to continue.`);
10991152

1153+
// Show/hide emotion panel based on engine
1154+
showEmotionPanel(selectedEngine === 'v2');
1155+
resetEmotionUI();
1156+
if (selectedEngine === 'v2') updateEmotionVector();
1157+
11001158
// Reload VRM to rebind VRMA bone animations from new engine
11011159
if (vrm) {
11021160
const vrmScene = vrm.scene;
@@ -1140,6 +1198,56 @@ <h2 class="step-title">Add Real-time Microphone</h2>
11401198
}
11411199
});
11421200

1201+
// ════════════════════════════════════════
1202+
// Emotion Control (V2 only)
1203+
// ════════════════════════════════════════
1204+
const emotionGroup = $('emotion-group');
1205+
const emotionDivider = $('emotion-divider');
1206+
const EMOTION_KEYS = ['neutral', 'joy', 'anger', 'sadness', 'surprise'];
1207+
const emotionSliders = EMOTION_KEYS.map(k => $(`emo-${k}`));
1208+
const emotionVals = EMOTION_KEYS.map(k => $(`emo-${k}-val`));
1209+
1210+
function updateEmotionVector() {
1211+
const vec = emotionSliders.map(s => parseInt(s.value) / 100);
1212+
if (lipsync?.setEmotion) {
1213+
try { lipsync.setEmotion(vec); } catch (e) { console.warn('setEmotion:', e.message); }
1214+
}
1215+
}
1216+
1217+
function showEmotionPanel(show) {
1218+
emotionGroup.style.display = show ? '' : 'none';
1219+
emotionDivider.style.display = show ? '' : 'none';
1220+
}
1221+
1222+
function resetEmotionUI() {
1223+
emotionSliders.forEach(s => { s.value = 0; });
1224+
emotionVals.forEach(v => { v.textContent = '0%'; });
1225+
document.querySelectorAll('.emotion-presets button').forEach(b => b.classList.remove('active'));
1226+
}
1227+
1228+
// Slider input events
1229+
emotionSliders.forEach((slider, i) => {
1230+
slider.addEventListener('input', () => {
1231+
emotionVals[i].textContent = slider.value + '%';
1232+
document.querySelectorAll('.emotion-presets button').forEach(b => b.classList.remove('active'));
1233+
updateEmotionVector();
1234+
});
1235+
});
1236+
1237+
// Preset buttons
1238+
document.querySelectorAll('.emotion-presets button').forEach(btn => {
1239+
btn.addEventListener('click', () => {
1240+
const preset = btn.dataset.preset;
1241+
document.querySelectorAll('.emotion-presets button').forEach(b => b.classList.remove('active'));
1242+
btn.classList.add('active');
1243+
emotionSliders.forEach((s, i) => {
1244+
s.value = EMOTION_KEYS[i] === preset ? 100 : 0;
1245+
emotionVals[i].textContent = s.value + '%';
1246+
});
1247+
updateEmotionVector();
1248+
});
1249+
});
1250+
11431251
// ════════════════════════════════════════
11441252
// VRM Upload (button + drop + drag)
11451253
// ════════════════════════════════════════

0 commit comments

Comments
 (0)