yozkut commited on
Commit
c206f78
·
verified ·
1 Parent(s): 382d5f2

Sync from GitHub via huggingface-sync-action

Browse files
Files changed (5) hide show
  1. demo.js +535 -0
  2. index.html +107 -59
  3. reachy-happy.svg +0 -0
  4. reachy-mad.svg +35 -0
  5. style.css +1241 -115
demo.js ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Judgy Reachy No Phone - Browser Demo
2
+ // Uses Transformers.js for YOLO detection in the browser
3
+
4
+ import { AutoModel, AutoProcessor, RawImage } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1';
5
+
6
+ // DOM Elements
7
+ const video = document.getElementById('webcam');
8
+ const canvas = document.getElementById('canvas');
9
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
10
+ const robotSvg = document.getElementById('robot-svg');
11
+ const cameraBtn = document.getElementById('camera-btn');
12
+ const cameraIcon = document.getElementById('camera-icon');
13
+ const cameraText = document.getElementById('camera-text');
14
+ const startBtn = document.getElementById('start-btn');
15
+ const btnIcon = document.getElementById('btn-icon');
16
+ const btnText = document.getElementById('btn-text');
17
+ const statusIndicator = document.getElementById('status-indicator');
18
+ const statusText = document.getElementById('status-text');
19
+ const fpsEl = document.getElementById('fps');
20
+ const responseText = document.getElementById('response-text');
21
+ const loader = document.getElementById('loader');
22
+ const loaderText = document.getElementById('loader-text');
23
+
24
+ // Loader helpers
25
+ const showLoader = (text) => {
26
+ loaderText.textContent = text;
27
+ loader.classList.add('visible');
28
+ };
29
+
30
+ const hideLoader = () => {
31
+ loader.classList.remove('visible');
32
+ };
33
+
34
+ // Demo defaults (hardcoded - Pure Reachy mode)
35
+ const DEMO_COOLDOWN = 10; // 10 seconds cooldown
36
+ const DEMO_PRAISE_ENABLED = true; // Enable praise sounds!
37
+
38
+ // State
39
+ let model = null;
40
+ let processor = null;
41
+ let isRunning = false;
42
+ let isMonitoring = false;
43
+ let isProcessing = false; // Prevent overlapping detections
44
+ let animationId = null;
45
+ let stream = null;
46
+
47
+ // Detection state
48
+ let phoneVisible = false;
49
+ let consecutivePhone = 0;
50
+ let consecutiveNoPhone = 0;
51
+ let phoneCount = 0;
52
+ let lastReactionTime = 0;
53
+
54
+ // Tracking state
55
+ let lastPhoneBox = null; // Last known phone position
56
+ let framesWithoutDetection = 0; // Count frames without detection
57
+
58
+ // Offscreen canvas for processing
59
+ const offscreen = document.createElement('canvas');
60
+ const offscreenCtx = offscreen.getContext('2d', { willReadFrequently: true });
61
+
62
+ // Constants
63
+ const PHONE_CLASS_ID = 67; // Cell phone in COCO dataset
64
+ const PICKUP_THRESHOLD = 3;
65
+ const PUTDOWN_THRESHOLD = 15;
66
+ const DETECTION_CONFIDENCE = 0.5; // Initial detection: 0.1 (low) to 0.9 (high)
67
+ const TRACKING_CONFIDENCE = 0.2; // Lower threshold when tracking existing phone
68
+ const TRACKING_PERSIST_FRAMES = 3; // Keep tracking for N frames after losing detection
69
+
70
+ // Pure Reachy Mode - Robot emotion sounds from HuggingFace
71
+ const SHAME_EMOTIONS = [
72
+ "disgusted1",
73
+ "resigned1",
74
+ "displeased1",
75
+ "displeased2",
76
+ "rage1",
77
+ "no1",
78
+ "reprimand1",
79
+ "reprimand3",
80
+ "dying1",
81
+ "surprised1",
82
+ "surprised2"
83
+ ];
84
+
85
+ const PRAISE_EMOTIONS = [
86
+ "welcoming2",
87
+ "inquiring1",
88
+ "inquiring2",
89
+ "proud1",
90
+ "proud3",
91
+ "success1",
92
+ "success2",
93
+ "enthusiastic1",
94
+ "enthusiastic2",
95
+ "grateful1",
96
+ "yes1",
97
+ "cheerful1"
98
+ ];
99
+
100
+ const EMOTIONS_BASE_URL = "https://huggingface.co/datasets/pollen-robotics/reachy-mini-emotions-library/resolve/main";
101
+
102
+ // Initialize
103
+ async function init() {
104
+ try {
105
+ // Disable buttons while loading
106
+ cameraBtn.disabled = true;
107
+ startBtn.disabled = true;
108
+
109
+ // Show loader
110
+ showLoader('Loading YOLO26m model...');
111
+ statusText.textContent = 'Loading AI model...';
112
+ statusIndicator.className = 'status-dot loading';
113
+
114
+ // Load YOLO model
115
+ model = await AutoModel.from_pretrained('onnx-community/yolo26m-ONNX', {
116
+ device: 'webgpu',
117
+ dtype: 'fp16'
118
+ });
119
+
120
+ showLoader('Loading processor...');
121
+ processor = await AutoProcessor.from_pretrained('onnx-community/yolo26m-ONNX');
122
+
123
+ // Hide loader
124
+ hideLoader();
125
+ statusText.textContent = 'Model ready! Open camera to begin';
126
+ statusIndicator.className = 'status-dot ready';
127
+ cameraBtn.disabled = false;
128
+
129
+ console.log('YOLO model loaded successfully');
130
+ } catch (error) {
131
+ console.error('Failed to load model:', error);
132
+ showLoader('Failed to load model: ' + error.message);
133
+ statusText.textContent = 'Error loading model';
134
+ statusIndicator.className = 'status-dot error';
135
+ }
136
+ }
137
+
138
+ // Start webcam
139
+ async function startCamera() {
140
+ try {
141
+ stream = await navigator.mediaDevices.getUserMedia({
142
+ video: {
143
+ width: 640,
144
+ height: 480,
145
+ facingMode: 'user'
146
+ }
147
+ });
148
+
149
+ video.srcObject = stream;
150
+ await video.play();
151
+
152
+ // Show video and canvas
153
+ video.style.display = 'block';
154
+ canvas.style.display = 'block';
155
+
156
+ canvas.width = offscreen.width = video.videoWidth;
157
+ canvas.height = offscreen.height = video.videoHeight;
158
+
159
+ isRunning = true;
160
+ loop(); // Start the loop
161
+
162
+ statusIndicator.className = isMonitoring ? 'status-dot monitoring' : 'status-dot ready';
163
+
164
+ } catch (error) {
165
+ console.error('Camera error:', error);
166
+ alert('Could not access webcam. Please allow camera permissions.');
167
+ }
168
+ }
169
+
170
+ // Stop webcam
171
+ function stopCamera() {
172
+ isRunning = false;
173
+
174
+ if (stream) {
175
+ stream.getTracks().forEach(track => track.stop());
176
+ stream = null;
177
+ }
178
+
179
+ if (animationId) {
180
+ cancelAnimationFrame(animationId);
181
+ }
182
+
183
+ // Clear and hide video/canvas when closed
184
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
185
+ video.style.display = 'none';
186
+ canvas.style.display = 'none';
187
+ }
188
+
189
+ // Main loop (like YOLO26-WebGPU)
190
+ function loop() {
191
+ if (!isRunning) return;
192
+
193
+ if (!isMonitoring) {
194
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
195
+ }
196
+
197
+ // Run detection if ready (non-blocking)
198
+ if (isMonitoring && !isProcessing) {
199
+ isProcessing = true;
200
+ const startTime = performance.now();
201
+ detectAndProcess()
202
+ .then(() => {
203
+ fpsEl.textContent = Math.round(1000 / (performance.now() - startTime));
204
+ })
205
+ .finally(() => {
206
+ isProcessing = false;
207
+ });
208
+ }
209
+
210
+ if (isRunning) animationId = requestAnimationFrame(loop);
211
+ }
212
+
213
+ // Combined detection and processing
214
+ async function detectAndProcess() {
215
+ // Detect phone and get results
216
+ const detections = await detectPhoneAndGetBoxes();
217
+
218
+ // Process state machine
219
+ const phoneInFrame = detections.length > 0;
220
+
221
+ // Update consecutive counters
222
+ if (phoneInFrame) {
223
+ consecutivePhone++;
224
+ consecutiveNoPhone = 0;
225
+ } else {
226
+ consecutiveNoPhone++;
227
+ }
228
+
229
+ // Check for phone pickup (3 frames)
230
+ if (consecutivePhone >= PICKUP_THRESHOLD && !phoneVisible) {
231
+ phoneVisible = true;
232
+ consecutiveNoPhone = 0;
233
+
234
+ const now = Date.now();
235
+ const cooldown = DEMO_COOLDOWN * 1000;
236
+
237
+ if (now - lastReactionTime >= cooldown) {
238
+ phoneCount++;
239
+ lastReactionTime = now;
240
+ handlePhonePickup();
241
+ }
242
+ }
243
+
244
+ // Check for periodic reaction while STILL holding phone
245
+ if (phoneVisible && phoneInFrame) {
246
+ const now = Date.now();
247
+ const cooldown = DEMO_COOLDOWN * 1000;
248
+
249
+ if (now - lastReactionTime >= cooldown) {
250
+ phoneCount++;
251
+ lastReactionTime = now;
252
+ handlePhonePickup();
253
+ }
254
+ }
255
+
256
+ // Check for putdown (15 frames)
257
+ if (consecutiveNoPhone >= PUTDOWN_THRESHOLD && phoneVisible) {
258
+ phoneVisible = false;
259
+ consecutivePhone = 0;
260
+ lastReactionTime = 0;
261
+
262
+ // No praise in demo (keeps it simple)
263
+ if (DEMO_PRAISE_ENABLED) {
264
+ handlePhonePutdown();
265
+ }
266
+ }
267
+
268
+ // Update status (only if still monitoring)
269
+ if (isMonitoring) {
270
+ if (phoneVisible) {
271
+ statusText.textContent = '📱 PHONE DETECTED!';
272
+ statusIndicator.className = 'status-dot detected';
273
+ } else {
274
+ statusText.textContent = '✅ Phone-free';
275
+ statusIndicator.className = 'status-dot monitoring';
276
+ }
277
+
278
+ // Draw (only when monitoring)
279
+ draw(detections);
280
+ }
281
+ }
282
+
283
+ // Detect phone and return detection boxes (like YOLO26-WebGPU)
284
+ async function detectPhoneAndGetBoxes() {
285
+ try {
286
+ // Resize for faster inference (trade accuracy for speed)
287
+ const targetWidth = 320; // Smaller = faster (try 320, 416, or 640)
288
+ const targetHeight = Math.round((targetWidth / offscreen.width) * offscreen.height);
289
+ console.log(`Detection resolution: ${targetWidth}x${targetHeight}`);
290
+
291
+ // Create smaller canvas for YOLO
292
+ const smallCanvas = document.createElement('canvas');
293
+ smallCanvas.width = targetWidth;
294
+ smallCanvas.height = targetHeight;
295
+ const smallCtx = smallCanvas.getContext('2d');
296
+
297
+ // Draw resized image
298
+ offscreenCtx.drawImage(video, 0, 0);
299
+ smallCtx.drawImage(offscreen, 0, 0, targetWidth, targetHeight);
300
+
301
+ const image = RawImage.fromCanvas(smallCanvas);
302
+
303
+ // Run YOLO detection
304
+ const inputs = await processor(image);
305
+ const output = await model(inputs);
306
+
307
+ // Process detections - YOLO26 format
308
+ const scores = output.logits.sigmoid().data;
309
+ const boxes = output.pred_boxes.data;
310
+
311
+ // Adaptive confidence: lower threshold when tracking existing phone
312
+ const confidenceThreshold = lastPhoneBox ? TRACKING_CONFIDENCE : DETECTION_CONFIDENCE;
313
+
314
+ // Collect new detections
315
+ const newDetections = [];
316
+ let bestPhone = null;
317
+ let bestScore = 0;
318
+
319
+ // Check 300 detections
320
+ for (let i = 0; i < 300; i++) {
321
+ let maxScore = 0, maxClass = 0;
322
+
323
+ // Find max class and score
324
+ for (let j = 0; j < 80; j++) {
325
+ const score = scores[i * 80 + j];
326
+ if (score > maxScore) {
327
+ maxScore = score;
328
+ maxClass = j;
329
+ }
330
+ }
331
+
332
+ // Check if it's a phone with adaptive confidence
333
+ if (maxClass === PHONE_CLASS_ID && maxScore >= confidenceThreshold) {
334
+ // Get box coordinates (cx, cy, w, h) - normalized 0-1
335
+ const cx = boxes[i * 4];
336
+ const cy = boxes[i * 4 + 1];
337
+ const w = boxes[i * 4 + 2];
338
+ const h = boxes[i * 4 + 3];
339
+
340
+ // Convert to x1, y1, x2, y2 and scale to original canvas size
341
+ const scaleX = canvas.width / targetWidth;
342
+ const scaleY = canvas.height / targetHeight;
343
+
344
+ const x1 = (cx - w / 2) * targetWidth * scaleX;
345
+ const y1 = (cy - h / 2) * targetHeight * scaleY;
346
+ const x2 = (cx + w / 2) * targetWidth * scaleX;
347
+ const y2 = (cy + h / 2) * targetHeight * scaleY;
348
+
349
+ const detection = {
350
+ x1, y1, x2, y2,
351
+ confidence: maxScore,
352
+ class: 'cell phone'
353
+ };
354
+
355
+ // Keep track of best detection
356
+ if (maxScore > bestScore) {
357
+ bestScore = maxScore;
358
+ bestPhone = detection;
359
+ }
360
+ }
361
+ }
362
+
363
+ // Tracking logic: smooth and persist
364
+ if (bestPhone) {
365
+ // Phone detected - update tracking
366
+ lastPhoneBox = bestPhone;
367
+ framesWithoutDetection = 0;
368
+ newDetections.push(bestPhone);
369
+ } if (lastPhoneBox && framesWithoutDetection < TRACKING_PERSIST_FRAMES) {
370
+ // No detection but still tracking - persist last known box
371
+ framesWithoutDetection++;
372
+ newDetections.push({
373
+ ...lastPhoneBox,
374
+ confidence: lastPhoneBox.confidence * 0.9 // Fade confidence
375
+ });
376
+ } else {
377
+ // Lost tracking completely
378
+ lastPhoneBox = null;
379
+ framesWithoutDetection = 0;
380
+ }
381
+
382
+ // Return detections array
383
+ return newDetections;
384
+
385
+ } catch (error) {
386
+ console.error('Detection error:', error);
387
+ return [];
388
+ }
389
+ }
390
+
391
+ // Draw (like YOLO26-WebGPU - clear and redraw every time)
392
+ function draw(detections) {
393
+ // Clear canvas
394
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
395
+
396
+ // Draw video
397
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
398
+
399
+ // Draw detection boxes
400
+ for (const det of detections) {
401
+ // Draw green box for phone
402
+ ctx.strokeStyle = '#00ff00';
403
+ ctx.lineWidth = 3;
404
+ ctx.strokeRect(det.x1, det.y1, det.x2 - det.x1, det.y2 - det.y1);
405
+
406
+ // Draw label
407
+ ctx.fillStyle = '#00ff00';
408
+ ctx.font = '16px Arial';
409
+ const text = `${det.class} ${(det.confidence * 100).toFixed(0)}%`;
410
+ ctx.fillText(text, det.x1, det.y1 - 10);
411
+ }
412
+ }
413
+
414
+ // Play Reachy emotion sound (Pure Reachy mode)
415
+ async function playReachyEmotion(emotionList) {
416
+ // Pick random emotion from list
417
+ const emotionName = emotionList[Math.floor(Math.random() * emotionList.length)];
418
+ const audioUrl = `${EMOTIONS_BASE_URL}/${emotionName}.wav`;
419
+
420
+ try {
421
+ const audio = new Audio(audioUrl);
422
+ await audio.play();
423
+ return emotionName;
424
+ } catch (error) {
425
+ console.warn(`Failed to play emotion ${emotionName}:`, error);
426
+ return null;
427
+ }
428
+ }
429
+
430
+ // Handle phone pickup - Pure Reachy mode
431
+ async function handlePhonePickup() {
432
+ // Change to KO/shame robot
433
+ robotSvg.setAttribute('data', 'reachy-mad.svg');
434
+ robotSvg.classList.add('shaking');
435
+
436
+ // Play random shame emotion sound
437
+ const emotionName = await playReachyEmotion(SHAME_EMOTIONS);
438
+
439
+ // Show which emotion played
440
+ if (emotionName) {
441
+ responseText.textContent = `😡 *${emotionName}*`;
442
+ }
443
+
444
+ // Return to normal after animation
445
+ setTimeout(() => {
446
+ robotSvg.classList.remove('shaking');
447
+ }, 2000);
448
+ }
449
+
450
+ // Handle phone putdown - Pure Reachy mode
451
+ async function handlePhonePutdown() {
452
+ // Trigger robot praise animation (stays happy)
453
+ robotSvg.classList.add('nodding');
454
+ robotSvg.setAttribute('data', 'reachy-happy.svg');
455
+
456
+ // Play random praise emotion sound
457
+ const emotionName = await playReachyEmotion(PRAISE_EMOTIONS);
458
+
459
+ // Show which emotion played
460
+ if (emotionName) {
461
+ responseText.textContent = `✨ *${emotionName}*`;
462
+ }
463
+
464
+ // Return to normal after animation
465
+ setTimeout(() => {
466
+ robotSvg.classList.remove('nodding');
467
+ }, 1500);
468
+ }
469
+
470
+ // Removed - stats not needed for demo
471
+
472
+ // Event handlers
473
+ cameraBtn.addEventListener('click', async () => {
474
+ if (!isRunning) {
475
+ // Open camera
476
+ await startCamera();
477
+ cameraIcon.textContent = '🎥';
478
+ cameraText.textContent = 'Close Camera';
479
+ startBtn.disabled = false;
480
+ isMonitoring = true;
481
+ btnIcon.textContent = '🛑';
482
+ btnText.textContent = 'Stop Monitoring';
483
+ statusIndicator.className = 'status-dot monitoring';
484
+
485
+ } else {
486
+ // Close camera
487
+ isMonitoring = false;
488
+ stopCamera();
489
+ cameraIcon.textContent = '📹';
490
+ cameraText.textContent = 'Open Camera';
491
+ startBtn.disabled = true;
492
+ btnIcon.textContent = '▶️';
493
+ btnText.textContent = 'Start Monitoring';
494
+ statusText.textContent = 'Camera closed';
495
+ statusIndicator.className = 'status-dot ready';
496
+ robotSvg.setAttribute('data', 'reachy-happy.svg');
497
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
498
+
499
+ // Reset tracking
500
+ phoneVisible = false;
501
+ consecutivePhone = 0;
502
+ consecutiveNoPhone = 0;
503
+ lastPhoneBox = null;
504
+ framesWithoutDetection = 0;
505
+ lastReactionTime = 0;
506
+ }
507
+ });
508
+
509
+ startBtn.addEventListener('click', async () => {
510
+ isMonitoring = !isMonitoring;
511
+ if (isMonitoring) {
512
+ btnIcon.textContent = '🛑';
513
+ btnText.textContent = 'Stop Monitoring';
514
+ statusIndicator.className = 'status-dot monitoring';
515
+
516
+ } else {
517
+ btnIcon.textContent = '▶️';
518
+ btnText.textContent = 'Start Monitoring';
519
+ statusText.textContent = 'Paused';
520
+ statusIndicator.className = 'status-dot ready';
521
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
522
+ robotSvg.setAttribute('data', 'reachy-happy.svg');
523
+
524
+ // Reset tracking
525
+ phoneVisible = false;
526
+ consecutivePhone = 0;
527
+ consecutiveNoPhone = 0;
528
+ lastPhoneBox = null;
529
+ framesWithoutDetection = 0;
530
+ lastReactionTime = 0;
531
+ }
532
+ });
533
+
534
+ // Initialize on load
535
+ init();
index.html CHANGED
@@ -20,6 +20,7 @@
20
  </div>
