Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions ui-concepts/console-euclid-live/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1339,11 +1339,141 @@ function renderAll() {
engine.on("change", renderAll);
engine.on("connection", renderLinkStatus);

/* ---------------------------------------------------------------------
Modules — collapsible + drag-reorderable main-view panels.
Every main-view block carries data-module + a .module-head (which is
both the drag handle and the collapse toggle host). Order and collapse
state persist in localStorage so a user's layout survives reloads.
Pure DOM sugar — no device wiring here; all element IDs are untouched
so the engine bindings keep working regardless of order.
-------------------------------------------------------------------- */

function initModules() {
const view = $("#viewMain");
if (!view) return;

const KEY_ORDER = "lume.console.moduleOrder";
const KEY_COLLAPSED = "lume.console.moduleCollapsed";
const store = {
get(k) { try { return JSON.parse(localStorage.getItem(k)); } catch (_) { return null; } },
set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (_) {} },
};

const modules = () => $$("#viewMain > [data-module]");

// Restore saved order. Saved modules are re-appended in order; any module
// not in the saved list (e.g. one added in a future version) is left where
// it is and so surfaces ahead of the restored ones until reordered.
const savedOrder = store.get(KEY_ORDER);
if (Array.isArray(savedOrder)) {
const byId = new Map(modules().map((m) => [m.dataset.module, m]));
savedOrder.forEach((id) => { const el = byId.get(id); if (el) view.appendChild(el); });
}
const saveOrder = () => store.set(KEY_ORDER, modules().map((m) => m.dataset.module));

// Restore + wire collapse state.
const savedCollapsed = store.get(KEY_COLLAPSED);
const collapsed = new Set(Array.isArray(savedCollapsed) ? savedCollapsed : []);
const saveCollapsed = () => store.set(KEY_COLLAPSED, Array.from(collapsed));

let dragEl = null;

modules().forEach((mod) => {
const id = mod.dataset.module;
const head = mod.querySelector(".module-head");
const btn = mod.querySelector(".module-collapse");

if (collapsed.has(id)) {
mod.classList.add("is-collapsed");
if (btn) btn.setAttribute("aria-expanded", "false");
}

if (btn) btn.addEventListener("click", (e) => {
e.stopPropagation();
const nowCollapsed = mod.classList.toggle("is-collapsed");
btn.setAttribute("aria-expanded", nowCollapsed ? "false" : "true");
if (nowCollapsed) collapsed.add(id); else collapsed.delete(id);
saveCollapsed();
});

if (head) {
head.setAttribute("draggable", "true");
head.addEventListener("dragstart", (e) => {
dragEl = mod;
mod.classList.add("is-dragging");
view.classList.add("modules-dragging");
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = "move";
try { e.dataTransfer.setData("text/plain", id); } catch (_) {}
}
});
head.addEventListener("dragend", () => {
mod.classList.remove("is-dragging");
view.classList.remove("modules-dragging");
dragEl = null;
saveOrder();
});
}
});

view.addEventListener("dragover", (e) => {
if (!dragEl) return;
e.preventDefault();
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
const target = e.target.closest("[data-module]");
if (!target || target === dragEl || target.parentElement !== view) return;
Comment on lines +1421 to +1424
const rect = target.getBoundingClientRect();
const before = (e.clientY - rect.top) < rect.height / 2;
view.insertBefore(dragEl, before ? target : target.nextElementSibling);
});
view.addEventListener("drop", (e) => { if (dragEl) e.preventDefault(); });
}

/* ---------------------------------------------------------------------
Theme — Auto / Light / Dark. The <head> inline script sets the initial
resolved data-theme before first paint (no flash); this wires the
Settings control, persists the preference, and re-resolves "auto" live
when the system scheme changes.
-------------------------------------------------------------------- */

function initTheme() {
const KEY = "lume.console.theme";
const root = document.documentElement;
const mq = window.matchMedia("(prefers-color-scheme: light)");
const store = {
get() { try { return localStorage.getItem(KEY); } catch (_) { return null; } },
set(v) { try { localStorage.setItem(KEY, v); } catch (_) {} },
};

let pref = store.get() || "auto"; // "auto" | "light" | "dark"
const btns = $$("#themeSeg .theme-seg-btn");

const resolve = (p) => (p === "auto" ? (mq.matches ? "light" : "dark") : p);
const apply = () => {
root.setAttribute("data-theme", resolve(pref));
btns.forEach((b) => b.classList.toggle("active", b.dataset.themeChoice === pref));
};

btns.forEach((b) => b.addEventListener("click", () => {
pref = b.dataset.themeChoice;
store.set(pref);
apply();
}));

const onSystemChange = () => { if (pref === "auto") apply(); };
if (mq.addEventListener) mq.addEventListener("change", onSystemChange);
else if (mq.addListener) mq.addListener(onSystemChange); // older Safari

apply();
}

