r1-create 1.2.0 → 1.3.1
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/README.md +47 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/index.d.ts +140 -2
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +288 -4
- package/dist/llm/index.js.map +1 -1
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -1
- package/examples/README.md +36 -0
- package/examples/camera/index.html +160 -0
- package/examples/device-controls/index.html +191 -0
- package/examples/hardware-game/index.html +165 -0
- package/examples/messaging-test/index.html +359 -0
- package/examples/nextjs-test-app/README.md +72 -0
- package/examples/nextjs-test-app/app/globals.css +282 -0
- package/examples/nextjs-test-app/app/layout.jsx +14 -0
- package/examples/nextjs-test-app/app/page.jsx +1265 -0
- package/examples/nextjs-test-app/log-relay/device-console-bridge.js +91 -0
- package/examples/nextjs-test-app/log-relay/server.js +55 -0
- package/examples/nextjs-test-app/next.config.js +8 -0
- package/examples/nextjs-test-app/package-lock.json +1227 -0
- package/examples/nextjs-test-app/package.json +18 -0
- package/examples/text-to-speech/index.html +128 -0
- package/examples/ui-design/index.html +189 -0
- package/examples/voice-recorder/index.html +149 -0
- package/examples/web-search/index.html +147 -0
- package/package.json +1 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=240, height=282, user-scalable=no">
|
|
6
|
+
<title>Messaging Test Harness</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
-webkit-tap-highlight-color: transparent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html, body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
width: 240px;
|
|
17
|
+
height: 282px;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
20
|
+
background: #070707;
|
|
21
|
+
color: #f4f4f4;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.app {
|
|
25
|
+
width: 240px;
|
|
26
|
+
height: 282px;
|
|
27
|
+
display: grid;
|
|
28
|
+
grid-template-rows: auto 1fr auto;
|
|
29
|
+
gap: 8px;
|
|
30
|
+
padding: 8px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.title {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: 12px;
|
|
36
|
+
letter-spacing: 0.4px;
|
|
37
|
+
color: #ffb38a;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.grid {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: 1fr 1fr;
|
|
43
|
+
gap: 6px;
|
|
44
|
+
align-content: start;
|
|
45
|
+
overflow-y: auto;
|
|
46
|
+
padding-right: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.grid::-webkit-scrollbar {
|
|
50
|
+
width: 0;
|
|
51
|
+
height: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
button {
|
|
55
|
+
border: 1px solid #2a2a2a;
|
|
56
|
+
background: #141414;
|
|
57
|
+
color: #f4f4f4;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
min-height: 34px;
|
|
60
|
+
font-size: 10px;
|
|
61
|
+
line-height: 1.2;
|
|
62
|
+
padding: 6px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
button:active {
|
|
66
|
+
transform: scale(0.98);
|
|
67
|
+
background: #1e1e1e;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.accent {
|
|
71
|
+
background: #fe5f00;
|
|
72
|
+
border-color: #fe5f00;
|
|
73
|
+
color: #fff;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.log {
|
|
77
|
+
height: 96px;
|
|
78
|
+
overflow-y: auto;
|
|
79
|
+
border: 1px solid #1f1f1f;
|
|
80
|
+
border-radius: 8px;
|
|
81
|
+
background: #0f0f0f;
|
|
82
|
+
padding: 6px;
|
|
83
|
+
font-size: 9px;
|
|
84
|
+
line-height: 1.3;
|
|
85
|
+
white-space: pre-wrap;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.row {
|
|
89
|
+
margin-bottom: 4px;
|
|
90
|
+
border-bottom: 1px solid #1b1b1b;
|
|
91
|
+
padding-bottom: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.row:last-child {
|
|
95
|
+
border-bottom: 0;
|
|
96
|
+
margin-bottom: 0;
|
|
97
|
+
padding-bottom: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.muted {
|
|
101
|
+
color: #9f9f9f;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<div class="app">
|
|
107
|
+
<h1 class="title">Messaging Test Harness</h1>
|
|
108
|
+
|
|
109
|
+
<div class="grid">
|
|
110
|
+
<button class="accent" onclick="runRuntimeCheck()">Runtime Caps</button>
|
|
111
|
+
<button onclick="runWaitForNext()">Wait Next Msg</button>
|
|
112
|
+
|
|
113
|
+
<button onclick="runAskLLMTimeout()">askLLMWithTimeout</button>
|
|
114
|
+
<button onclick="runSearchWeather()">Search Weather</button>
|
|
115
|
+
|
|
116
|
+
<button onclick="runSearchImage()">Search Image</button>
|
|
117
|
+
<button onclick="runEmailUser()">emailUser</button>
|
|
118
|
+
|
|
119
|
+
<button onclick="runAnalyzeImage()">analyzeImageBase64</button>
|
|
120
|
+
<button onclick="runSpeakText()">speakText</button>
|
|
121
|
+
|
|
122
|
+
<button onclick="runStartSTT()">startSTTListening</button>
|
|
123
|
+
<button onclick="runStopSTT()">stopSTTListening</button>
|
|
124
|
+
|
|
125
|
+
<button onclick="runEnablePTT()">enablePushToTalk</button>
|
|
126
|
+
<button onclick="runDisablePTT()">disablePushToTalk</button>
|
|
127
|
+
|
|
128
|
+
<button onclick="showSTTState()">isSTTListening</button>
|
|
129
|
+
<button onclick="clearLog()">Clear Log</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div id="log" class="log"></div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<script type="module">
|
|
136
|
+
import { r1, createR1App } from './../../dist/index.js';
|
|
137
|
+
|
|
138
|
+
const logEl = document.getElementById('log');
|
|
139
|
+
let disablePTT = null;
|
|
140
|
+
|
|
141
|
+
function log(message, extra) {
|
|
142
|
+
const row = document.createElement('div');
|
|
143
|
+
row.className = 'row';
|
|
144
|
+
const timestamp = new Date().toISOString().slice(11, 19);
|
|
145
|
+
row.textContent = `[${timestamp}] ${message}` + (extra ? `\n${extra}` : '');
|
|
146
|
+
logEl.prepend(row);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function safeStringify(value) {
|
|
150
|
+
try {
|
|
151
|
+
return JSON.stringify(value, null, 2);
|
|
152
|
+
} catch {
|
|
153
|
+
return String(value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getSampleBase64Image() {
|
|
158
|
+
const canvas = document.createElement('canvas');
|
|
159
|
+
canvas.width = 64;
|
|
160
|
+
canvas.height = 64;
|
|
161
|
+
const ctx = canvas.getContext('2d');
|
|
162
|
+
if (!ctx) return '';
|
|
163
|
+
|
|
164
|
+
ctx.fillStyle = '#111111';
|
|
165
|
+
ctx.fillRect(0, 0, 64, 64);
|
|
166
|
+
ctx.fillStyle = '#fe5f00';
|
|
167
|
+
ctx.fillRect(8, 8, 48, 48);
|
|
168
|
+
ctx.fillStyle = '#ffffff';
|
|
169
|
+
ctx.font = '10px sans-serif';
|
|
170
|
+
ctx.fillText('R1', 23, 35);
|
|
171
|
+
|
|
172
|
+
return canvas.toDataURL('image/png').split(',')[1];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
createR1App(async (sdk) => {
|
|
176
|
+
log('SDK initialized. Ready to test messaging APIs.');
|
|
177
|
+
|
|
178
|
+
sdk.messaging.onMessage((response) => {
|
|
179
|
+
const transcript = response.transcript ? ` transcript=${response.transcript}` : '';
|
|
180
|
+
log(`onMessage type=${response.type || 'n/a'}${transcript}`);
|
|
181
|
+
|
|
182
|
+
if (response.parsedData) {
|
|
183
|
+
log('parsedData:', safeStringify(response.parsedData));
|
|
184
|
+
} else if (response.message) {
|
|
185
|
+
log('message:', response.message);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
sdk.hardware.on('sideClick', () => {
|
|
190
|
+
log('Hardware event: sideClick');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
window.runRuntimeCheck = () => {
|
|
195
|
+
try {
|
|
196
|
+
const caps = r1.messaging.getRuntimeCapabilities();
|
|
197
|
+
log('Runtime capabilities', safeStringify(caps));
|
|
198
|
+
} catch (error) {
|
|
199
|
+
log('Runtime capability check failed', error.message);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
window.runWaitForNext = async () => {
|
|
204
|
+
try {
|
|
205
|
+
log('Waiting for next message (10s timeout)...');
|
|
206
|
+
const msg = await r1.messaging.waitForNextMessage({ timeoutMs: 10000 });
|
|
207
|
+
log('waitForNextMessage resolved', safeStringify({
|
|
208
|
+
type: msg.type,
|
|
209
|
+
message: msg.message,
|
|
210
|
+
transcript: msg.transcript
|
|
211
|
+
}));
|
|
212
|
+
} catch (error) {
|
|
213
|
+
log('waitForNextMessage failed', error.message);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
window.runAskLLMTimeout = async () => {
|
|
218
|
+
try {
|
|
219
|
+
log('Calling askLLMWithTimeout...');
|
|
220
|
+
const response = await r1.messaging.askLLMWithTimeout(
|
|
221
|
+
'Reply with a short JSON object: {"ok": true, "source": "askLLMWithTimeout"}',
|
|
222
|
+
{ wantsR1Response: false, wantsJournalEntry: false },
|
|
223
|
+
{ timeoutMs: 20000 }
|
|
224
|
+
);
|
|
225
|
+
log('askLLMWithTimeout resolved', safeStringify(response.parsedData || response.message));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
log('askLLMWithTimeout failed', error.message);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
window.runSearchWeather = async () => {
|
|
232
|
+
try {
|
|
233
|
+
log('Sending weather search...');
|
|
234
|
+
await r1.messaging.searchWeb('weather in Tokyo', {
|
|
235
|
+
tag: 'weather',
|
|
236
|
+
useLocation: true
|
|
237
|
+
});
|
|
238
|
+
log('searchWeb(weather) sent');
|
|
239
|
+
} catch (error) {
|
|
240
|
+
log('searchWeb(weather) failed', error.message);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
window.runSearchImage = async () => {
|
|
245
|
+
try {
|
|
246
|
+
log('Sending image search...');
|
|
247
|
+
await r1.messaging.searchWeb('rabbit mascot logo', {
|
|
248
|
+
tag: 'image',
|
|
249
|
+
useLocation: false
|
|
250
|
+
});
|
|
251
|
+
log('searchWeb(image) sent');
|
|
252
|
+
} catch (error) {
|
|
253
|
+
log('searchWeb(image) failed', error.message);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
window.runEmailUser = async () => {
|
|
258
|
+
try {
|
|
259
|
+
log('Sending emailUser request...');
|
|
260
|
+
await r1.messaging.emailUser('SDK messaging test from examples/messaging-test');
|
|
261
|
+
log('emailUser sent');
|
|
262
|
+
} catch (error) {
|
|
263
|
+
log('emailUser failed', error.message);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
window.runAnalyzeImage = async () => {
|
|
268
|
+
try {
|
|
269
|
+
const imageBase64 = getSampleBase64Image();
|
|
270
|
+
if (!imageBase64) {
|
|
271
|
+
log('Unable to create sample image');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
log('Sending analyzeImageBase64 request...');
|
|
276
|
+
await r1.llm.analyzeImageBase64(
|
|
277
|
+
'Describe this test image in one sentence.',
|
|
278
|
+
imageBase64,
|
|
279
|
+
{ wantsR1Response: false, wantsJournalEntry: false }
|
|
280
|
+
);
|
|
281
|
+
log('analyzeImageBase64 sent');
|
|
282
|
+
} catch (error) {
|
|
283
|
+
log('analyzeImageBase64 failed', error.message);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
window.runSpeakText = async () => {
|
|
288
|
+
try {
|
|
289
|
+
log('Sending speakText request...');
|
|
290
|
+
await r1.messaging.speakText('Messaging test harness is working.');
|
|
291
|
+
log('speakText sent');
|
|
292
|
+
} catch (error) {
|
|
293
|
+
log('speakText failed', error.message);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
window.runStartSTT = () => {
|
|
298
|
+
try {
|
|
299
|
+
r1.messaging.startSTTListening();
|
|
300
|
+
log('startSTTListening called');
|
|
301
|
+
} catch (error) {
|
|
302
|
+
log('startSTTListening failed', error.message);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
window.runStopSTT = () => {
|
|
307
|
+
try {
|
|
308
|
+
r1.messaging.stopSTTListening();
|
|
309
|
+
log('stopSTTListening called');
|
|
310
|
+
} catch (error) {
|
|
311
|
+
log('stopSTTListening failed', error.message);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
window.runEnablePTT = () => {
|
|
316
|
+
try {
|
|
317
|
+
if (disablePTT) {
|
|
318
|
+
disablePTT();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
disablePTT = r1.messaging.enablePushToTalk({
|
|
322
|
+
onTranscript: (transcript) => {
|
|
323
|
+
log('PTT transcript callback', transcript);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
log('enablePushToTalk enabled');
|
|
327
|
+
} catch (error) {
|
|
328
|
+
log('enablePushToTalk failed', error.message);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
window.runDisablePTT = () => {
|
|
333
|
+
try {
|
|
334
|
+
if (disablePTT) {
|
|
335
|
+
disablePTT();
|
|
336
|
+
disablePTT = null;
|
|
337
|
+
} else {
|
|
338
|
+
r1.messaging.disablePushToTalk();
|
|
339
|
+
}
|
|
340
|
+
log('disablePushToTalk called');
|
|
341
|
+
} catch (error) {
|
|
342
|
+
log('disablePushToTalk failed', error.message);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
window.showSTTState = () => {
|
|
347
|
+
try {
|
|
348
|
+
log(`isSTTListening=${r1.messaging.isSTTListening()}`);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
log('isSTTListening check failed', error.message);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
window.clearLog = () => {
|
|
355
|
+
logEl.innerHTML = '<div class="muted">Log cleared</div>';
|
|
356
|
+
};
|
|
357
|
+
</script>
|
|
358
|
+
</body>
|
|
359
|
+
</html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Next.js Full SDK Test App
|
|
2
|
+
|
|
3
|
+
Comprehensive test application for the R1 Create SDK using Next.js.
|
|
4
|
+
|
|
5
|
+
## What it covers
|
|
6
|
+
|
|
7
|
+
- Core SDK initialization and feature checks
|
|
8
|
+
- Hardware APIs (accelerometer, touch, hardware events, device controls)
|
|
9
|
+
- Storage APIs (Base64 utilities, plain/secure storage helpers)
|
|
10
|
+
- Messaging and LLM APIs (including timeout, STT, push-to-talk, search, email, image analysis)
|
|
11
|
+
- STT transcript simulation path through `onPluginMessage`
|
|
12
|
+
- AI image-generation prompt workflow (manual and smoke-path send)
|
|
13
|
+
- AI calling-style JSON schema workflow (manual and smoke-path send)
|
|
14
|
+
- Full LLM helper coverage (`getUserMemories`, `analyzeData`, `performTask`, `textToSpeech`, `textToSpeechAudio`, `getUISuggestions`, `analyzeImageBase64`)
|
|
15
|
+
- UI utilities and component lifecycle
|
|
16
|
+
- Media utilities and manual camera/microphone/speaker tests
|
|
17
|
+
|
|
18
|
+
## Run
|
|
19
|
+
|
|
20
|
+
From repository root:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd examples/nextjs-test-app
|
|
24
|
+
npm install
|
|
25
|
+
npm run dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then open:
|
|
29
|
+
|
|
30
|
+
- http://localhost:3000
|
|
31
|
+
|
|
32
|
+
## Device Log Relay (Socket.IO)
|
|
33
|
+
|
|
34
|
+
This app includes a Socket.IO relay server to stream console logs from a device creation.
|
|
35
|
+
|
|
36
|
+
Start relay server in a second terminal:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd examples/nextjs-test-app
|
|
40
|
+
npm run relay
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then in the dashboard, connect to relay URL (default: `http://localhost:3031`).
|
|
44
|
+
|
|
45
|
+
The dashboard now auto-fills relay URL using the current page host:
|
|
46
|
+
|
|
47
|
+
- `${window.location.protocol}//${window.location.hostname}:3031`
|
|
48
|
+
|
|
49
|
+
You can also click **Use Current Address** in the UI to reset it.
|
|
50
|
+
|
|
51
|
+
To forward logs from a device/webview, include this bridge in your creation:
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<script>
|
|
55
|
+
window.__R1_RELAY_URL__ = 'http://YOUR_HOST:3031';
|
|
56
|
+
window.__R1_DEVICE_ID__ = 'my-r1-device';
|
|
57
|
+
</script>
|
|
58
|
+
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
|
|
59
|
+
<script src="/path/to/device-console-bridge.js"></script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Bridge file location:
|
|
63
|
+
|
|
64
|
+
- `examples/nextjs-test-app/log-relay/device-console-bridge.js`
|
|
65
|
+
|
|
66
|
+
## Notes
|
|
67
|
+
|
|
68
|
+
- Some tests are environment-dependent and will be marked as skipped in non-R1 environments.
|
|
69
|
+
- Manual media tests require browser permissions.
|
|
70
|
+
- `closePlugin` can close the webview on device, so it is intentionally a manual button.
|
|
71
|
+
- For AI image generation and AI-calling tests, the app verifies request flow and response handling; exact model behavior depends on runtime LLM capabilities.
|
|
72
|
+
- The UI includes a compact small-screen mode and a live hardware panel for side button/PTT, long press, scroll, and accelerometer monitoring.
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
color-scheme: dark;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
html,
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
background: #050505;
|
|
14
|
+
color: #f4f4f4;
|
|
15
|
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main {
|
|
19
|
+
max-width: 980px;
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
padding: 18px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
h1 {
|
|
25
|
+
margin: 0 0 6px;
|
|
26
|
+
font-size: 20px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
p {
|
|
30
|
+
margin: 0;
|
|
31
|
+
color: #bcbcbc;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.panel {
|
|
35
|
+
border: 1px solid #1e1e1e;
|
|
36
|
+
background: #0c0c0c;
|
|
37
|
+
border-radius: 12px;
|
|
38
|
+
padding: 12px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.grid {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
44
|
+
gap: 10px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.stack {
|
|
48
|
+
display: grid;
|
|
49
|
+
gap: 10px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
button {
|
|
53
|
+
border: 1px solid #2d2d2d;
|
|
54
|
+
background: #141414;
|
|
55
|
+
color: #f6f6f6;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
padding: 8px 10px;
|
|
58
|
+
min-height: 36px;
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
button:hover {
|
|
64
|
+
background: #1a1a1a;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
button.primary {
|
|
68
|
+
border-color: #fe5f00;
|
|
69
|
+
background: #fe5f00;
|
|
70
|
+
color: #fff;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.relay-input {
|
|
74
|
+
width: 100%;
|
|
75
|
+
border: 1px solid #2c2c2c;
|
|
76
|
+
background: #101010;
|
|
77
|
+
color: #fff;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
min-height: 34px;
|
|
80
|
+
padding: 6px 8px;
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
button.warn {
|
|
85
|
+
border-color: #c33;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.code {
|
|
89
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.log {
|
|
94
|
+
height: 280px;
|
|
95
|
+
overflow: auto;
|
|
96
|
+
border: 1px solid #222;
|
|
97
|
+
border-radius: 10px;
|
|
98
|
+
background: #090909;
|
|
99
|
+
padding: 8px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.entry {
|
|
103
|
+
border-bottom: 1px solid #1a1a1a;
|
|
104
|
+
padding: 6px 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.entry:last-child {
|
|
108
|
+
border-bottom: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.pass {
|
|
112
|
+
color: #7bdf8e;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.fail {
|
|
116
|
+
color: #ff8d8d;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.skip {
|
|
120
|
+
color: #e6cb7f;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.small {
|
|
124
|
+
font-size: 11px;
|
|
125
|
+
color: #9e9e9e;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.preview {
|
|
129
|
+
width: 160px;
|
|
130
|
+
height: 160px;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
border: 1px solid #2a2a2a;
|
|
133
|
+
object-fit: cover;
|
|
134
|
+
background: #111;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.gyro-visual-row {
|
|
138
|
+
display: grid;
|
|
139
|
+
grid-template-columns: 1fr auto;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 10px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.gyro-xy {
|
|
145
|
+
width: 140px;
|
|
146
|
+
height: 140px;
|
|
147
|
+
border: 1px solid #2b2b2b;
|
|
148
|
+
border-radius: 10px;
|
|
149
|
+
position: relative;
|
|
150
|
+
background: #101010;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.gyro-cross-h {
|
|
154
|
+
position: absolute;
|
|
155
|
+
left: 0;
|
|
156
|
+
right: 0;
|
|
157
|
+
top: 50%;
|
|
158
|
+
height: 1px;
|
|
159
|
+
background: #2f2f2f;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.gyro-cross-v {
|
|
163
|
+
position: absolute;
|
|
164
|
+
top: 0;
|
|
165
|
+
bottom: 0;
|
|
166
|
+
left: 50%;
|
|
167
|
+
width: 1px;
|
|
168
|
+
background: #2f2f2f;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.gyro-dot {
|
|
172
|
+
position: absolute;
|
|
173
|
+
width: 10px;
|
|
174
|
+
height: 10px;
|
|
175
|
+
background: #fe5f00;
|
|
176
|
+
border-radius: 50%;
|
|
177
|
+
transform: translate(-50%, -50%);
|
|
178
|
+
transition: left 40ms linear, top 40ms linear;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.gyro-dot-raw {
|
|
182
|
+
position: absolute;
|
|
183
|
+
width: 8px;
|
|
184
|
+
height: 8px;
|
|
185
|
+
background: #3ea6ff;
|
|
186
|
+
border-radius: 50%;
|
|
187
|
+
transform: translate(-50%, -50%);
|
|
188
|
+
transition: left 40ms linear, top 40ms linear;
|
|
189
|
+
box-shadow: 0 0 0 1px #0b1b2c;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.gyro-z-wrap {
|
|
193
|
+
display: grid;
|
|
194
|
+
justify-items: center;
|
|
195
|
+
gap: 4px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.gyro-z-track {
|
|
199
|
+
width: 16px;
|
|
200
|
+
height: 140px;
|
|
201
|
+
border: 1px solid #2b2b2b;
|
|
202
|
+
border-radius: 8px;
|
|
203
|
+
background: #101010;
|
|
204
|
+
position: relative;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.gyro-z-fill {
|
|
209
|
+
position: absolute;
|
|
210
|
+
left: 0;
|
|
211
|
+
right: 0;
|
|
212
|
+
bottom: 0;
|
|
213
|
+
background: #3ea6ff;
|
|
214
|
+
transition: height 40ms linear;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@media (max-width: 340px) {
|
|
218
|
+
main {
|
|
219
|
+
max-width: 240px;
|
|
220
|
+
padding: 6px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
h1 {
|
|
224
|
+
font-size: 14px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.panel {
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
padding: 8px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.grid {
|
|
233
|
+
grid-template-columns: 1fr;
|
|
234
|
+
gap: 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.stack {
|
|
238
|
+
gap: 8px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
button {
|
|
242
|
+
min-height: 32px;
|
|
243
|
+
font-size: 11px;
|
|
244
|
+
padding: 6px 8px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.relay-input {
|
|
248
|
+
min-height: 30px;
|
|
249
|
+
font-size: 11px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.log {
|
|
253
|
+
height: 170px;
|
|
254
|
+
padding: 6px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.entry {
|
|
258
|
+
padding: 4px 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.small {
|
|
262
|
+
font-size: 10px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.code {
|
|
266
|
+
font-size: 10px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.preview {
|
|
270
|
+
width: 120px;
|
|
271
|
+
height: 120px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.gyro-xy {
|
|
275
|
+
width: 110px;
|
|
276
|
+
height: 110px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.gyro-z-track {
|
|
280
|
+
height: 110px;
|
|
281
|
+
}
|
|
282
|
+
}
|