21
 
22
  <nav class="nav">
 
23
  <a href="#setup" class="nav-link">Quick Setup</a>
24
  <a href="#config" class="nav-link">Config</a>
25
  <a href="#personalities" class="nav-link">Personalities</a>
@@ -41,26 +42,17 @@
41
  <div class="hero-grid">
42
  <div class="hero-left">
43
  <div class="hero-label">Reachy Mini App</div>
44
- <h1 class="hero-title">Get off<br><span>your phone!</span></h1>
45
  <p class="hero-description">
46
- Your robot companion uses AI vision to catch you scrolling and shame you back to productivity with brutal and funny comments. Pure chaos, pure fun.
47
  </p>
48
  <div class="hero-tags">
49
  <span class="tag">⚡ Real-time Detection</span>
50
  <span class="tag">👾 LLM</span>
51
  <span class="tag">🔈 TTS</span>
52
  <span class="tag">😌 9 Personalities</span>
53
- <span class="tag">⚙️ Customizable</span>
54
- <span class="tag">🔒 100% Local Mode or API</span>
55
  </div>
56
-
57
- </div>
58
-
59
- <div class="hero-right">
60
- <div style="border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.3); margin-top: 5rem;">
61
- <img src="quick_demo.gif" alt="Judgy Reachy demo - phone detection in action" style="width: 100%; display: block;">
62
- </div>
63
-
64
  <div class="hero-tech" style="margin-top: 1.5rem;">