/* ---------------------------------------------------------------------
Bootstrap
-------------------------------------------------------------------- */

requestAnimationFrame(() => {
initTheme();
initModules();
engine.start().then(() => {
if (engine.state.demo) {
showToast("Demo mode — no device found, using sample data");
Expand Down
153 changes: 115 additions & 38 deletions ui-concepts/console-euclid-live/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@
<meta name="description" content="A studio lighting-console control surface for LUME, in the Byrne (Euclid) material language." />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&family=Space+Grotesk:wght@500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css" />
<script>
/* Resolve theme before first paint (Auto → system) to avoid a flash. */
(function () {
try {
var p = localStorage.getItem("lume.console.theme") || "auto";
var d = p === "auto"
? (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark")
: p;
Comment on lines +16 to +19
document.documentElement.setAttribute("data-theme", d);
} catch (e) {}
})();
</script>
</head>
<body>

Expand Down Expand Up @@ -47,8 +59,16 @@
<section id="viewMain" class="view active">

<!-- Master rail -->
<div class="panel">
<div class="panel-label"><span>Master</span><span class="screw"></span></div>
<div class="panel module" data-module="master">
<div class="panel-label module-head">
<span>Master</span>
<span class="module-tools">
<button class="module-collapse" type="button" aria-label="Collapse module" aria-expanded="true">
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<span class="screw"></span>
</span>
</div>
<div class="master-grid">

<div class="power-module">
Expand Down Expand Up @@ -102,39 +122,31 @@
</div>
</div>

<!-- AI Prompt -->
<div class="panel ai-panel">
<div class="ai-row">
<div class="ai-icon" id="aiIcon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.2"/></svg>
</div>
<div class="ai-input-wrap">
<input type="text" class="ai-input" id="aiInput" placeholder="Describe a mood — “cozy warm fireplace”, “calm ocean at dusk”…" />
</div>
<button class="ai-go" id="aiGo" type="button" aria-label="Generate">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
</button>
</div>
<div class="ai-chips">
<span class="ai-chip" data-prompt="cozy warm fireplace">cozy fireplace</span>
<span class="ai-chip" data-prompt="calm ocean at dusk">calm ocean</span>
<span class="ai-chip" data-prompt="soft sunset glow">sunset glow</span>
<span class="ai-chip" data-prompt="quiet forest morning">forest morning</span>
</div>
<div class="ai-status" id="aiStatus"></div>
</div>

<!-- Segment rack -->
<div class="rack-head">
<span class="rack-title">Channels</span>
<span class="rack-title" id="ledTotalLabel">300 LEDs total</span>
<div class="panel module" data-module="channels">
<div class="rack-head module-head">
<span class="rack-title">Channels</span>
<span class="module-tools">
<span class="rack-title" id="ledTotalLabel">300 LEDs total</span>
<button class="module-collapse" type="button" aria-label="Collapse module" aria-expanded="true">
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
</span>
</div>
<div class="strip-select-row" id="segTabs"></div>
</div>

<div class="strip-select-row" id="segTabs"></div>

<!-- Visualizer -->
<div class="panel viz-panel" id="vizPanel">
<div class="panel-label"><span id="vizLabel">CH 1 — Fire</span><span class="screw"></span></div>
<div class="panel viz-panel module" data-module="viz" id="vizPanel">
<div class="panel-label module-head">
<span id="vizLabel">CH 1 — Fire</span>
<span class="module-tools">
<button class="module-collapse" type="button" aria-label="Collapse module" aria-expanded="true">
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<span class="screw"></span>
</span>
</div>
<div class="viz-stage" id="vizStage">
<div class="viz-glow" id="vizGlow"></div>
<div class="viz-led-row" id="vizLedRow"></div>
Expand All @@ -148,7 +160,17 @@
</div>

<!-- Channel strip controls -->
<div class="panel strip-panel" id="stripPanel">
<div class="panel strip-panel module" data-module="strip" id="stripPanel">

<div class="panel-label module-head">
<span>Controls</span>
<span class="module-tools">
<button class="module-collapse" type="button" aria-label="Collapse module" aria-expanded="true">
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<span class="screw"></span>
</span>
</div>

<div class="field-block">
<span class="field-label">Segment Range</span>
Expand Down Expand Up @@ -183,6 +205,37 @@

</div>

<!-- AI Prompt (moved to bottom — scroll to reach) -->
<div class="panel ai-panel module" data-module="ai">
<div class="panel-label module-head">
<span>AI Prompt</span>
<span class="module-tools">
<button class="module-collapse" type="button" aria-label="Collapse module" aria-expanded="true">
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<span class="screw"></span>
</span>
</div>
<div class="ai-row">
<div class="ai-icon" id="aiIcon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.2"/></svg>
</div>
<div class="ai-input-wrap">
<input type="text" class="ai-input" id="aiInput" placeholder="Describe a mood — “cozy warm fireplace”, “calm ocean at dusk”…" />
</div>
<button class="ai-go" id="aiGo" type="button" aria-label="Generate">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
</button>
</div>
<div class="ai-chips">
<span class="ai-chip" data-prompt="cozy warm fireplace">cozy fireplace</span>
<span class="ai-chip" data-prompt="calm ocean at dusk">calm ocean</span>
<span class="ai-chip" data-prompt="soft sunset glow">sunset glow</span>
<span class="ai-chip" data-prompt="quiet forest morning">forest morning</span>
</div>
<div class="ai-status" id="aiStatus"></div>
</div>

</section>

<!-- ==================== SETTINGS VIEW ==================== -->
Expand Down Expand Up @@ -256,12 +309,36 @@
</div>
</div>

<!-- Appearance -->
<div class="rack-unit">
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
<span class="rack-unit-title">03 · Appearance</span>
</div>
<span class="rack-led on"></span>
</div>
<div class="rack-unit-body">
<div class="toggle-field">
<div class="toggle-field-text">
<span class="toggle-field-title">Theme</span>
<span class="toggle-field-sub">Auto follows your system · Light uses the warm-paper ground</span>
</div>
<div class="theme-seg" id="themeSeg" role="group" aria-label="Theme">
<button type="button" class="theme-seg-btn" data-theme-choice="auto">Auto</button>
<button type="button" class="theme-seg-btn" data-theme-choice="light">Light</button>
<button type="button" class="theme-seg-btn" data-theme-choice="dark">Dark</button>
</div>
</div>
</div>
</div>

<!-- AI -->
<div class="rack-unit">
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.2"/></svg>
<span class="rack-unit-title">03 · AI Prompt Engine</span>
<span class="rack-unit-title">04 · AI Prompt Engine</span>
</div>
<span class="rack-led on"></span>
</div>
Expand All @@ -284,7 +361,7 @@
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="6" r="2.4"/><circle cx="19" cy="6" r="2.4"/><circle cx="12" cy="18" r="2.4"/><path d="M6.8 7.7 10.5 16M17.2 7.7 13.5 16M7.4 6h9.2"/></svg>
<span class="rack-unit-title">04 · MQTT Broker</span>
<span class="rack-unit-title">05 · MQTT Broker</span>
</div>
<span class="rack-led" id="mqttLed"></span>
</div>
Expand Down Expand Up @@ -320,7 +397,7 @@
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="1.5"/><path d="M7 7V5M12 7V5M17 7V5M7 19v-2M12 19v-2M17 19v-2"/></svg>
<span class="rack-unit-title">05 · sACN / DMX</span>
<span class="rack-unit-title">06 · sACN / DMX</span>
</div>
<span class="rack-led" id="sacnLed"></span>
</div>
Expand Down Expand Up @@ -348,7 +425,7 @@
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h13M21 18h-1"/><circle cx="16" cy="6" r="2"/><circle cx="8" cy="12" r="2"/><circle cx="19" cy="18" r="2"/></svg>
<span class="rack-unit-title">06 · Feel / Tuning</span>
<span class="rack-unit-title">07 · Feel / Tuning</span>
</div>
<span class="rack-led on"></span>
</div>
Expand Down Expand Up @@ -403,7 +480,7 @@
<div class="rack-unit-head">
<div class="title-group">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg>
<span class="rack-unit-title">07 · Firmware Update</span>
<span class="rack-unit-title">08 · Firmware Update</span>
</div>
<span class="rack-led on"></span>
</div>
Expand Down
Loading
Loading