@hybridlabor-api/aos 4.4.2-beta.7 → 4.4.2-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +0 -104
- package/installer.js +97 -13
- package/package.json +1 -1
- package/skills/global_config/live-preview-canvas/SKILL.md +19 -0
- package/skills/global_config/live-preview-canvas/examples/preview_interactive_dashboard.html +406 -0
- package/skills/global_config/live-preview-canvas/resources/template.html +291 -0
- package/skills/global_config/live-preview-canvas/scripts/base_template.html +178 -0
- package/skills/global_config/openwiki-skill/scripts/install_daemon.ps1 +81 -28
- package/skills/global_config/openwiki-skill/scripts/install_daemon.sh +71 -25
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Live Preview: Multi-Agent Show Control Dashboard</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script>
|
|
9
|
+
tailwind.config = {
|
|
10
|
+
darkMode: 'class',
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {
|
|
13
|
+
colors: {
|
|
14
|
+
brand: {
|
|
15
|
+
400: '#818cf8',
|
|
16
|
+
500: '#6366f1',
|
|
17
|
+
600: '#4f46e5'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
</script>
|
|
24
|
+
<style>
|
|
25
|
+
body {
|
|
26
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
27
|
+
background: #090d16;
|
|
28
|
+
color: #f1f5f9;
|
|
29
|
+
min-height: 100vh;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
}
|
|
33
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
34
|
+
width: 6px;
|
|
35
|
+
height: 6px;
|
|
36
|
+
}
|
|
37
|
+
.custom-scrollbar::-webkit-scrollbar-track {
|
|
38
|
+
background: #0f172a;
|
|
39
|
+
}
|
|
40
|
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
41
|
+
background: #334155;
|
|
42
|
+
border-radius: 3px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body class="bg-slate-950 text-slate-100 antialiased pb-28">
|
|
47
|
+
|
|
48
|
+
<!-- =========================================================================
|
|
49
|
+
Navigation & Review Bar
|
|
50
|
+
========================================================================= -->
|
|
51
|
+
<header id="show-control-header" data-component="header" class="border-b border-slate-800 bg-slate-900/90 backdrop-blur sticky top-0 z-40 px-6 py-3 flex items-center justify-between">
|
|
52
|
+
<div class="flex items-center space-x-3">
|
|
53
|
+
<div class="h-9 w-9 rounded-lg bg-gradient-to-tr from-indigo-600 to-violet-500 flex items-center justify-center font-bold text-white shadow-lg shadow-indigo-500/20">
|
|
54
|
+
⚡
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<div class="flex items-center space-x-2">
|
|
58
|
+
<h1 id="app-title" class="text-sm font-semibold text-white tracking-wide">BDB Agent OS • Live Show Controller</h1>
|
|
59
|
+
<span class="text-[10px] uppercase font-mono px-2 py-0.5 rounded bg-indigo-950 text-indigo-300 border border-indigo-800/60">Live Mock</span>
|
|
60
|
+
</div>
|
|
61
|
+
<p id="app-subtitle" class="text-xs text-slate-400">Interactive Preview for Layout Review & Element Red-Pinning</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Live Status & In-Memory Trigger -->
|
|
66
|
+
<div id="header-actions" class="flex items-center space-x-3">
|
|
67
|
+
<div id="heartbeat-pill" class="flex items-center space-x-1.5 px-3 py-1 rounded-full text-xs font-mono bg-emerald-950 text-emerald-300 border border-emerald-800">
|
|
68
|
+
<span class="h-2 w-2 rounded-full bg-emerald-400 animate-ping"></span>
|
|
69
|
+
<span>ONLINE • 60 FPS</span>
|
|
70
|
+
</div>
|
|
71
|
+
<button id="btn-emergency-blackout" onclick="triggerPanic()" class="text-xs font-semibold bg-rose-600/20 hover:bg-rose-600 text-rose-300 hover:text-white px-3 py-1.5 rounded-lg border border-rose-700/50 transition">
|
|
72
|
+
Blackout
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</header>
|
|
76
|
+
|
|
77
|
+
<!-- =========================================================================
|
|
78
|
+
Main Workspace
|
|
79
|
+
========================================================================= -->
|
|
80
|
+
<main class="flex-1 max-w-6xl w-full mx-auto px-6 py-6 space-y-6">
|
|
81
|
+
|
|
82
|
+
<!-- Reviewer Context Alert -->
|
|
83
|
+
<section id="review-banner" data-component="review-banner" class="p-4 rounded-xl bg-indigo-950/40 border border-indigo-800/50 flex items-center justify-between">
|
|
84
|
+
<div class="flex items-start space-x-3">
|
|
85
|
+
<div class="text-indigo-400 text-lg">💡</div>
|
|
86
|
+
<div>
|
|
87
|
+
<p class="text-xs font-medium text-indigo-200">
|
|
88
|
+
<strong>Designer / Reviewer Notice:</strong> You can click any card, button, or label to pin an annotation. Use the floating bar below to toggle between Layouts and the Panic state.
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<span class="text-xs font-mono text-indigo-300 bg-indigo-900/60 px-2.5 py-1 rounded border border-indigo-700/50">
|
|
93
|
+
Auto-Reloads on Save
|
|
94
|
+
</span>
|
|
95
|
+
</section>
|
|
96
|
+
|
|
97
|
+
<!-- Top KPI Grid -->
|
|
98
|
+
<section id="kpi-grid" data-component="kpi-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
99
|
+
<div id="kpi-touchdesigner" class="p-4 rounded-xl bg-slate-900 border border-slate-800 hover:border-slate-700 transition">
|
|
100
|
+
<div class="flex justify-between items-start">
|
|
101
|
+
<span class="text-xs text-slate-400 uppercase font-medium">TouchDesigner</span>
|
|
102
|
+
<span class="text-[10px] font-mono text-emerald-400 bg-emerald-950 px-1.5 py-0.5 rounded border border-emerald-800">OK</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="mt-2 text-2xl font-bold text-white">4.1ms</div>
|
|
105
|
+
<p class="text-xs text-slate-500 mt-1">GPU Cook Cost (RTX 4090)</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div id="kpi-grandma" class="p-4 rounded-xl bg-slate-900 border border-slate-800 hover:border-slate-700 transition">
|
|
109
|
+
<div class="flex justify-between items-start">
|
|
110
|
+
<span class="text-xs text-slate-400 uppercase font-medium">grandMA3 Session</span>
|
|
111
|
+
<span class="text-[10px] font-mono text-emerald-400 bg-emerald-950 px-1.5 py-0.5 rounded border border-emerald-800">LOCKED</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="mt-2 text-2xl font-bold text-white">Uni 1-8</div>
|
|
114
|
+
<p class="text-xs text-slate-500 mt-1">DMX over sACN / ArtNet</p>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div id="kpi-resolume" class="p-4 rounded-xl bg-slate-900 border border-slate-800 hover:border-slate-700 transition">
|
|
118
|
+
<div class="flex justify-between items-start">
|
|
119
|
+
<span class="text-xs text-slate-400 uppercase font-medium">Resolume Arena</span>
|
|
120
|
+
<span class="text-[10px] font-mono text-amber-400 bg-amber-950 px-1.5 py-0.5 rounded border border-amber-800">STANDBY</span>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="mt-2 text-2xl font-bold text-white">Deck 02</div>
|
|
123
|
+
<p class="text-xs text-slate-500 mt-1">Layer 3: Generative Pass</p>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div id="kpi-agents" class="p-4 rounded-xl bg-slate-900 border border-slate-800 hover:border-slate-700 transition">
|
|
127
|
+
<div class="flex justify-between items-start">
|
|
128
|
+
<span class="text-xs text-slate-400 uppercase font-medium">Pipeline Cues</span>
|
|
129
|
+
<span class="text-[10px] font-mono text-indigo-400 bg-indigo-950 px-1.5 py-0.5 rounded border border-indigo-800">AUTO</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="mt-2 text-2xl font-bold text-white" id="cue-counter">Cue 14/40</div>
|
|
132
|
+
<p class="text-xs text-slate-500 mt-1">Next: Intro Drop Strobes</p>
|
|
133
|
+
</div>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<!-- VARIANT A: Full Orchestration Board (Default) -->
|
|
137
|
+
<div id="view-variant-a" class="preview-variant space-y-6">
|
|
138
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
139
|
+
|
|
140
|
+
<!-- Left 2 Cols: Active Cue Stack & Audio Spectrum Mock -->
|
|
141
|
+
<div class="lg:col-span-2 space-y-6">
|
|
142
|
+
<section id="cue-stack-section" data-component="cue-stack" class="p-5 rounded-xl bg-slate-900 border border-slate-800">
|
|
143
|
+
<div class="flex items-center justify-between mb-4">
|
|
144
|
+
<h2 class="text-sm font-semibold text-white">Live Cue Sequencer</h2>
|
|
145
|
+
<div class="flex space-x-2">
|
|
146
|
+
<button onclick="advanceCue()" class="text-xs bg-indigo-600 hover:bg-indigo-500 text-white font-medium px-3 py-1 rounded-lg transition shadow-sm">
|
|
147
|
+
▶ Go Next Cue
|
|
148
|
+
</button>
|
|
149
|
+
<button onclick="resetCues()" class="text-xs bg-slate-800 hover:bg-slate-700 text-slate-300 px-2.5 py-1 rounded-lg border border-slate-700 transition">
|
|
150
|
+
Restart
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div id="cue-list" class="space-y-2 font-mono text-xs">
|
|
156
|
+
<div id="cue-item-12" class="p-3 rounded-lg bg-slate-950/60 border border-slate-800/80 flex items-center justify-between text-slate-400">
|
|
157
|
+
<div class="flex items-center space-x-3">
|
|
158
|
+
<span class="text-slate-500 font-bold">#12</span>
|
|
159
|
+
<span class="text-slate-300">Ambient Drone - Blue Wash</span>
|
|
160
|
+
</div>
|
|
161
|
+
<span class="text-[10px] text-slate-500">COMPLETE</span>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<div id="cue-item-13" class="p-3 rounded-lg bg-slate-950/60 border border-slate-800/80 flex items-center justify-between text-slate-400">
|
|
165
|
+
<div class="flex items-center space-x-3">
|
|
166
|
+
<span class="text-slate-500 font-bold">#13</span>
|
|
167
|
+
<span class="text-slate-300">Beat Drop Transition - Strobe Pulse</span>
|
|
168
|
+
</div>
|
|
169
|
+
<span class="text-[10px] text-slate-500">COMPLETE</span>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div id="cue-item-14" class="p-3 rounded-lg bg-indigo-950/40 border border-indigo-500/50 flex items-center justify-between text-white shadow-sm">
|
|
173
|
+
<div class="flex items-center space-x-3">
|
|
174
|
+
<span class="text-indigo-400 font-bold">#14</span>
|
|
175
|
+
<span class="font-medium text-indigo-100">Main Drop: Audio-Reactive Feedback Tunnel</span>
|
|
176
|
+
</div>
|
|
177
|
+
<span class="text-[10px] bg-indigo-600 text-white px-2 py-0.5 rounded font-sans uppercase tracking-wider font-semibold">RUNNING</span>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div id="cue-item-15" class="p-3 rounded-lg bg-slate-950/40 border border-slate-800/50 flex items-center justify-between text-slate-400">
|
|
181
|
+
<div class="flex items-center space-x-3">
|
|
182
|
+
<span class="text-slate-500 font-bold">#15</span>
|
|
183
|
+
<span>Post-Chorus: Particle Dissolve</span>
|
|
184
|
+
</div>
|
|
185
|
+
<span class="text-[10px] text-slate-500">STANDBY</span>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</section>
|
|
189
|
+
|
|
190
|
+
<!-- Interactive Mock Control Sliders -->
|
|
191
|
+
<section id="reactive-controls" data-component="reactive-controls" class="p-5 rounded-xl bg-slate-900 border border-slate-800">
|
|
192
|
+
<h3 class="text-sm font-semibold text-white mb-4">Real-Time Parameter Modulation</h3>
|
|
193
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
194
|
+
<div>
|
|
195
|
+
<div class="flex justify-between text-xs text-slate-300 mb-1">
|
|
196
|
+
<span>Feedback Gain</span>
|
|
197
|
+
<span id="label-gain" class="font-mono text-indigo-400">0.82</span>
|
|
198
|
+
</div>
|
|
199
|
+
<input type="range" min="0" max="1" step="0.01" value="0.82" oninput="updateSlider('gain', this.value)" class="w-full accent-indigo-500 cursor-pointer">
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div>
|
|
203
|
+
<div class="flex justify-between text-xs text-slate-300 mb-1">
|
|
204
|
+
<span>FFT Bass Dampening</span>
|
|
205
|
+
<span id="label-bass" class="font-mono text-indigo-400">0.45</span>
|
|
206
|
+
</div>
|
|
207
|
+
<input type="range" min="0" max="1" step="0.01" value="0.45" oninput="updateSlider('bass', this.value)" class="w-full accent-indigo-500 cursor-pointer">
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</section>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<!-- Right 1 Col: Live Event Log -->
|
|
214
|
+
<div class="space-y-6">
|
|
215
|
+
<section id="log-feed-section" data-component="log-feed" class="p-5 rounded-xl bg-slate-900 border border-slate-800 flex flex-col h-[460px]">
|
|
216
|
+
<div class="flex items-center justify-between mb-3">
|
|
217
|
+
<h3 class="text-sm font-semibold text-white">Event Dispatcher Log</h3>
|
|
218
|
+
<button onclick="clearLogs()" class="text-[11px] text-slate-400 hover:text-slate-200">Clear</button>
|
|
219
|
+
</div>
|
|
220
|
+
<div id="log-feed" class="flex-1 overflow-y-auto font-mono text-[11px] space-y-2 custom-scrollbar pr-1">
|
|
221
|
+
<div class="text-slate-500"><span class="text-slate-600">[03:32:01]</span> OSC /cue/14 fired to port 8000</div>
|
|
222
|
+
<div class="text-emerald-400"><span class="text-slate-600">[03:32:02]</span> TD TOP cook ok (4.1ms)</div>
|
|
223
|
+
<div class="text-indigo-300"><span class="text-slate-600">[03:32:04]</span> grandMA3 Macro 21 active</div>
|
|
224
|
+
<div class="text-slate-400"><span class="text-slate-600">[03:32:05]</span> MIDI beat sync: 128.0 BPM</div>
|
|
225
|
+
</div>
|
|
226
|
+
</section>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<!-- VARIANT B: Compact Data Sheet (Table View) -->
|
|
233
|
+
<div id="view-variant-b" class="preview-variant hidden space-y-4">
|
|
234
|
+
<section id="table-section" data-component="compact-table" class="rounded-xl bg-slate-900 border border-slate-800 overflow-hidden">
|
|
235
|
+
<table class="w-full text-left text-xs text-slate-300">
|
|
236
|
+
<thead class="bg-slate-950 text-slate-400 uppercase font-mono text-[10px] border-b border-slate-800">
|
|
237
|
+
<tr>
|
|
238
|
+
<th class="px-4 py-3">Fixture / Node</th>
|
|
239
|
+
<th class="px-4 py-3">Protocol</th>
|
|
240
|
+
<th class="px-4 py-3">Address</th>
|
|
241
|
+
<th class="px-4 py-3">FPS / Rate</th>
|
|
242
|
+
<th class="px-4 py-3 text-right">Status</th>
|
|
243
|
+
</tr>
|
|
244
|
+
</thead>
|
|
245
|
+
<tbody class="divide-y divide-slate-800 font-mono">
|
|
246
|
+
<tr class="hover:bg-slate-800/40">
|
|
247
|
+
<td class="px-4 py-3 font-semibold text-white font-sans">Beam 350 Array</td>
|
|
248
|
+
<td class="px-4 py-3">sACN E1.31</td>
|
|
249
|
+
<td class="px-4 py-3">10.0.1.50:5568</td>
|
|
250
|
+
<td class="px-4 py-3">44 Hz</td>
|
|
251
|
+
<td class="px-4 py-3 text-right text-emerald-400">SYNCED</td>
|
|
252
|
+
</tr>
|
|
253
|
+
<tr class="hover:bg-slate-800/40">
|
|
254
|
+
<td class="px-4 py-3 font-semibold text-white font-sans">Laser Pangolin FB4</td>
|
|
255
|
+
<td class="px-4 py-3">ILDA / IP</td>
|
|
256
|
+
<td class="px-4 py-3">10.0.2.10:1200</td>
|
|
257
|
+
<td class="px-4 py-3">30 kpps</td>
|
|
258
|
+
<td class="px-4 py-3 text-right text-emerald-400">ARMED</td>
|
|
259
|
+
</tr>
|
|
260
|
+
<tr class="hover:bg-slate-800/40">
|
|
261
|
+
<td class="px-4 py-3 font-semibold text-white font-sans">Resolume Arena Deck</td>
|
|
262
|
+
<td class="px-4 py-3">OSC + Syphon</td>
|
|
263
|
+
<td class="px-4 py-3">127.0.0.1:7000</td>
|
|
264
|
+
<td class="px-4 py-3">60 FPS</td>
|
|
265
|
+
<td class="px-4 py-3 text-right text-indigo-400">ACTIVE</td>
|
|
266
|
+
</tr>
|
|
267
|
+
</tbody>
|
|
268
|
+
</table>
|
|
269
|
+
</section>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- VARIANT C: Panic / Blackout / Disconnected View -->
|
|
273
|
+
<div id="view-variant-c" class="preview-variant hidden">
|
|
274
|
+
<section id="panic-section" data-component="panic-view" class="p-8 rounded-xl bg-slate-900 border-2 border-rose-600/60 text-center space-y-4">
|
|
275
|
+
<div class="inline-flex h-14 w-14 rounded-full bg-rose-950 text-rose-400 items-center justify-center text-2xl font-bold border border-rose-800">
|
|
276
|
+
🛑
|
|
277
|
+
</div>
|
|
278
|
+
<h2 class="text-lg font-bold text-white">Emergency Blackout Triggered</h2>
|
|
279
|
+
<p class="text-xs text-slate-400 max-w-md mx-auto">
|
|
280
|
+
All sACN universes clamped to 0. TouchDesigner master OP bypassed. Resolume output blackout layer 1 active.
|
|
281
|
+
</p>
|
|
282
|
+
<div class="pt-2 flex justify-center space-x-3">
|
|
283
|
+
<button onclick="switchVariant('A')" class="bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold px-4 py-2 rounded-lg transition shadow-md shadow-indigo-600/20">
|
|
284
|
+
Resume Normal Flow (Variant A)
|
|
285
|
+
</button>
|
|
286
|
+
</div>
|
|
287
|
+
</section>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
</main>
|
|
291
|
+
|
|
292
|
+
<!-- =========================================================================
|
|
293
|
+
Floating Variant Switcher Bar
|
|
294
|
+
========================================================================= -->
|
|
295
|
+
<footer id="prototype-switcher-bar" data-component="switcher-bar" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-slate-900/90 backdrop-blur-md border border-slate-700/80 shadow-2xl rounded-full px-3 py-2 flex items-center space-x-2">
|
|
296
|
+
<button onclick="cycleVariant(-1)" aria-label="Previous Variant" class="p-1.5 text-slate-400 hover:text-white hover:bg-slate-800 rounded-full transition">
|
|
297
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
|
298
|
+
</button>
|
|
299
|
+
|
|
300
|
+
<div class="flex items-center space-x-1">
|
|
301
|
+
<button onclick="switchVariant('A')" id="tab-btn-A" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition bg-indigo-600 text-white shadow-sm">
|
|
302
|
+
Variant A (Control Board)
|
|
303
|
+
</button>
|
|
304
|
+
<button onclick="switchVariant('B')" id="tab-btn-B" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition text-slate-400 hover:text-slate-200">
|
|
305
|
+
Variant B (Table Matrix)
|
|
306
|
+
</button>
|
|
307
|
+
<button onclick="switchVariant('C')" id="tab-btn-C" class="switcher-pill px-3 py-1 rounded-full text-xs font-medium transition text-slate-400 hover:text-slate-200">
|
|
308
|
+
Variant C (Panic State)
|
|
309
|
+
</button>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<button onclick="cycleVariant(1)" aria-label="Next Variant" class="p-1.5 text-slate-400 hover:text-white hover:bg-slate-800 rounded-full transition">
|
|
313
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
314
|
+
</button>
|
|
315
|
+
</footer>
|
|
316
|
+
|
|
317
|
+
<!-- =========================================================================
|
|
318
|
+
Interactive State Script
|
|
319
|
+
========================================================================= -->
|
|
320
|
+
<script>
|
|
321
|
+
const VARIANTS = ['A', 'B', 'C'];
|
|
322
|
+
let currentVariantIndex = 0;
|
|
323
|
+
let cueNum = 14;
|
|
324
|
+
|
|
325
|
+
function getVariantFromUrl() {
|
|
326
|
+
const params = new URLSearchParams(window.location.search);
|
|
327
|
+
const v = params.get('variant');
|
|
328
|
+
return (v && VARIANTS.includes(v.toUpperCase())) ? v.toUpperCase() : 'A';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function switchVariant(variantKey) {
|
|
332
|
+
const normalized = variantKey.toUpperCase();
|
|
333
|
+
currentVariantIndex = VARIANTS.indexOf(normalized);
|
|
334
|
+
if (currentVariantIndex === -1) currentVariantIndex = 0;
|
|
335
|
+
|
|
336
|
+
document.querySelectorAll('.preview-variant').forEach(el => el.classList.add('hidden'));
|
|
337
|
+
const activeEl = document.getElementById(`view-variant-${normalized.toLowerCase()}`);
|
|
338
|
+
if (activeEl) activeEl.classList.remove('hidden');
|
|
339
|
+
|
|
340
|
+
document.querySelectorAll('.switcher-pill').forEach(pill => {
|
|
341
|
+
pill.classList.remove('bg-indigo-600', 'text-white', 'shadow-sm');
|
|
342
|
+
pill.classList.add('text-slate-400');
|
|
343
|
+
});
|
|
344
|
+
const activePill = document.getElementById(`tab-btn-${normalized}`);
|
|
345
|
+
if (activePill) {
|
|
346
|
+
activePill.classList.add('bg-indigo-600', 'text-white', 'shadow-sm');
|
|
347
|
+
activePill.classList.remove('text-slate-400');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const url = new URL(window.location);
|
|
351
|
+
url.searchParams.set('variant', normalized);
|
|
352
|
+
window.history.replaceState({}, '', url);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function cycleVariant(delta) {
|
|
356
|
+
currentVariantIndex = (currentVariantIndex + delta + VARIANTS.length) % VARIANTS.length;
|
|
357
|
+
switchVariant(VARIANTS[currentVariantIndex]);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function advanceCue() {
|
|
361
|
+
cueNum++;
|
|
362
|
+
document.getElementById('cue-counter').textContent = `Cue ${cueNum}/40`;
|
|
363
|
+
addLog(`Dispatched Cue #${cueNum} over OSC`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function resetCues() {
|
|
367
|
+
cueNum = 14;
|
|
368
|
+
document.getElementById('cue-counter').textContent = `Cue ${cueNum}/40`;
|
|
369
|
+
addLog(`Reset cue counter to Cue #14`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function updateSlider(key, val) {
|
|
373
|
+
document.getElementById(`label-${key}`).textContent = val;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function addLog(msg) {
|
|
377
|
+
const feed = document.getElementById('log-feed');
|
|
378
|
+
const time = new Date().toLocaleTimeString();
|
|
379
|
+
const div = document.createElement('div');
|
|
380
|
+
div.className = 'text-indigo-300';
|
|
381
|
+
div.innerHTML = `<span class="text-slate-600">[${time}]</span> ${msg}`;
|
|
382
|
+
feed.prepend(div);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function clearLogs() {
|
|
386
|
+
document.getElementById('log-feed').innerHTML = '<div class="text-slate-500 font-mono text-[11px]">Logs cleared.</div>';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function triggerPanic() {
|
|
390
|
+
switchVariant('C');
|
|
391
|
+
addLog('⚠️ BLACKOUT TRIGGERED');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
window.addEventListener('keydown', (e) => {
|
|
395
|
+
const tag = document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
|
|
396
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
|
|
397
|
+
if (e.key === 'ArrowRight') cycleVariant(1);
|
|
398
|
+
if (e.key === 'ArrowLeft') cycleVariant(-1);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
402
|
+
switchVariant(getVariantFromUrl());
|
|
403
|
+
});
|
|
404
|
+
</script>
|
|
405
|
+
</body>
|
|
406
|
+
</html>
|