openvibers/openvibe.bot Commits

The headlights control, designed (#408)

OP OpenVibers committed 1 days ago · f8fa0ef50e · 2 files, +41 −0

One row in Controls v2 family: the sun in its own circle (amber and glowing
when on), the name, and the state as a pill at the end (On amber, Off plain,
Unknown dashed, waiting for the robot a gently breathing pill). Unknown offers
Turn on and Turn off under it; Not confirmed on the robot is a quiet line; a
refusal reads in the warning colour. panel.js marks the state on the control
(data-state) for it. The full panel only, scoped as rule 131 asks.

Changes

ddiff --git a/public/panel.css b/public/panel.css
iindex e756220..d6b469d 100644
---- a/public/panel.css
++++ b/public/panel.css
@@@ -797,3 +797,42 @@ body.bot-mode-compact .bot-compact .bot-notice { color: var(--warn); background:
and position was perfect"): Controls v2 is the full panel's (#panel:not(.bot-surface) above); their stick draws its
dots, not the v2 direction marks. */
.bot-surface .stick-dirs { display: none; }
++
++/* ── LOOI headlights (the full panel only, Controls v2's family) ── One row: the sun in its own circle (amber and glowing
++ when on), the name, and the state as a pill at the end; waiting for the robot, the pill breathes. Unknown offers two
++ choices under it. "Not confirmed on the robot" is a quiet line, because the light's state is what was commanded. */
++#panel:not(.bot-surface) .headlight { display: grid; gap: 0.45rem; margin-top: 0.75rem; }
++#panel:not(.bot-surface) .headlight-toggle {
++ display: flex; align-items: center; gap: 0.7rem; width: 100%; min-height: 52px; padding: 0.45rem 0.6rem 0.45rem 0.45rem;
++ border-radius: 14px; border: 1px solid var(--line); background: var(--raised); color: var(--text); font: inherit; font-weight: 650;
++ cursor: pointer; transition: border-color 0.18s ease, background-color 0.18s ease, transform 0.12s ease;
++}
++#panel:not(.bot-surface) .headlight-toggle:hover:not(:disabled) { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
++#panel:not(.bot-surface) .headlight-toggle:active:not(:disabled) { transform: scale(0.98); }
++#panel:not(.bot-surface) .headlight-toggle:disabled { cursor: progress; opacity: 1; filter: none; }
++#panel:not(.bot-surface) .headlight-toggle .icon {
++ width: 36px; height: 36px; padding: 8px; flex: none; border-radius: 50%; box-sizing: border-box;
++ color: var(--muted); background: color-mix(in srgb, var(--text) 7%, transparent); transition: color 0.2s ease, background-color 0.2s ease, box-shadow 0.25s ease;
++}
++#panel:not(.bot-surface) .headlight-toggle .control-name { flex: 1 1 auto; text-align: left; color: var(--text); font-size: 0.95rem; letter-spacing: normal; text-transform: none; }
++#panel:not(.bot-surface) .headlight-state {
++ flex: none; min-width: 4.5rem; padding: 0.3rem 0.7rem; border-radius: 999px; text-align: center; font-size: 0.8rem; font-weight: 700;
++ color: var(--muted); border: 1px solid var(--line); background: transparent;
++}
++#panel:not(.bot-surface) .headlight[data-state="on"] .headlight-toggle { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); background: color-mix(in srgb, var(--warn) 9%, var(--raised)); }
++#panel:not(.bot-surface) .headlight[data-state="on"] .headlight-toggle .icon { color: #1a1300; background: var(--warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 22%, transparent), 0 0 18px color-mix(in srgb, var(--warn) 55%, transparent); }
++#panel:not(.bot-surface) .headlight[data-state="on"] .headlight-state { color: #1a1300; background: var(--warn); border-color: var(--warn); }
++#panel:not(.bot-surface) .headlight[data-state="off"] .headlight-state { color: var(--text); }
++#panel:not(.bot-surface) .headlight[data-state="unknown"] .headlight-state { border-style: dashed; }
++#panel:not(.bot-surface) .headlight[data-state="pending"] .headlight-state { color: var(--text); border-color: color-mix(in srgb, var(--warn) 60%, var(--line)); animation: headlight-wait 1.2s ease-in-out infinite; }
++@keyframes headlight-wait { 50% { background: color-mix(in srgb, var(--warn) 18%, transparent); } }
++html[data-ov-motion="off"] #panel:not(.bot-surface) .headlight[data-state="pending"] .headlight-state { animation: none; }
++@media (prefers-reduced-motion: reduce) { #panel:not(.bot-surface) .headlight[data-state="pending"] .headlight-state { animation-duration: 2.4s; } }
++#panel:not(.bot-surface) .headlight-choices:not([hidden]) { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
++#panel:not(.bot-surface) .headlight-choices button {
++ min-height: 44px; border-radius: 12px; border: 1px solid var(--line); background: transparent; color: var(--text); font: inherit; font-weight: 650; cursor: pointer;
++}
++#panel:not(.bot-surface) .headlight-choices [data-headlight-on] { background: var(--warn); border-color: var(--warn); color: #1a1300; }
++#panel:not(.bot-surface) .headlight-unconfirmed { font-size: 0.78rem; color: var(--muted); padding-left: 0.2rem; }
++#panel:not(.bot-surface) .headlight-unconfirmed[hidden] { display: none; }
++#panel:not(.bot-surface) .headlight-note { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--warn); }
ddiff --git a/public/panel.js b/public/panel.js
iindex adf5153..f3fec62 100644
---- a/public/panel.js
++++ b/public/panel.js
@@@ -959,6 +959,8 @@
? (on === true ? 'Turning on…' : on === false ? 'Turning off…' : 'Unknown')
: (hl.commanded === true ? 'On' : hl.commanded === false ? 'Off' : 'Unknown');
hlStateEl.textContent = state;
++ // The look follows the state (panel.css): on, off, unknown, or waiting for the robot.
++ headlightEl.dataset.state = hl.pending ? 'pending' : hl.commanded === true ? 'on' : hl.commanded === false ? 'off' : 'unknown';
if (hlUnconfirmed) hlUnconfirmed.hidden = hl.verified;
const unknown = !hl.pending && hl.commanded === null;
if (hlChoices) hlChoices.hidden = !(unknown && hl.choices);