65
  <div class="hero-tech-stack">
66
  <span class="tech-pill">YOLO26n</span>
@@ -70,6 +62,82 @@
70
  <span class="tech-pill">ElevenLabs</span>
71
  </div>
72
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  </div>
74
  </div>
75
  </div>
@@ -84,22 +152,34 @@
84
  <div class="guide-card">
85
  <ol class="guide-list">
86
  <li>
87
- <strong>Start the app</strong> and wait for the camera to connect
88
  </li>
89
  <li>
90
- <strong>Choose a personality</strong> - all 9 work immediately
91
  </li>
92
  <li>
93
- <strong>Add API keys</strong> (optional) - From settings on the right top corner
94
- <a href="https://console.groq.com" target="_blank" class="api-link-inline">Groq →</a>
95
- <a href="https://elevenlabs.io" target="_blank" class="api-link-inline">ElevenLabs →</a>
 
 
 
 
 
 
 
 
 
 
96
  </li>
 
 
97
  </li>
98
  <li>
99
- <strong>Adjust settings</strong> (optional) - From settings on the right top corner (enable/disable praise response after you put your phone down or adjust cooldown time for next pickup)
100
  </li>
101
  <li>
102
- <strong>Customize voices</strong> (optional) - Add custom voice IDs per personality (setting inside personality card). Auto-fallback to default if unavailable.
103
  </li>
104
  </ol>
105
  </div>
@@ -157,8 +237,8 @@
157
  <h3>Pure Reachy</h3>
158
  <p>No speech, pure authentic Reachy emotions from Pollen Robotics library.</p>
159
  <div class="personality-tags">
160
- <span class="personality-tag">Default</span>
161
- <span class="personality-tag">No TTS</span>
162
  </div>
163
  </div>
164
 
@@ -178,8 +258,8 @@
178
  <h3>Sarcastic</h3>
179
  <p>Dripping with dry wit. Mock enthusiasm, feigned interest. Peak passive aggression.</p>
180
  <div class="personality-tags">
181
- <span class="personality-tag">Deadpan</span>
182
- <span class="personality-tag">Sardonic</span>
183
  </div>
184
  </div>
185
 
@@ -229,7 +309,7 @@
229
  <p>Impeccably polite yet quietly judgmental.</p>
230
  <div class="personality-tags">
231
  <span class="personality-tag">Formal</span>
232
- <span class="personality-tag">Passive-Aggressive</span>
233
  </div>
234
  </div>
235
 
@@ -327,41 +407,6 @@
327
  </div>
328
  </section>
329
 
330
- <!-- How It Works -->
331
- <section class="how-section">
332
- <div class="container">
333
- <div class="section-label">How It Works</div>
334
- <h2 class="section-title">Four Simple Steps</h2>
335
- <p class="section-subtitle">From detection to shame delivery</p>
336
-
337
- <div class="steps-flow">
338
- <div class="step-box">
339
- <div class="step-num">1</div>
340
- <h3>Camera Watch</h3>
341
- <p>Reachy's camera processes frames at 5 FPS for real-time monitoring.</p>
342
- </div>
343
-
344
- <div class="step-box">
345
- <div class="step-num">2</div>
346
- <h3>Phone Detected</h3>
347
- <p>YOLO26n spots your phone with 3-frame confirmation.</p>
348
- </div>
349
-
350
- <div class="step-box">
351
- <div class="step-num">3</div>
352
- <h3>Shame Delivered</h3>
353
- <p>Robot generates or picks a snarky comment and speaks it.</p>
354
- </div>
355
-
356
- <div class="step-box">
357
- <div class="step-num">4</div>
358
- <h3>Robot Reacts</h3>
359
- <p>Expressions escalate with repeat offenses. Put phone down for praise!</p>
360
- </div>
361
- </div>
362
- </div>
363
- </section>
364
-
365
  <!-- Footer -->
366
  <footer class="footer">
367
  <div class="container">
@@ -375,6 +420,9 @@
375
  <p class="footer-text">Open Source • Built for productivity 🤖</p>
376
  </div>
377
  </footer>
 
 
 
378
  </body>
379
 
380
  </html>
 
20
  </div>
21
 
22
  <nav class="nav">
23
+ <a href="#demo" class="nav-link">Try Demo</a>
24
  <a href="#setup" class="nav-link">Quick Setup</a>
25
  <a href="#config" class="nav-link">Config</a>
26
  <a href="#personalities" class="nav-link">Personalities</a>
 
42
  <div class="hero-grid">
43
  <div class="hero-left">
44
  <div class="hero-label">Reachy Mini App</div>
45
+ <h1 class="hero-title">Get off <span>your phone!</span></h1>
46
  <p class="hero-description">
47
+ Your robot companion catches you scrolling with AI vision. Shames you back to productivity. Brutal, funny, pure chaos.
48
  </p>
49
  <div class="hero-tags">
50
  <span class="tag">⚡ Real-time Detection</span>
51
  <span class="tag">👾 LLM</span>
52
  <span class="tag">🔈 TTS</span>
53
  <span class="tag">😌 9 Personalities</span>
54
+ <span class="tag">🔒 100% Local or API</span>
 
55
  </div>
 
 
 
 
 
 
 
 
56
  <div class="hero-tech" style="margin-top: 1.5rem;">
57
  <div class="hero-tech-stack">
58
  <span class="tech-pill">YOLO26n</span>
 
62
  <span class="tech-pill">ElevenLabs</span>
63
  </div>
64
  </div>
65
+
66
+ <div class="hero-cta">
67
+ <a href="#demo" class="cta-button">🎮 Try Demo in Browser</a>
68
+ <a href="#setup" class="cta-button-secondary">📥 Download for Robot</a>
69
+ </div>
70
+
71
+ </div>
72
+
73
+ <div class="hero-right">
74
+ <div style="border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.3); margin-top: 2rem; max-width: 600px; margin-left: auto; margin-right: auto;">
75
+ <img src="quick_demo.gif" alt="Judgy Reachy demo - phone detection in action" style="width: 100%; display: block;">
76
+ </div>
77
+
78
+ </div>
79
+ </div>
80
+
81
+ </div>
82
+ </section>
83
+
84
+ <!-- Try Demo Section -->
85
+ <section id="demo" class="demo-section">
86
+ <div class="container">
87
+ <h2 class="section-title">Interactive Browser Demo</h2>
88
+ <p class="section-subtitle">Just a quick teaser demo! If you haven't tried Reachy Mini yet, this is a fun way to get a taste of what it can do. Hope it makes you smile :) and sparks your curiosity to try the full real simulation/robot. For the complete experience with expressive robot animations, head movements, and all 9 personalities, download this app to your Reachy Mini robot!</p>
89
+ <div class="robot-label">Try the demo! Download for full robot experience and personalities with robot movements.</div>
90
+ <div class="demo-grid">
91
+ <!-- Camera with Robot PiP -->
92
+ <div class="camera-column">
93
+ <div class="video-wrapper">
94
+ <video id="webcam" autoplay playsinline muted></video>
95
+ <canvas id="canvas"></canvas>
96
+
97
+ <!-- Robot PiP Overlay -->
98
+ <div class="robot-pip">
99
+ <div class="robot-bg-pip">
100
+ <object type="image/svg+xml" data="reachy-happy.svg" class="robot-svg-pip" id="robot-svg">
101
+ Reachy Robot
102
+ </object>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- Status overlay -->
107
+ <div class="demo-status">
108
+ <span id="status-indicator" class="status-dot"></span>
109
+ <span id="status-text">Click Start to begin</span>
110
+ </div>
111
+
112
+ <!-- FPS counter -->
113
+ <div class="fps-counter">
114
+ <span id="fps">0</span> FPS
115
+ </div>
116
+
117
+ <!-- Loader overlay -->
118
+ <div id="loader" class="loader-overlay">
119
+ <div class="spinner"></div>
120
+ <p id="loader-text">Loading AI model...</p>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- Controls -->
125
+ <div class="demo-controls">
126
+ <button id="camera-btn" class="btn-demo btn-secondary">
127
+ <span id="camera-icon">📹</span>
128
+ <span id="camera-text">Open Camera</span>
129
+ </button>
130
+ <button id="start-btn" class="btn-demo btn-primary" disabled>
131
+ <span id="btn-icon">▶️</span>
132
+ <span id="btn-text">Start Detection</span>
133
+ </button>
134
+ </div>
135
+
136
+ <!-- Response Box (below camera) -->
137
+ <div id="response-box" class="response-box">
138
+ <div class="response-label">💬 Latest Emotion:</div>
139
+ <div id="response-text" class="response-text">Pick up your phone to hear Reachy react!</div>
140
+ </div>
141
  </div>
142
  </div>
143
  </div>
 
152
  <div class="guide-card">
153
  <ol class="guide-list">
154
  <li>
155
+ <strong>Install Reachy Mini SDK</strong> - Follow the <a href="https://github.com/pollen-robotics/reachy_mini/blob/main/docs/source/SDK/installation.md" target="_blank" class="setup-link">installation guide</a>
156
  </li>
157
  <li>
158
+ <strong>Install this app</strong> - Via <a href="https://github.com/pollen-robotics/reachy_mini/" target="_blank" class="setup-link">Reachy Mini SDK</a> or <a href="https://github.com/pollen-robotics/reachy-mini-desktop-app" target="_blank" class="setup-link">Desktop App</a>
159
  </li>
160
  <li>
