Watch
1
0
Fork
You've already forked souveraine
0

hands: arm the wire before opening the pad

This commit is contained in:
Fimeg 2026-08-17 11:08:36 -04:00
commit 59b52a818b
7 changed files with 210 additions and 148 deletions

View file

@ -23,27 +23,31 @@
* she is still on screen while you work. Casey, 2026-08-15: *"I want her, and
* then below her to have like a trackpad zone / keyboard zone stuff."*
*
* At 760/226 the split was the other way round and the controls had 226px to
* share between a status row, an agent field, the pads and four buttons — so
* the pads were 92px and the last row sat under the dock.
*
* `--dock-reserve` is a number here because the dock claims **no exclusive
* zone** unless it is Pinned (`modules/ii/dock/Dock.qml:321`), so nothing
* shrinks this surface for it and the bottom row would be untappable. Delete
* this the day the dock yields on its own.
* At 760/226 the controls had only 226px. At 320/the-rest the trackpad ate
* nearly half the glass. This split keeps her proportions and gives the pad
* one deliberate working area. The dock now yields only while this
* conditional surface is open; the remaining reserve belongs to the system
* gesture rail, not the dock.
*/
:root { --dock-reserve: 84px; }
body.hid #live2d-widget { height: 320px; }
:root { --gesture-rail-reserve: 32px; }
body.hid #live2d-widget { height: 520px; }
/*
* Keep her proportions when the box shrinks. The canvas fills its element and
* the rig has no letterboxing of its own, so a short full-width box draws the
* same model squashed. The ratio is the full-height box's, 540:760.
*/
body.hid #live2d {
display: block; height: 100%; width: auto;
aspect-ratio: 540 / 760; margin: 0 auto;
}
body.hid #hands {
top: 326px;
top: 526px;
height: auto;
bottom: var(--dock-reserve);
bottom: var(--gesture-rail-reserve);
display: flex;
flex-direction: column;
}
/* The pads take whatever the fixed rows leave, rather than a written-down 92. */
body.hid #track-row,
body.hid #hands.keyboard #keyboard-pane { flex: 1; height: auto; min-height: 92px; }
body.hid #track-row { flex: 1; height: auto; min-height: 180px; }
#live2d { width: 100%; height: 100%; }
#bubble {
position: fixed; left: 8px; right: 8px; top: 12px;
@ -56,8 +60,7 @@
#bubble.up { opacity: 1; }
/*
* The room viewtop left under her, given a purpose. Trackpad first; the
* literal keyboard only replaces it when summoned. The dark sheer gradient
* The room viewtop left under her, given a purpose. The dark sheer gradient
* keeps the controls legible without putting an app-shaped slab around her.
*/
#hands {
@ -100,20 +103,18 @@
#hands-listening { display: none; color: #e8b7d8; }
#hands.listening #hands-listening { display: inline; }
#hands-status { flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: rgba(255,255,255,.68); }
.thread-button { width: 30px; min-width: 30px !important; height: 26px !important; padding: 0 !important; border-radius: 10px !important; }
#agent-form, #host-form { height: 40px; display: flex; gap: 6px; }
#agent-input, #host-input {
#agent-form { height: 40px; display: flex; gap: 6px; }
#agent-input {
min-width: 0; flex: 1; height: 40px; padding: 0 13px;
border: 1px solid rgba(255,255,255,.12); border-radius: 14px;
outline: none; color: #fff; background: rgba(0,0,0,.20);
-webkit-user-select: text; user-select: text;
}
#agent-input:focus, #host-input:focus { border-color: rgba(217,180,235,.55); }
#agent-input::placeholder, #host-input::placeholder { color: rgba(255,255,255,.43); }
#agent-input:focus { border-color: rgba(217,180,235,.55); }
#agent-input::placeholder { color: rgba(255,255,255,.43); }
#mic.listening { color: #f0b6db; box-shadow: inset 0 0 0 1px rgba(240,182,219,.45); }
#track-row, #keyboard-pane { height: 92px; margin-top: 6px; }
#track-row { height: 92px; margin-top: 6px; }
#track-row { display: flex; gap: 6px; }
#trackpad, #scrollpad {
position: relative; overflow: hidden;
@ -133,12 +134,6 @@
content: '↕'; position: absolute; inset: 0; display: grid; place-items: center;
color: rgba(255,255,255,.35); font-size: 20px;
}
#keyboard-pane { display: none; }
#keyboard-pane .quick-keys { display: flex; gap: 5px; margin-top: 6px; }
#keyboard-pane .quick-keys button { flex: 1; height: 40px; padding: 0 5px; }
#hands.keyboard #track-row { display: none; }
#hands.keyboard #keyboard-pane { display: block; }
.hands-actions { height: 40px; display: flex; gap: 5px; margin-top: 6px; }
.hands-actions button { flex: 1; height: 40px !important; padding: 0 5px !important; }
#cad.holding {
@ -155,8 +150,6 @@
<span id="hands-dot"></span>
<span id="hands-status">USB hand asleep</span>
<span id="hands-listening">listening</span>
<button class="thread-button" id="resume-thread" title="Resume latest conversation"></button>
<button class="thread-button" id="new-thread" title="Begin a new conversation"></button>
</div>
<form id="agent-form">
<button type="button" id="mic" aria-label="Start or stop listening"></button>
@ -168,24 +161,9 @@
<div id="trackpad" aria-label="Trackpad"></div>
<div id="scrollpad" aria-label="Scroll"></div>
</div>
<div id="keyboard-pane">
<form id="host-form">
<input id="host-input" autocomplete="off" enterkeyhint="send"
placeholder="Type into the attached host…">
<button type="submit" aria-label="Type on host"></button>
</form>
<div class="quick-keys">
<button data-key="escape">Esc</button>
<button data-key="tab">Tab</button>
<button data-key="backspace"></button>
<button data-key="enter">Enter</button>
</div>
</div>
<div class="hands-actions">
<button id="left-click">Left</button>
<button id="right-click">Right</button>
<button id="keyboard-toggle"></button>
<button id="arm-hid">Arm</button>
<button id="cad" title="Hold for Ctrl+Alt+Delete">CAD</button>
</div>
</section>
@ -363,7 +341,7 @@
window.souveraine.hid(message);
}
var agentPending = false, hostPending = false;
var agentPending = false;
window.hands = {
state: function (state) {
state = state || {};
@ -386,8 +364,6 @@
: 'Tell ' + (state.agent || 'your agent') + ' what the other screen needs…';
hands.classList.toggle('thinking', !!state.thinking);
document.getElementById('agent-input').disabled = !!state.thinking || agentPending;
document.getElementById('resume-thread').classList.toggle('on', state.conversation === 'resumed');
document.getElementById('new-thread').classList.toggle('on', state.conversation === 'new');
requestAnimationFrame(publishShape);
},
agentResult: function (result) {
@ -397,14 +373,6 @@
input.disabled = hands.classList.contains('thinking');
if (result.accepted) input.value = '';
else if (result.reason) document.getElementById('hands-status').textContent = result.reason;
},
hostResult: function (result) {
result = result || {};
hostPending = false;
var input = document.getElementById('host-input');
input.disabled = false;
if (result.accepted) input.value = '';
else if (result.reason) document.getElementById('hands-status').textContent = result.reason;
}
};
@ -422,13 +390,6 @@
if (!window.souveraine) return;
window.souveraine.talk(hands.classList.contains('listening') ? 'end' : 'start');
});
document.getElementById('resume-thread').addEventListener('click', function () {
if (window.souveraine) window.souveraine.thread('resume');
});
document.getElementById('new-thread').addEventListener('click', function () {
if (window.souveraine) window.souveraine.thread('new');
});
var trackpad = document.getElementById('trackpad');
var trackDown = false, trackX = 0, trackY = 0, trackTravel = 0, trackAt = 0;
trackpad.addEventListener('pointerdown', function (event) {
@ -478,28 +439,6 @@
document.getElementById('right-click').addEventListener('click', function () {
hid('click', { button: 'right' });
});
document.getElementById('arm-hid').addEventListener('click', function () { hid('arm'); });
var keyboardToggle = document.getElementById('keyboard-toggle');
keyboardToggle.addEventListener('click', function () {
var showing = !hands.classList.contains('keyboard');
hands.classList.toggle('keyboard', showing);
keyboardToggle.classList.toggle('on', showing);
var input = document.getElementById('host-input');
if (showing) input.focus(); else input.blur();
});
document.getElementById('host-form').addEventListener('submit', function (event) {
event.preventDefault();
var input = document.getElementById('host-input');
if (input.value.length === 0 || hostPending) return;
hostPending = true;
input.disabled = true;
hid('type', { text: input.value });
});
Array.prototype.forEach.call(document.querySelectorAll('[data-key]'), function (button) {
button.addEventListener('click', function () { hid('key', { key: button.dataset.key }); });
});
// Ctrl+Alt+Delete is the one quick key a stray tap must not send. Its hold
// is local feedback; only the completed edge crosses to the HID owner.
var cad = document.getElementById('cad'), cadTimer = null, cadPointer = null;