161
+ <strong>Start Reachy daemon</strong> - See <a href="https://github.com/pollen-robotics/reachy_mini/blob/main/docs/source/SDK/quickstart.md" target="_blank" class="setup-link">quickstart guide</a> (simulation or real robot)
162
+ </li>
163
+ <li>
164
+ <strong>Launch & open UI</strong> - Start the app and visit <code>http://localhost:8042</code>
165
+ </li>
166
+ </ol>
167
+
168
+ <!-- Usage Guide -->
169
+ <h3 style="font-family: 'Space Grotesk', sans-serif; font-size: 1.5rem; font-weight: 700; margin: 3rem 0 1.5rem; color: var(--coral-light);">Using the App</h3>
170
+
171
+ <ol class="guide-list" style="counter-reset: step-counter;">
172
+ <li>
173
+ <strong>Choose a personality</strong> - All 9 work immediately with pre-written responses
174
  </li>
175
+ <li>
176
+ <strong>Add API keys</strong> (optional) - <a href="https://console.groq.com" target="_blank" class="setup-link">Groq</a> for LLM, <a href="https://elevenlabs.io" target="_blank" class="setup-link">ElevenLabs</a> for premium voices
177
  </li>
178
  <li>
179
+ <strong>Adjust settings</strong> (optional) - Cooldown time, enable/disable praise mode
180
  </li>
181
  <li>
182
+ <strong>Customize voices</strong> (optional) - Custom voice IDs per personality with auto-fallback
183
  </li>
184
  </ol>
185
  </div>
 
237
  <h3>Pure Reachy</h3>
238
  <p>No speech, pure authentic Reachy emotions from Pollen Robotics library.</p>
239
  <div class="personality-tags">
240
+ <span class="personality-tag">Cute</span>
241
+ <span class="personality-tag">Emotions</span>
242
  </div>
243
  </div>
244
 
 
258
  <h3>Sarcastic</h3>
259
  <p>Dripping with dry wit. Mock enthusiasm, feigned interest. Peak passive aggression.</p>
260
  <div class="personality-tags">
261
+ <span class="personality-tag">Passive-aggressive</span>
262
+ <span class="personality-tag">Ironic</span>
263
  </div>
264
  </div>
265
 
 
309
  <p>Impeccably polite yet quietly judgmental.</p>
310
  <div class="personality-tags">
311
  <span class="personality-tag">Formal</span>
312
+ <span class="personality-tag">Polite</span>
313
  </div>
314
  </div>
315
 
 
407
  </div>
408
  </section>
409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  <!-- Footer -->
411
  <footer class="footer">
412
  <div class="container">
 
420
  <p class="footer-text">Open Source • Built for productivity 🤖</p>
421
  </div>
422
  </footer>
423
+
424
+ <!-- Demo JavaScript -->
425
+ <script type="module" src="demo.js"></script>
426
  </body>
427
 
428
  </html>
reachy-happy.svg ADDED
reachy-mad.svg ADDED
style.css CHANGED
@@ -1,4 +1,4 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=DM+Sans:wght@400;500;600&display=swap');
2
 
3
  * {
4
  margin: 0;
@@ -34,7 +34,7 @@ html {
34
  }
35
 
36
  body {
37
- font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
38
  background: var(--bg-dark);
39
  color: var(--text-primary);
40
  line-height: 1.6;
@@ -99,7 +99,7 @@ body::after {
99
  display: flex;
100
  align-items: center;
101
  gap: 0.75rem;
102
- font-family: 'Space Grotesk', sans-serif;
103
  font-weight: 700;
104
  font-size: 1.15rem;
105
  color: var(--text-primary);
@@ -207,7 +207,7 @@ body::after {
207
  }
208
 
209
  .hero-title {
210
- font-family: 'Space Grotesk', sans-serif;
211
  font-size: clamp(3rem, 7vw, 4.5rem);
212
  font-weight: 700;
213
  line-height: 1.1;
@@ -279,13 +279,13 @@ body::after {
279
  }
280
 
281
  .tech-pill {
282
- padding: 0.5rem 0.5rem;
283
- background: linear-gradient(135deg, rgba(255, 107, 74, 0.1), rgba(155, 126, 232, 0.08));
284
- border: 1px solid rgba(255, 107, 74, 0.3);
285
  border-radius: 100px;
286
- font-size: 0.85rem;
287
- font-weight: 600;
288
- color: var(--coral-light);
289
  transition: all 0.3s ease;
290
  }
291
 
@@ -316,7 +316,7 @@ section {
316
  }
317
 
318
  .section-title {
319
- font-family: 'Space Grotesk', sans-serif;
320
  font-size: clamp(2rem, 5vw, 3rem);
321
  font-weight: 700;
322
  text-align: center;
@@ -329,7 +329,7 @@ section {
329
  font-size: 1.1rem;
330
  text-align: center;
331
  color: var(--text-muted);
332
- margin-bottom: 3.5rem;
333
  max-width: 600px;
334
  margin-left: auto;
335
  margin-right: auto;
@@ -355,7 +355,7 @@ section {
355
  }
356
 
357
  .callout-title {
358
- font-family: 'Space Grotesk', sans-serif;
359
  font-size: 1.75rem;
360
  font-weight: 700;
361
  margin-bottom: 1.25rem;
@@ -438,7 +438,7 @@ section {
438
  }
439
 
440
  .config-card h3 {
441
- font-family: 'Space Grotesk', sans-serif;
442
  font-size: 1.5rem;
443
  font-weight: 700;
444
  margin-bottom: 0.75rem;
@@ -508,8 +508,8 @@ section {
508
 
509
  .personalities-grid {
510
  display: grid;
511
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
512
- gap: 1rem;
513
  }
514
 
515
  .personality-card {
@@ -550,7 +550,7 @@ section {
550
  .personality-card:nth-child(9) .personality-emoji { animation-delay: 1.6s; }
551
 
552
  .personality-card h3 {
553
- font-family: 'Space Grotesk', sans-serif;
554
  font-size: 1.1rem;
555
  font-weight: 700;
556
  margin-bottom: 0.5rem;
@@ -646,7 +646,7 @@ section {
646
  }
647
 
648
  .feature-item h3 {
649
- font-family: 'Space Grotesk', sans-serif;
650
  font-size: 1.25rem;
651
  font-weight: 700;
652
  margin-bottom: 0.75rem;
@@ -703,7 +703,7 @@ section {
703
  display: flex;
704
  align-items: center;
705
  justify-content: center;
706
- font-family: 'Space Grotesk', sans-serif;
707
  font-weight: 700;
708
  font-size: 1rem;
709
  color: white;
@@ -751,101 +751,6 @@ section {
751
  color: var(--coral);
752
  }
753
 
754
- /* How It Works */
755
- .how-section {
756
- text-align: center;
757
- }
758
-
759
- .steps-flow {
760
- display: grid;
761
- grid-template-columns: repeat(4, 1fr);
762
- gap: 0;
763
- max-width: 1000px;
764
- margin: 0 auto;
765
- position: relative;
766
- }
767
-
768
- .steps-flow::before {
769
- content: '';
770
- position: absolute;
771
- top: 50px;
772
- left: 10%;
773
- right: 10%;
774
- height: 3px;
775
- background: linear-gradient(90deg, var(--coral), var(--lavender), var(--sky), var(--mint));
776
- border-radius: 100px;
777
- z-index: 0;
778
- }
779
-
780
- .step-box {
781
- background: var(--bg-card);
782
- border: 2px solid var(--border-soft);
783
- border-radius: var(--radius-lg);
784
- padding: 1.75rem;
785
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
786
- box-shadow: var(--shadow-sm);
787
- position: relative;
788
- z-index: 1;
789
- margin: 0 0.5rem;
790
- }
791
-
792
- .step-box:hover {
793
- border-color: var(--coral);
794
- box-shadow: var(--shadow-md);
795
- transform: translateY(-4px);
796
- }
797
-
798
- .step-num {
799
- width: 44px;
800
- height: 44px;
801
- background: linear-gradient(135deg, var(--coral), var(--coral-light));
802
- border-radius: 50%;
803
- display: flex;
804
- align-items: center;
805
- justify-content: center;
806
- font-family: 'Space Grotesk', sans-serif;
807
- font-weight: 700;
808
- font-size: 1.2rem;
809
- margin: 0 auto 1.25rem;
810
- color: white;
811
- box-shadow: 0 4px 12px rgba(255, 107, 74, 0.35);
812
- }
813
-
814
- .step-box:nth-child(2) .step-num {
815
- background: linear-gradient(135deg, var(--lavender), #8B6BD8);
816
- box-shadow: 0 4px 12px rgba(155, 126, 232, 0.35);
817
- }
818
-
819
- .step-box:nth-child(3) .step-num {
820
- background: linear-gradient(135deg, var(--sky), #3EBDB4);
821
- box-shadow: 0 4px 12px rgba(78, 205, 196, 0.35);
822
- }
823
-
824
- .step-box:nth-child(4) .step-num {
825
- background: linear-gradient(135deg, var(--mint), #5AB868);
826
- box-shadow: 0 4px 12px rgba(107, 203, 119, 0.35);
827
- }
828
-
829
- .step-arrow {
830
- display: none;
831
- }
832
-
833
- .step-box h3 {
834
- font-family: 'Space Grotesk', sans-serif;
835
- font-size: 1rem;
836
- font-weight: 700;
837
- margin-bottom: 0.6rem;
838
- color: var(--text-primary);
839
- }
840
-
841
- .step-box p {
842
- font-size: 0.85rem;
843
- color: var(--text-muted);
844
- line-height: 1.6;
845
- }
846
-
847
- /* Tech Stack */
848
- .tech-section {
849
  text-align: center;
850
  }
851
 
@@ -882,7 +787,7 @@ section {
882
  }
883
 
884
  .tech-value {
885
- font-family: 'Space Grotesk', sans-serif;
886
  font-size: 0.95rem;
887
  font-weight: 600;
888
  color: var(--text-primary);
@@ -1045,3 +950,1224 @@ section {
1045
  font-size: 0.8rem;
1046
  }
1047
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&display=swap');
2
 
3
  * {
4
  margin: 0;
 
34
  }
35
 
36
  body {
37
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
38
  background: var(--bg-dark);
39
  color: var(--text-primary);
40
  line-height: 1.6;
 
99
  display: flex;
100
  align-items: center;
101
  gap: 0.75rem;
102
+ font-family: 'Inter', sans-serif;
103
  font-weight: 700;
104
  font-size: 1.15rem;
105
  color: var(--text-primary);
 
207
  }
208
 
209
  .hero-title {
210
+ font-family: 'Inter', sans-serif;
211
  font-size: clamp(3rem, 7vw, 4.5rem);
212
  font-weight: 700;
213
  line-height: 1.1;
 
279
  }
280
 
281
  .tech-pill {
282
+ padding: 0.4rem 0.75rem;
283
+ background: linear-gradient(135deg, rgba(255, 107, 74, 0.08), rgba(155, 126, 232, 0.05));
284
+ border: 1px solid rgba(255, 107, 74, 0.2);
285
  border-radius: 100px;
286
+ font-size: 0.75rem;
287
+ font-weight: 500;
288
+ color: rgba(255, 138, 110, 0.8);
289
  transition: all 0.3s ease;
290
  }
291
 
 
316
  }
317
 
318
  .section-title {
319
+ font-family: 'Inter', sans-serif;
320
  font-size: clamp(2rem, 5vw, 3rem);
321
  font-weight: 700;
322
  text-align: center;
 
329
  font-size: 1.1rem;
330
  text-align: center;
331
  color: var(--text-muted);
332
+ margin-bottom: 2rem;
333
  max-width: 600px;
334
  margin-left: auto;
335
  margin-right: auto;
 
355
  }
356
 
357
  .callout-title {
358
+ font-family: 'Inter', sans-serif;
359
  font-size: 1.75rem;
360
  font-weight: 700;
361
  margin-bottom: 1.25rem;
 
438
  }
439
 
440
  .config-card h3 {
441
+ font-family: 'Inter', sans-serif;
442
  font-size: 1.5rem;
443
  font-weight: 700;
444
  margin-bottom: 0.75rem;
 
508
 
509
  .personalities-grid {
510
  display: grid;
511
+ grid-template-columns: repeat(3, 1fr); /* Fixed 3 columns */
512
+ gap: 1.5rem;
513
  }
514
 
515
  .personality-card {
 
550
  .personality-card:nth-child(9) .personality-emoji { animation-delay: 1.6s; }
551
 
552
  .personality-card h3 {
553
+ font-family: 'Inter', sans-serif;
554
  font-size: 1.1rem;
555
  font-weight: 700;
556
  margin-bottom: 0.5rem;
 
646
  }
647
 
648
  .feature-item h3 {
649
+ font-family: 'Inter', sans-serif;
650
  font-size: 1.25rem;
651
  font-weight: 700;
652
  margin-bottom: 0.75rem;
 
703
  display: flex;
704
  align-items: center;
705
  justify-content: center;
706
+ font-family: 'Inter', sans-serif;
707
  font-weight: 700;
708
  font-size: 1rem;
709
  color: white;
 
751
  color: var(--coral);
752
  }
753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  text-align: center;
755
  }
756
 
 
787
  }
788
 
789
  .tech-value {
790
+ font-family: 'Inter', sans-serif;
791
  font-size: 0.95rem;
792
  font-weight: 600;
793
  color: var(--text-primary);
 
950
  font-size: 0.8rem;
951
  }
952
  }
953
+
954
+ /* ============================================
955
+ Demo Section Styles
956
+ ============================================ */
957
+
958
+ .demo-section {
959
+ padding: 5rem 2rem;
960
+ /* Background inherits from body for seamless blend */
961
+ }
962
+
963
+ .demo-grid {
964
+ display: flex;
965
+ justify-content: center;
966
+ align-items: center;
967
+ margin-top: 3rem;
968
+ }
969
+
970
+ @media (max-width: 968px) {
971
+ .demo-grid {
972
+ flex-direction: column;
973
+ align-items: center;
974
+ gap: 2rem;
975
+ }
976
+
977
+ .robot-column,
978
+ .camera-column {
979
+ width: 100% !important;
980
+ max-width: 100% !important;
981
+ min-width: 0 !important; /* Remove min-width constraint */
982
+ }
983
+
984
+ .video-wrapper {
985
+ width: 100%;
986
+ height: auto;
987
+ }
988
+
989
+ .robot-bg {
990
+ width: 260px;
991
+ height: 380px;
992
+ }
993
+
994
+ .demo-controls {
995
+ flex-direction: column;
996
+ }
997
+
998
+ .btn-demo {
999
+ width: 100%;
1000
+ min-width: auto;
1001
+ }
1002
+ }
1003
+
1004
+ @media (max-width: 640px) {
1005
+ .demo-section .section-subtitle {
1006
+ font-size: 0.95rem;
1007
+ max-width: 100%;
1008
+ }
1009
+
1010
+ .robot-bg {
1011
+ width: 220px;
1012
+ height: 320px;
1013
+ }
1014
+
1015
+ .demo-section .section-title {
1016
+ font-size: 1.5rem;
1017
+ }
1018
+ }
1019
+
1020
+ /* Video Container */
1021
+ .demo-video-container {
1022
+ display: flex;
1023
+ flex-direction: column;
1024
+ gap: 1.5rem;
1025
+ flex: 1; /* Take available space */
1026
+ max-width: 700px; /* Limit maximum width */
1027
+ }
1028
+
1029
+ .video-wrapper {
1030
+ position: relative;
1031
+ width: 100%;
1032
+ background: #1c0f2a;
1033
+ border-radius: 16px;
1034
+ overflow: hidden;
1035
+ aspect-ratio: 4/3;
1036
+ }
1037
+
1038
+ #webcam, #canvas {
1039
+ position: absolute;
1040
+ top: 0;
1041
+ left: 0;
1042
+ width: 100%;
1043
+ height: 100%;
1044
+ object-fit: cover;
1045
+ }
1046
+
1047
+ #canvas {
1048
+ z-index: 2;
1049
+ background: transparent; /* Don't block background color */
1050
+ }
1051
+
1052
+ .demo-status {
1053
+ position: absolute;
1054
+ top: 1rem;
1055
+ left: 1rem;
1056
+ background: rgba(0, 0, 0, 0.7);
1057
+ backdrop-filter: blur(8px);
1058
+ padding: 0.75rem 1.25rem;
1059
+ border-radius: 24px;
1060
+ display: flex;
1061
+ align-items: center;
1062
+ gap: 0.75rem;
1063
+ z-index: 3;
1064
+ color: #fff;
1065
+ font-weight: 600;
1066
+ font-size: 0.9rem;
1067
+ }
1068
+
1069
+ .status-dot {
1070
+ width: 10px;
1071
+ height: 10px;
1072
+ border-radius: 50%;
1073
+ background: #64748b;
1074
+ animation: pulse 2s ease-in-out infinite;
1075
+ }
1076
+
1077
+ .status-dot.ready {
1078
+ background: #22c55e;
1079
+ }
1080
+
1081
+ .status-dot.monitoring {
1082
+ background: #3b82f6;
1083
+ }
1084
+
1085
+ .status-dot.detected {
1086
+ background: #ef4444;
1087
+ }
1088
+
1089
+ .status-dot.loading {
1090
+ background: #f59e0b;
1091
+ }
1092
+
1093
+ .status-dot.error {
1094
+ background: #dc2626;
1095
+ }
1096
+
1097
+ @keyframes pulse {
1098
+ 0%, 100% { opacity: 1; }
1099
+ 50% { opacity: 0.5; }
1100
+ }
1101
+
1102
+ .fps-counter {
1103
+ position: absolute;
1104
+ top: 1rem;
1105
+ right: 1rem;
1106
+ background: rgba(0, 0, 0, 0.7);
1107
+ backdrop-filter: blur(8px);
1108
+ padding: 0.5rem 1rem;
1109
+ border-radius: 16px;
1110
+ z-index: 3;
1111
+ color: #22c55e;
1112
+ font-weight: 700;
1113
+ font-size: 0.85rem;
1114
+ }
1115
+
1116
+ /* Controls */
1117
+ .demo-controls {
1118
+ display: flex;
1119
+ gap: 1rem;
1120
+ flex-wrap: wrap;
1121
+ }
1122
+
1123
+ .btn-demo {
1124
+ flex: 1;
1125
+ min-width: 150px;
1126
+ padding: 1rem 1.5rem;
1127
+ border: none;
1128
+ border-radius: 12px;
1129
+ font-weight: 600;
1130
+ font-size: 1rem;
1131
+ cursor: pointer;
1132
+ transition: all 0.2s ease;
1133
+ display: flex;
1134
+ align-items: center;
1135
+ justify-content: center;
1136
+ gap: 0.5rem;
1137
+ }
1138
+
1139
+ .btn-demo:disabled {
1140
+ opacity: 0.5;
1141
+ cursor: not-allowed;
1142
+ }
1143
+
1144
+ .btn-primary {
1145
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1146
+ color: white;
1147
+ }
1148
+
1149
+ .btn-primary:hover:not(:disabled) {
1150
+ transform: translateY(-2px);
1151
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
1152
+ }
1153
+
1154
+ .btn-secondary {
1155
+ background: #1e293b;
1156
+ color: #f1f5f9;
1157
+ border: 2px solid #475569;
1158
+ }
1159
+
1160
+ .btn-secondary:hover:not(:disabled) {
1161
+ background: #334155;
1162
+ border-color: #64748b;
1163
+ }
1164
+
1165
+ .btn-ghost {
1166
+ background: transparent;
1167
+ color: #94a3b8;
1168
+ border: 2px solid #334155;
1169
+ }
1170
+
1171
+ .btn-ghost:hover:not(:disabled) {
1172
+ background: #1e293b;
1173
+ color: #f1f5f9;
1174
+ }
1175
+
1176
+ /* Response Box */
1177
+ .response-box {
1178
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
1179
+ border: 2px solid #475569;
1180
+ border-radius: 16px;
1181
+ padding: 1rem;
1182
+ min-height: 100px;
1183
+ }
1184
+
1185
+ .response-label {
1186
+ color: #94a3b8;
1187
+ font-size: 0.9rem;
1188
+ font-weight: 600;
1189
+ margin-bottom: 0.75rem;
1190
+ }
1191
+
1192
+ .response-text {
1193
+ color: #f1f5f9;
1194
+ font-size: 1.1rem;
1195
+ line-height: 1;
1196
+ font-style: italic;
1197
+ }
1198
+
1199
+ /* Sidebar */
1200
+ .demo-sidebar {
1201
+ display: flex;
1202
+ flex-direction: column;
1203
+ gap: 1.5rem;
1204
+ }
1205
+
1206
+ .demo-card {
1207
+ background: #1e293b;
1208
+ border: 2px solid #334155;
1209
+ border-radius: 16px;
1210
+ padding: 1.5rem;
1211
+ }
1212
+
1213
+ .demo-card-title {
1214
+ color: #f1f5f9;
1215
+ font-size: 1.1rem;
1216
+ font-weight: 700;
1217
+ margin-bottom: 1rem;
1218
+ }
1219
+
1220
+ /* Stats */
1221
+ .stat-grid {
1222
+ display: grid;
1223
+ gap: 0.75rem;
1224
+ }
1225
+
1226
+ .stat-item {
1227
+ display: flex;
1228
+ justify-content: space-between;
1229
+ align-items: center;
1230
+ padding: 0.75rem 0;
1231
+ border-bottom: 1px solid #334155;
1232
+ }
1233
+
1234
+ .stat-item:last-child {
1235
+ border-bottom: none;
1236
+ }
1237
+
1238
+ .stat-label {
1239
+ color: #94a3b8;
1240
+ font-size: 0.9rem;
1241
+ }
1242
+
1243
+ .stat-value {
1244
+ color: #f1f5f9;
1245
+ font-weight: 700;
1246
+ font-size: 1.1rem;
1247
+ }
1248
+
1249
+ /* Select */
1250
+ .demo-select {
1251
+ width: 100%;
1252
+ padding: 0.75rem 1rem;
1253
+ background: #0f172a;
1254
+ border: 2px solid #334155;
1255
+ border-radius: 8px;
1256
+ color: #f1f5f9;
1257
+ font-size: 1rem;
1258
+ cursor: pointer;
1259
+ transition: all 0.2s ease;
1260
+ }
1261
+
1262
+ .demo-select:hover, .demo-select:focus {
1263
+ border-color: #667eea;
1264
+ outline: none;
1265
+ }
1266
+
1267
+ /* Settings */
1268
+ .setting-item {
1269
+ margin-bottom: 1.5rem;
1270
+ }
1271
+
1272
+ .setting-item:last-child {
1273
+ margin-bottom: 0;
1274
+ }
1275
+
1276
+ .setting-label {
1277
+ display: block;
1278
+ color: #94a3b8;
1279
+ font-size: 0.9rem;
1280
+ margin-bottom: 0.5rem;
1281
+ }
1282
+
1283
+ .demo-slider {
1284
+ width: 100%;
1285
+ height: 6px;
1286
+ border-radius: 3px;
1287
+ background: #334155;
1288
+ outline: none;
1289
+ -webkit-appearance: none;
1290
+ }
1291
+
1292
+ .demo-slider::-webkit-slider-thumb {
1293
+ -webkit-appearance: none;
1294
+ appearance: none;
1295
+ width: 20px;
1296
+ height: 20px;
1297
+ border-radius: 50%;
1298
+ background: #667eea;
1299
+ cursor: pointer;
1300
+ transition: all 0.2s ease;
1301
+ }
1302
+
1303
+ .demo-slider::-webkit-slider-thumb:hover {
1304
+ background: #764ba2;
1305
+ transform: scale(1.1);
1306
+ }
1307
+
1308
+ .demo-slider::-moz-range-thumb {
1309
+ width: 20px;
1310
+ height: 20px;
1311
+ border-radius: 50%;
1312
+ background: #667eea;
1313
+ cursor: pointer;
1314
+ border: none;
1315
+ transition: all 0.2s ease;
1316
+ }
1317
+
1318
+ .demo-slider::-moz-range-thumb:hover {
1319
+ background: #764ba2;
1320
+ transform: scale(1.1);
1321
+ }
1322
+
1323
+ .setting-checkbox {
1324
+ display: flex;
1325
+ align-items: center;
1326
+ gap: 0.75rem;
1327
+ color: #f1f5f9;
1328
+ cursor: pointer;
1329
+ user-select: none;
1330
+ }
1331
+
1332
+ .setting-checkbox input[type="checkbox"] {
1333
+ width: 20px;
1334
+ height: 20px;
1335
+ cursor: pointer;
1336
+ accent-color: #667eea;
1337
+ }
1338
+
1339
+ /* Note */
1340
+ .demo-note {
1341
+ background: rgba(102, 126, 234, 0.1);
1342
+ border: 2px solid rgba(102, 126, 234, 0.3);
1343
+ border-radius: 12px;
1344
+ padding: 1rem;
1345
+ color: #cbd5e1;
1346
+ font-size: 0.85rem;
1347
+ line-height: 1.6;
1348
+ }
1349
+
1350
+ .demo-note strong {
1351
+ color: #f1f5f9;
1352
+ display: block;
1353
+ margin-bottom: 0.5rem;
1354
+ }
1355
+
1356
+ .demo-note p {
1357
+ margin: 0;
1358
+ }
1359
+
1360
+ /* API Keys Section */
1361
+ .api-note {
1362
+ color: #94a3b8;
1363
+ font-size: 0.85rem;
1364
+ margin-bottom: 1rem;
1365
+ line-height: 1.5;
1366
+ }
1367
+
1368
+ .demo-input {
1369
+ width: 100%;
1370
+ padding: 0.75rem 1rem;
1371
+ background: #0f172a;
1372
+ border: 2px solid #334155;
1373
+ border-radius: 8px;
1374
+ color: #f1f5f9;
1375
+ font-size: 0.9rem;
1376
+ transition: all 0.2s ease;
1377
+ }
1378
+
1379
+ .demo-input:hover,
1380
+ .demo-input:focus {
1381
+ border-color: #667eea;
1382
+ outline: none;
1383
+ }
1384
+
1385
+ .demo-input::placeholder {
1386
+ color: #475569;
1387
+ }
1388
+
1389
+ .api-link-inline {
1390
+ color: #667eea;
1391
+ text-decoration: none;
1392
+ font-size: 0.85rem;
1393
+ margin-left: 0.5rem;
1394
+ transition: color 0.2s ease;
1395
+ }
1396
+
1397
+ .api-link-inline:hover {
1398
+ color: #764ba2;
1399
+ text-decoration: underline;
1400
+ }
1401
+
1402
+ .mode-indicator {
1403
+ margin-top: 1rem;
1404
+ padding: 0.75rem;
1405
+ background: rgba(102, 126, 234, 0.1);
1406
+ border-radius: 8px;
1407
+ text-align: center;
1408
+ }
1409
+
1410
+ #mode-text {
1411
+ color: #cbd5e1;
1412
+ font-size: 0.85rem;
1413
+ font-weight: 600;
1414
+ }
1415
+
1416
+ /* Secondary Controls Row */
1417
+ .demo-controls-secondary {
1418
+ display: flex;
1419
+ gap: 1rem;
1420
+ flex-wrap: wrap;
1421
+ margin-top: 1rem;
1422
+ }
1423
+
1424
+ .demo-controls-secondary .btn-demo {
1425
+ flex: 1;
1426
+ min-width: 120px;
1427
+ }
1428
+
1429
+ /* Pure Reachy Mode Info */
1430
+ .reachy-description {
1431
+ color: #cbd5e1;
1432
+ font-size: 0.85rem;
1433
+ line-height: 1.6;
1434
+ margin: 0;
1435
+ }
1436
+
1437
+ /* Demo Download Button */
1438
+ .demo-download-btn {
1439
+ display: inline-block;
1440
+ margin-top: 1rem;
1441
+ padding: 0.75rem 1.5rem;
1442
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1443
+ color: white;
1444
+ text-decoration: none;
1445
+ border-radius: 8px;
1446
+ font-weight: 600;
1447
+ transition: all 0.2s ease;
1448
+ }
1449
+
1450
+ .demo-download-btn:hover {
1451
+ transform: translateY(-2px);
1452
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
1453
+ }
1454
+
1455
+ .demo-section .section-subtitle {
1456
+ max-width: 1500px;
1457
+ line-height: 1.8;
1458
+ }
1459
+
1460
+ .demo-section .section-title {
1461
+ background: linear-gradient(135deg, var(--coral) 0%, var(--coral-light) 100%);
1462
+ -webkit-background-clip: text;
1463
+ -webkit-text-fill-color: transparent;
1464
+ background-clip: text;
1465
+ margin-top: -4rem;
1466
+ }
1467
+
1468
+ /* Hero CTA Buttons */
1469
+ .hero-cta {
1470
+ display: flex;
1471
+ gap: 1rem;
1472
+ margin-top: 2rem;
1473
+ flex-wrap: wrap;
1474
+ }
1475
+
1476
+ .cta-button {
1477
+ display: inline-flex;
1478
+ align-items: center;
1479
+ gap: 0.5rem;
1480
+ padding: 0.5rem 1rem;
1481
+ background: linear-gradient(135deg, var(--coral) 0%, var(--coral-light) 100%);
1482
+ color: white;
1483
+ text-decoration: none;
1484
+ border-radius: 12px;
1485
+ font-weight: 700;
1486
+ font-size: 1.05rem;
1487
+ transition: all 0.3s ease;
1488
+ box-shadow: 0 4px 15px rgba(255, 107, 74, 0.3);
1489
+ }
1490
+
1491
+ .cta-button:hover {
1492
+ transform: translateY(-3px);
1493
+ box-shadow: 0 8px 25px rgba(255, 107, 74, 0.5);
1494
+ }
1495
+
1496
+ .cta-button-secondary {
1497
+ display: inline-flex;
1498
+ align-items: center;
1499
+ gap: 0.5rem;
1500
+ padding: 0.5rem 1rem;
1501
+ background: rgba(255, 255, 255, 0.1);
1502
+ border: 2px solid rgba(255, 255, 255, 0.3);
1503
+ color: white;
1504
+ text-decoration: none;
1505
+ border-radius: 12px;
1506
+ font-weight: 700;
1507
+ font-size: 1.05rem;
1508
+ transition: all 0.3s ease;
1509
+ }
1510
+
1511
+ .cta-button-secondary:hover {
1512
+ background: rgba(255, 255, 255, 0.15);
1513
+ border-color: rgba(255, 255, 255, 0.5);
1514
+ transform: translateY(-3px);
1515
+ }
1516
+
1517
+ /* Scroll Indicator */
1518
+ .scroll-indicator {
1519
+ position: absolute;
1520
+ bottom: 2rem;
1521
+ left: 50%;
1522
+ transform: translateX(-50%);
1523
+ z-index: 10;
1524
+ }
1525
+
1526
+ .scroll-arrow {
1527
+ display: flex;
1528
+ align-items: center;
1529
+ justify-content: center;
1530
+ width: 48px;
1531
+ height: 48px;
1532
+ border-radius: 50%;
1533
+ background: rgba(255, 255, 255, 0.1);
1534
+ border: 2px solid rgba(255, 255, 255, 0.3);
1535
+ color: white;
1536
+ text-decoration: none;
1537
+ transition: all 0.3s ease;
1538
+ animation: bounce 2s infinite;
1539
+ }
1540
+
1541
+ .scroll-arrow:hover {
1542
+ background: rgba(255, 255, 255, 0.2);
1543
+ border-color: rgba(255, 255, 255, 0.5);
1544
+ }
1545
+
1546
+ @keyframes bounce {
1547
+ 0%, 100% {
1548
+ transform: translateY(0);
1549
+ }
1550
+ 50% {
1551
+ transform: translateY(-10px);
1552
+ }
1553
+ }
1554
+
1555
+ /* Make hero section position relative for scroll indicator */
1556
+ .hero-section {
1557
+ position: relative;
1558
+ }
1559
+
1560
+ /* Wider container for hero section */
1561
+ .hero-section .container {
1562
+ max-width: 1450px; /* Wider for more spread out content */
1563
+ }
1564
+
1565
+ /* Robot Animation Container */
1566
+ .robot-container {
1567
+ display: flex;
1568
+ flex-direction: column;
1569
+ align-items: center;
1570
+ gap: 1rem;
1571
+ }
1572
+
1573
+ /* Green background container (stationary, vertical rectangle) */
1574
+ .robot-bg {
1575
+ background: #3DDE99; /* Reachy green */
1576
+ border-radius: 20px;
1577
+ box-shadow: 0 4px 15px rgba(61, 222, 153, 0.2);
1578
+ display: flex;
1579
+ align-items: center;
1580
+ justify-content: center;
1581
+ width: 250px;
1582
+ height: 300px;
1583
+ }
1584
+
1585
+ /* Robot SVG (animates inside bg) */
1586
+ .robot-svg {
1587
+ width: 150%;
1588
+ height: 150%;
1589
+ display: block;
1590
+ object-fit: cover; /* Fill the container */
1591
+ transform-origin: center center;
1592
+ margin: -25%; /* Re-center after zoom */
1593
+ animation: robot-idle 3s ease-in-out infinite;
1594
+ }
1595
+
1596
+ .robot-label {
1597
+ display: none; /* Hidden on desktop */
1598
+ color: #cbd5e1;
1599
+ font-size: 0.85rem;
1600
+ font-weight: 500;
1601
+ text-align: center;
1602
+ line-height: 1.5;
1603
+ margin-bottom: 2rem;
1604
+ }
1605
+
1606
+ @media (max-width: 640px) {
1607
+ .robot-label {
1608
+ display: block; /* Show on mobile only */
1609
+ }
1610
+ }
1611
+
1612
+ /* Robot Animations */
1613
+ @keyframes robot-idle {
1614
+ 0%, 100% {
1615
+ transform: translateY(0) scale(1);
1616
+ }
1617
+ 50% {
1618
+ transform: translateY(-8px) scale(1.02);
1619
+ }
1620
+ }
1621
+
1622
+ @keyframes robot-shame {
1623
+ 0%, 100% {
1624
+ transform: rotate(0deg) scale(1);
1625
+ }
1626
+ 15% {
1627
+ transform: rotate(-15deg) scale(0.95);
1628
+ }
1629
+ 30% {
1630
+ transform: rotate(15deg) scale(0.95);
1631
+ }
1632
+ 45% {
1633
+ transform: rotate(-15deg) scale(0.95);
1634
+ }
1635
+ 60% {
1636
+ transform: rotate(15deg) scale(0.95);
1637
+ }
1638
+ 75% {
1639
+ transform: rotate(-8deg) scale(0.95);
1640
+ }
1641
+ }
1642
+
1643
+ @keyframes robot-praise {
1644
+ 0%, 100% {
1645
+ transform: translateY(0) rotate(0deg) scale(1);
1646
+ }
1647
+ 25% {
1648
+ transform: translateY(-15px) rotate(0deg) scale(1.1);
1649
+ }
1650
+ 50% {
1651
+ transform: translateY(0) rotate(0deg) scale(1);
1652
+ }
1653
+ 75% {
1654
+ transform: translateY(-10px) rotate(0deg) scale(1.05);
1655
+ }
1656
+ }
1657
+
1658
+ /* Animation states */
1659
+ .robot-svg.shaking {
1660
+ animation: robot-shame 0.6s ease-in-out;
1661
+ }
1662
+
1663
+ .robot-svg.nodding {
1664
+ animation: robot-praise 0.8s ease-in-out;
1665
+ }
1666
+
1667
+ /* Two-column layout with equal heights */
1668
+ .robot-column,
1669
+ .camera-column {
1670
+ display: flex;
1671
+ flex-direction: column;
1672
+ gap: 1.5rem;
1673
+ }
1674
+
1675
+ .robot-column {
1676
+ flex: 0 0 auto; /* Fixed width based on content */
1677
+ }
1678
+
1679
+ .camera-column {
1680
+ flex: 1; /* Takes remaining space */
1681
+ max-width: 700px;
1682
+ min-width: 500px;
1683
+ }
1684
+
1685
+ /* Make columns match height */
1686
+ .demo-grid {
1687
+ align-items: stretch; /* Stretch to match height */
1688
+ }
1689
+
1690
+ /* Loader Overlay */
1691
+ .loader-overlay {
1692
+ position: absolute;
1693
+ top: 0;
1694
+ left: 0;
1695
+ width: 100%;
1696
+ height: 100%;
1697
+ background: rgba(0, 0, 0, 0.9);
1698
+ backdrop-filter: blur(8px);
1699
+ display: flex;
1700
+ flex-direction: column;
1701
+ align-items: center;
1702
+ justify-content: center;
1703
+ z-index: 10;
1704
+ opacity: 0;
1705
+ visibility: hidden;
1706
+ transition: opacity 0.3s ease, visibility 0.3s ease;
1707
+ }
1708
+
1709
+ .loader-overlay.visible {
1710
+ opacity: 1;
1711
+ visibility: visible;
1712
+ }
1713
+
1714
+ #loader-text {
1715
+ color: #f1f5f9;
1716
+ font-size: 1rem;
1717
+ font-weight: 600;
1718
+ margin-top: 1.5rem;
1719
+ text-align: center;
1720
+ }
1721
+
1722
+ /* Spinner Animation */
1723
+ .spinner {
1724
+ width: 50px;
1725
+ height: 50px;
1726
+ border: 4px solid rgba(255, 255, 255, 0.1);
1727
+ border-top-color: #667eea;
1728
+ border-radius: 50%;
1729
+ animation: spin 1s linear infinite;
1730
+ }
1731
+
1732
+ @keyframes spin {
1733
+ to {
1734
+ transform: rotate(360deg);
1735
+ }
1736
+ }
1737
+
1738
+ /* Additional mobile fixes */
1739
+ @media (max-width: 768px) {
1740
+ .demo-section {
1741
+ padding: 3rem 1rem;
1742
+ }
1743
+
1744
+ .hero-section {
1745
+ padding: 3rem 1.5rem;
1746
+ }
1747
+
1748
+ .hero-cta {
1749
+ flex-direction: column;
1750
+ width: 100%;
1751
+ margin-bottom: 0.5rem;
1752
+ margin-top: 2rem; /* Space above buttons */
1753
+ }
1754
+
1755
+ .hero-tech {
1756
+ margin-bottom: 1rem; /* Space after tech pills */
1757
+ }
1758
+
1759
+ .cta-button,
1760
+ .cta-button-secondary {
1761
+ width: 100%;
1762
+ justify-content: center;
1763
+ }
1764
+
1765
+ .hero-grid {
1766
+ gap: 1rem; /* Reduce gap between hero sections */
1767
+ }
1768
+ }
1769
+
1770
+ /* Robot Picture-in-Picture Overlay */
1771
+ .robot-pip {
1772
+ position: absolute;
1773
+ bottom: 1rem;
1774
+ right: 1rem;
1775
+ z-index: 5;
1776
+ pointer-events: none; /* Don't block clicks */
1777
+ }
1778
+
1779
+ .robot-bg-pip {
1780
+ background: transparent; /* No background */
1781
+ width: 180px;
1782
+ height: 240px;
1783
+ display: flex;
1784
+ align-items: center;
1785
+ justify-content: center;
1786
+ filter: drop-shadow(0 4px 15px rgba(0, 0, 0, 0.5)); /* Shadow for visibility */
1787
+ }
1788
+
1789
+ .robot-svg-pip {
1790
+ width: 150%;
1791
+ height: 150%;
1792
+ display: block;
1793
+ object-fit: cover;
1794
+ margin: -25%;
1795
+ animation: robot-idle 3s ease-in-out infinite;
1796
+ }
1797
+
1798
+ /* Robot PiP animations */
1799
+ .robot-svg-pip.shaking {
1800
+ animation: robot-shame 0.6s ease-in-out;
1801
+ }
1802
+
1803
+ .robot-svg-pip.nodding {
1804
+ animation: robot-praise 0.8s ease-in-out;
1805
+ }
1806
+
1807
+ /* Mobile: Smaller robot PiP */
1808
+ @media (max-width: 640px) {
1809
+ .robot-bg-pip {
1810
+ width: 100px;
1811
+ height: 140px;
1812
+ }
1813
+
1814
+ .robot-pip {
1815
+ bottom: 0.5rem;
1816
+ right: 0.5rem;
1817
+ }
1818
+ }
1819
+
1820
+ /* Mobile: Smaller tag and move content up */
1821
+ @media (max-width: 640px) {
1822
+ .hero-label {
1823
+ font-size: 0.65rem;
1824
+ padding: 0.35rem 0.75rem 0.35rem 0.5rem;
1825
+ margin-bottom: 1rem;
1826
+ }
1827
+
1828
+ .hero-label::before {
1829
+ font-size: 0.9rem;
1830
+ }
1831
+
1832
+ .hero-section {
1833
+ padding: 2rem 0 2rem; /* Move up - less top padding */
1834
+ }
1835
+
1836
+ .hero-title {
1837
+ font-size: 2.25rem;
1838
+ margin-bottom: 1rem;
1839
+ }
1840
+
1841
+ .hero-description {
1842
+ font-size: 1rem;
1843
+ margin-bottom: 1.5rem;
1844
+ }
1845
+ }
1846
+
1847
+ /* Mobile: Smaller status and move hero up */
1848
+ @media (max-width: 640px) {
1849
+ .demo-section .section-subtitle {
1850
+ display: none; /* Hide long text on mobile */
1851
+ }
1852
+
1853
+ .demo-status {
1854
+ padding: 0.4rem 0.7rem;
1855
+ font-size: 0.65rem;
1856
+ gap: 0.4rem;
1857
+ }
1858
+
1859
+ .status-dot {
1860
+ width: 6px;
1861
+ height: 6px;
1862
+ }
1863
+
1864
+ #status-text {
1865
+ font-size: 0.65rem;
1866
+ }
1867
+
1868
+ .fps-counter {
1869
+ padding: 0.3rem 0.6rem;
1870
+ font-size: 0.6rem;
1871
+ }
1872
+ }
1873
+
1874
+ /* Mobile-specific subtitle */
1875
+ .mobile-subtitle {
1876
+ display: none; /* Hidden on desktop */
1877
+ }
1878
+
1879
+ @media (max-width: 640px) {
1880
+ .mobile-subtitle {
1881
+ display: block; /* Show on mobile */
1882
+ color: #cbd5e1;
1883
+ font-size: 0.85rem;
1884
+ line-height: 1.5;
1885
+ text-align: center;
1886
+ margin-bottom: 2rem;
1887
+ padding: 0 1rem;
1888
+ }
1889
+ }
1890
+
1891
+ /* Mobile: Tighter hero spacing and GIF closer to top */
1892
+ @media (max-width: 640px) {
1893
+ .hero-right > div {
1894
+ margin-top: 1rem !important; /* Bring GIF up (was 2rem) */
1895
+ }
1896
+
1897
+ .hero-left {
1898
+ margin-bottom: 1.5rem; /* Small gap before GIF */
1899
+ }
1900
+ }
1901
+
1902
+ /* Mobile: More space between GIF and demo title */
1903
+ @media (max-width: 640px) {
1904
+ .demo-section {
1905
+ padding-top: 0.2rem; /* More space above demo section */
1906
+ }
1907
+
1908
+ .demo-section .section-title {
1909
+ margin-top: 1rem;
1910
+ }
1911
+ }
1912
+
1913
+ /* Mobile: Tighter demo section spacing */
1914
+ @media (max-width: 640px) {
1915
+ .demo-section .section-title {
1916
+ margin-bottom: 0.5rem; /* Minimal space after title */
1917
+ margin-top: 0;
1918
+ }
1919
+
1920
+ .robot-label {
1921
+ margin-bottom: 0.75rem; /* Minimal space before demo grid */
1922
+ margin-top: 0;
1923
+ }
1924
+
1925
+ .demo-grid {
1926
+ margin-top: 0; /* Remove top margin */
1927
+ }
1928
+ }
1929
+
1930
+ /* Hide Quick Setup on mobile */
1931
+ @media (max-width: 640px) {
1932
+ .getting-started-section {
1933
+ display: none;
1934
+ }
1935
+ }
1936
+
1937
+ /* Setup guide links */
1938
+ .setup-link {
1939
+ color: var(--coral-light);
1940
+ text-decoration: none;
1941
+ border-bottom: 1px solid var(--coral-light);
1942
+ transition: all 0.2s ease;
1943
+ white-space: nowrap;
1944
+ }
1945
+
1946
+ .setup-link:hover {
1947
+ color: var(--coral);
1948
+ border-bottom-color: var(--coral);
1949
+ }
1950
+
1951
+ /* Override: Ultra-simple links, no fancy styling */
1952
+ .guide-list a {
1953
+ color: var(--coral-light) !important;
1954
+ text-decoration: none !important;
1955
+ border: none !important;
1956
+ padding: 0 !important;
1957
+ margin: 0 !important;
1958
+ display: inline !important;
1959
+ background: none !important;
1960
+ }
1961
+
1962
+ .guide-list a:hover {
1963
+ text-decoration: underline !important;
1964
+ }
1965
+
1966
+
1967
+ /* Inter Font Weight Hierarchy */
1968
+ body {
1969
+ font-weight: 400; /* Regular for body text */
1970
+ }
1971
+
1972
+ h1, h2, h3 {
1973
+ font-weight: 700; /* Bold for main headings */
1974
+ }
1975
+
1976
+ h4, h5, h6, strong {
1977
+ font-weight: 600; /* Semibold */
1978
+ }
1979
+
1980
+ .section-subtitle, .text-muted {
1981
+ font-weight: 300; /* Light for subtle text */
1982
+ }
1983
+
1984
+ .btn-demo {
1985
+ font-weight: 500; /* Medium for buttons */
1986
+ }
1987
+
1988
+ /* Mobile: Smaller 3-column personalities */
1989
+ @media (max-width: 640px) {
1990
+ .personalities-grid {
1991
+ grid-template-columns: repeat(3, 1fr);
1992
+ gap: 0.25rem; /* Tighter spacing */
1993
+ }
1994
+
1995
+ .personality-card {
1996
+ padding: 0.5rem 0.25rem; /* Minimal padding */
1997
+ border: none;
1998
+ background: transparent;
1999
+ box-shadow: none;
2000
+ }
2001
+
2002
+ .personality-card:hover {
2003
+ transform: none;
2004
+ box-shadow: none;
2005
+ }
2006
+
2007
+ .personality-emoji {
2008
+ font-size: 1.5rem;
2009
+ margin-bottom: 0.25rem;
2010
+ }
2011
+
2012
+ .personality-card h3 {
2013
+ font-size: 0.75rem;
2014
+ margin-bottom: 0.25rem;
2015
+ }
2016
+
2017
+ .personality-card p {
2018
+ display: none; /* Hide description on mobile */
2019
+ }
2020
+
2021
+ .personality-tags {
2022
+ display: none; /* Hide tags to save space */
2023
+ }
2024
+ }
2025
+
2026
+ /* Mobile: Compact features (3 columns) */
2027
+ @media (max-width: 640px) {
2028
+ .features-grid {
2029
+ grid-template-columns: repeat(3, 1fr);
2030
+ gap: 0.25rem;
2031
+ }
2032
+
2033
+ .feature-item {
2034
+ padding: 0.75rem 0.5rem;
2035
+ border: none;
2036
+ background: transparent;
2037
+ box-shadow: none;
2038
+ }
2039
+
2040
+ .feature-item:hover {
2041
+ transform: none;
2042
+ }
2043
+
2044
+ .feature-icon-badge {
2045
+ width: 36px;
2046
+ height: 36px;
2047
+ margin-bottom: 0.5rem;
2048
+ border-radius: 8px;
2049
+ }
2050
+
2051
+ .feature-icon-badge svg {
2052
+ width: 16px;
2053
+ height: 16px;
2054
+ }
2055
+
2056
+ .feature-item h3 {
2057
+ font-size: 0.75rem;
2058
+ margin-bottom: 0.25rem;
2059
+ }
2060
+
2061
+ .feature-item p {
2062
+ display: none; /* Hide description */
2063
+ }
2064
+ }
2065
+
2066
+ /* Mobile: Compact steps (2x2 grid for 4 steps) */
2067
+ @media (max-width: 640px) {
2068
+ .steps-flow {
2069
+ grid-template-columns: repeat(2, 1fr);
2070
+ gap: 0.5rem;
2071
+ }
2072
+
2073
+ .step-box {
2074
+ padding: 1rem 0.75rem;
2075
+ margin: 0;
2076
+ border: none;
2077
+ background: transparent;
2078
+ box-shadow: none;
2079
+ }
2080
+
2081
+ .step-box:hover {
2082
+ transform: none;
2083
+ }
2084
+
2085
+ .step-num {
2086
+ width: 32px;
2087
+ height: 32px;
2088
+ font-size: 0.9rem;
2089
+ margin-bottom: 0.5rem;
2090
+ }
2091
+
2092
+ .step-box h3 {
2093
+ font-size: 0.75rem;
2094
+ margin-bottom: 0.25rem;
2095
+ }
2096
+
2097
+ .step-box p {
2098
+ font-size: 0.65rem;
2099
+ }
2100
+ }
2101
+
2102
+ /* Mobile: Prevent all overflow */
2103
+ @media (max-width: 640px) {
2104
+ * {
2105
+ max-width: 100% !important;
2106
+ overflow-wrap: break-word !important;
2107
+ word-wrap: break-word !important;
2108
+ }
2109
+
2110
+ .container {
2111
+ padding: 0 0.75rem !important;
2112
+ }
2113
+
2114
+ body {
2115
+ overflow-x: hidden !important;
2116
+ }
2117
+
2118
+ /* Force sections to fit */
2119
+ section {
2120
+ padding-left: 1rem !important;
2121
+ padding-right: 1rem !important;
2122
+ }
2123
+ }
2124
+
2125
+ /* Mobile: Hide detailed sections (focus on demo) */
2126
+ @media (max-width: 640px) {
2127
+ .config-section,
2128
+ .features-section,
2129
+ .how-section,
2130
+ .personalities-section {
2131
+ display: none;
2132
+ }
2133
+ }
2134
+
2135
+ /* Mobile: Smaller, organized hero tags */
2136
+ @media (max-width: 640px) {
2137
+ .hero-tags {
2138
+ gap: 0.4rem;
2139
+ justify-content: center;
2140
+ }
2141
+
2142
+ .tag {
2143
+ padding: 0.35rem 0.65rem;
2144
+ font-size: 0.7rem;
2145
+ border-width: 1px;
2146
+ }
2147
+
2148
+ .hero-tech-stack {
2149
+ gap: 0.35rem;
2150
+ justify-content: center;
2151
+ }
2152
+
2153
+ .tech-pill {
2154
+ padding: 0.3rem 0.55rem;
2155
+ font-size: 0.65rem;
2156
+ border-width: 1px;
2157
+ }
2158
+ }
2159
+
2160
+ /* Mobile: Smaller response box text */
2161
+ @media (max-width: 640px) {
2162
+ .response-label {
2163
+ font-size: 0.75rem;
2164
+ }
2165
+
2166
+ .response-text {
2167
+ font-size: 0.85rem;
2168
+ }
2169
+
2170
+ .response-box {
2171
+ padding: 1rem;
2172
+ }
2173
+ }