@livelayer/react 0.1.0 → 0.2.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.
@@ -0,0 +1,1199 @@
1
+ /* ─────────────────────────────────────────────────────────────────────
2
+ * @livelayer/react — widget stylesheet
3
+ *
4
+ * Scoped under .ll-widget so CSS custom properties don't pollute :root.
5
+ * No Tailwind, no CSS-in-JS runtime. Just vanilla CSS. Import once in
6
+ * your app:
7
+ *
8
+ * import "@livelayer/react/styles.css";
9
+ *
10
+ * Consumer-overridable tokens (set via `branding` prop → inline vars on
11
+ * .ll-widget, or in your own CSS targeting .ll-widget):
12
+ * --ll-color-primary CTA / focus color (default: #0D0D0D)
13
+ * --ll-color-accent Highlight / waveform (default: #8B7355)
14
+ * --ll-color-bg Widget background (default: #FFFFFF)
15
+ * --ll-color-fg Primary text (default: #0D0D0D)
16
+ * --ll-color-muted Secondary text (default: #808080)
17
+ * --ll-color-border Border / divider (default: #E6E6E6)
18
+ * --ll-color-danger Destructive / End conversation (default: #EF4444)
19
+ * --ll-color-listening Listening dot (default: #22C55E)
20
+ * --ll-color-thinking Thinking dot (default: #F59E0B)
21
+ * --ll-color-speaking Speaking dot (default: #22C55E)
22
+ * --ll-color-idle Idle dot (default: #808080)
23
+ * ───────────────────────────────────────────────────────────────────── */
24
+
25
+ .ll-widget {
26
+ --ll-color-primary: #0d0d0d;
27
+ --ll-color-accent: #8b7355;
28
+ --ll-color-bg: #ffffff;
29
+ --ll-color-fg: #0d0d0d;
30
+ --ll-color-muted: #808080;
31
+ --ll-color-border: #e6e6e6;
32
+ --ll-color-border-subtle: #f1f1f1;
33
+ --ll-color-danger: #ef4444;
34
+ --ll-color-listening: #22c55e;
35
+ --ll-color-thinking: #f59e0b;
36
+ --ll-color-speaking: #22c55e;
37
+ --ll-color-idle: #808080;
38
+ --ll-color-chrome-bg: rgba(13, 13, 13, 0.85);
39
+ --ll-color-chrome-fg: rgba(255, 255, 255, 0.95);
40
+ --ll-color-chrome-fg-muted: rgba(255, 255, 255, 0.6);
41
+
42
+ --ll-radius-panel: 20px;
43
+ --ll-radius-card: 12px;
44
+ --ll-radius-pill: 999px;
45
+
46
+ --ll-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Geist", Roboto,
47
+ "Helvetica Neue", Arial, sans-serif;
48
+ --ll-font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas,
49
+ monospace;
50
+
51
+ --ll-shadow-card: 0 10px 40px rgba(0, 0, 0, 0.12),
52
+ 0 2px 8px rgba(0, 0, 0, 0.06);
53
+ --ll-shadow-pill: 0 4px 12px rgba(0, 0, 0, 0.15);
54
+
55
+ --ll-transition-fast: 150ms ease;
56
+ --ll-transition-base: 200ms ease;
57
+
58
+ font-family: var(--ll-font);
59
+ color: var(--ll-color-fg);
60
+ box-sizing: border-box;
61
+ }
62
+
63
+ .ll-widget *,
64
+ .ll-widget *::before,
65
+ .ll-widget *::after {
66
+ box-sizing: border-box;
67
+ }
68
+
69
+ .ll-widget button {
70
+ font-family: inherit;
71
+ border: none;
72
+ background: none;
73
+ cursor: pointer;
74
+ padding: 0;
75
+ color: inherit;
76
+ }
77
+
78
+ .ll-widget button:disabled {
79
+ cursor: not-allowed;
80
+ }
81
+
82
+ /* Fixed root positioning. Safe-area insets honored. */
83
+ .ll-widget {
84
+ position: fixed;
85
+ pointer-events: none; /* children opt back in */
86
+ }
87
+
88
+ .ll-widget > * {
89
+ pointer-events: auto;
90
+ }
91
+
92
+ .ll-widget[data-position="bottom-right"] {
93
+ right: max(16px, env(safe-area-inset-right));
94
+ bottom: max(16px, env(safe-area-inset-bottom));
95
+ }
96
+ .ll-widget[data-position="bottom-left"] {
97
+ left: max(16px, env(safe-area-inset-left));
98
+ bottom: max(16px, env(safe-area-inset-bottom));
99
+ }
100
+ .ll-widget[data-position="top-right"] {
101
+ right: max(16px, env(safe-area-inset-right));
102
+ top: max(16px, env(safe-area-inset-top));
103
+ }
104
+ .ll-widget[data-position="top-left"] {
105
+ left: max(16px, env(safe-area-inset-left));
106
+ top: max(16px, env(safe-area-inset-top));
107
+ }
108
+ /* "custom" position relies on consumer-supplied style. */
109
+
110
+ /* Mobile: minimized + expanded ignore position, always full-width bottom. */
111
+ .ll-widget--mobile.ll-widget--minimized,
112
+ .ll-widget--mobile.ll-widget--expanded {
113
+ left: 0;
114
+ right: 0;
115
+ bottom: 0;
116
+ top: auto;
117
+ }
118
+
119
+ /* ── Hidden mode: edge chevron tab ────────────────────────────────── */
120
+ /* Solid-black tab docked to the screen edge with a white chevron.
121
+ Designed to be unmistakably visible on light AND dark pages — no
122
+ transparency, no backdrop-blur tricks. Drag vertically to
123
+ reposition; click to reopen. */
124
+
125
+ .ll-hidden {
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ width: 36px;
130
+ height: 72px;
131
+ background: #0d0d0d;
132
+ color: #fff;
133
+ border: 1px solid rgba(255, 255, 255, 0.18);
134
+ box-shadow: 0 8px 22px rgba(0, 0, 0, 0.45),
135
+ 0 2px 6px rgba(0, 0, 0, 0.3);
136
+ cursor: grab;
137
+ transition: background 0.18s ease, width 0.18s ease;
138
+ /* Re-anchor to edge (ignore .ll-widget's 16px corner inset). The
139
+ drag handler overrides `top` (and clears the transform) once a
140
+ position is loaded; until then this CSS-only centering keeps
141
+ SSR / first-paint sane. */
142
+ position: fixed;
143
+ top: 50%;
144
+ transform: translateY(-50%);
145
+ padding: 0;
146
+ touch-action: none;
147
+ user-select: none;
148
+ -webkit-user-select: none;
149
+ }
150
+
151
+ .ll-hidden:hover {
152
+ background: #1a1a1a;
153
+ width: 42px;
154
+ }
155
+
156
+ .ll-hidden.is-dragging {
157
+ cursor: grabbing;
158
+ transition: none;
159
+ background: #1a1a1a;
160
+ }
161
+
162
+ .ll-hidden--right {
163
+ right: 0;
164
+ border-right: none;
165
+ border-radius: 14px 0 0 14px;
166
+ }
167
+ .ll-hidden--left {
168
+ left: 0;
169
+ border-left: none;
170
+ border-radius: 0 14px 14px 0;
171
+ }
172
+
173
+ .ll-hidden--mobile {
174
+ width: 40px;
175
+ height: 80px;
176
+ }
177
+
178
+ .ll-hidden__chevron {
179
+ /* Centered visually inside the tab. The SVG's stroke handles color;
180
+ `display: block` prevents any baseline gap that would offset the
181
+ icon vertically. */
182
+ display: block;
183
+ width: 18px;
184
+ height: 18px;
185
+ stroke: #fff;
186
+ stroke-width: 2.25;
187
+ opacity: 1;
188
+ pointer-events: none;
189
+ }
190
+
191
+ .ll-hidden--speaking {
192
+ animation: ll-pulse 1.5s ease-in-out infinite;
193
+ }
194
+
195
+ @keyframes ll-pulse {
196
+ 0%, 100% {
197
+ box-shadow: var(--ll-shadow-pill);
198
+ }
199
+ 50% {
200
+ box-shadow: var(--ll-shadow-pill),
201
+ 0 0 0 6px rgba(34, 197, 94, 0.2);
202
+ }
203
+ }
204
+
205
+ /* ── Minimized mode ────────────────────────────────────────────── */
206
+
207
+ /* Desktop pill */
208
+ .ll-minimized--desktop .ll-minimized__surface {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 12px;
212
+ padding: 8px 12px;
213
+ background: var(--ll-color-chrome-bg);
214
+ color: var(--ll-color-chrome-fg);
215
+ border-radius: var(--ll-radius-pill);
216
+ box-shadow: var(--ll-shadow-pill);
217
+ min-width: 240px;
218
+ max-width: 320px;
219
+ }
220
+
221
+ .ll-minimized__avatar {
222
+ width: 40px;
223
+ height: 40px;
224
+ border-radius: 50%;
225
+ object-fit: cover;
226
+ flex-shrink: 0;
227
+ background: linear-gradient(180deg, #e8e4df 0%, #d4cfc8 100%);
228
+ }
229
+
230
+ .ll-minimized__avatar--placeholder {
231
+ /* avatar is null; show the gradient */
232
+ }
233
+
234
+ .ll-minimized__meta {
235
+ flex: 1;
236
+ min-width: 0;
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 2px;
240
+ }
241
+
242
+ .ll-minimized__name {
243
+ font-size: 13px;
244
+ font-weight: 500;
245
+ color: var(--ll-color-chrome-fg);
246
+ white-space: nowrap;
247
+ overflow: hidden;
248
+ text-overflow: ellipsis;
249
+ }
250
+
251
+ .ll-minimized__state {
252
+ font-size: 11px;
253
+ color: var(--ll-color-chrome-fg-muted);
254
+ }
255
+
256
+ .ll-minimized__controls {
257
+ display: flex;
258
+ gap: 4px;
259
+ align-items: center;
260
+ }
261
+
262
+ .ll-minimized__btn {
263
+ width: 32px;
264
+ height: 32px;
265
+ border-radius: 50%;
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ transition: background var(--ll-transition-fast);
270
+ color: var(--ll-color-chrome-fg);
271
+ }
272
+
273
+ .ll-minimized__btn:hover {
274
+ background: rgba(255, 255, 255, 0.1);
275
+ }
276
+
277
+ .ll-minimized__btn--close {
278
+ color: var(--ll-color-chrome-fg-muted);
279
+ }
280
+
281
+ .ll-minimized__icon {
282
+ width: 16px;
283
+ height: 16px;
284
+ }
285
+
286
+ /* Mobile dock */
287
+ .ll-minimized--mobile {
288
+ padding-bottom: env(safe-area-inset-bottom);
289
+ }
290
+
291
+ .ll-minimized--mobile .ll-minimized__surface {
292
+ display: flex;
293
+ align-items: center;
294
+ gap: 12px;
295
+ padding: 12px 16px;
296
+ background: var(--ll-color-chrome-bg);
297
+ color: var(--ll-color-chrome-fg);
298
+ width: 100%;
299
+ text-align: left;
300
+ }
301
+
302
+ .ll-minimized--mobile .ll-minimized__avatar {
303
+ width: 48px;
304
+ height: 48px;
305
+ }
306
+
307
+ .ll-minimized--mobile .ll-minimized__waveform {
308
+ flex: 1;
309
+ min-width: 0;
310
+ }
311
+
312
+ .ll-minimized--mobile .ll-minimized__name {
313
+ font-size: 14px;
314
+ }
315
+
316
+ .ll-minimized__icon--expand {
317
+ width: 20px;
318
+ height: 20px;
319
+ }
320
+
321
+ /* ── Waveform ─────────────────────────────────────────────────── */
322
+
323
+ .ll-waveform {
324
+ display: flex;
325
+ align-items: center;
326
+ gap: 2px;
327
+ height: 24px;
328
+ }
329
+
330
+ .ll-waveform__bar {
331
+ width: 3px;
332
+ min-height: 4px;
333
+ border-radius: 2px;
334
+ background: rgba(255, 255, 255, 0.7);
335
+ transition: height 75ms linear;
336
+ }
337
+
338
+ /* ── Agent state pill ─────────────────────────────────────────── */
339
+
340
+ .ll-pill {
341
+ display: inline-flex;
342
+ align-items: center;
343
+ gap: 6px;
344
+ padding: 4px 10px;
345
+ border-radius: var(--ll-radius-pill);
346
+ background: var(--ll-color-chrome-bg);
347
+ backdrop-filter: blur(8px);
348
+ -webkit-backdrop-filter: blur(8px);
349
+ color: var(--ll-color-chrome-fg);
350
+ font-size: 11px;
351
+ font-weight: 500;
352
+ line-height: 1;
353
+ }
354
+
355
+ .ll-pill__dot {
356
+ width: 8px;
357
+ height: 8px;
358
+ border-radius: 50%;
359
+ background: var(--ll-color-idle);
360
+ flex-shrink: 0;
361
+ }
362
+
363
+ .ll-pill--idle .ll-pill__dot {
364
+ background: var(--ll-color-idle);
365
+ }
366
+ .ll-pill--listening .ll-pill__dot {
367
+ background: var(--ll-color-listening);
368
+ }
369
+ .ll-pill--thinking .ll-pill__dot {
370
+ background: var(--ll-color-thinking);
371
+ animation: ll-dot-pulse 1s ease-in-out infinite;
372
+ }
373
+ .ll-pill--speaking .ll-pill__dot {
374
+ background: var(--ll-color-speaking);
375
+ animation: ll-dot-pulse 1.2s ease-in-out infinite;
376
+ }
377
+
378
+ @keyframes ll-dot-pulse {
379
+ 0%, 100% { opacity: 1; }
380
+ 50% { opacity: 0.5; }
381
+ }
382
+
383
+ /* ── Expanded mode ────────────────────────────────────────────────── */
384
+ /* Full-bleed avatar with glass pills overlaid on top. No card chrome. */
385
+
386
+ .ll-expanded {
387
+ position: relative;
388
+ overflow: hidden;
389
+ background: #1a1a1a;
390
+ color: #fff;
391
+ box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
392
+ font-family: inherit;
393
+ display: flex;
394
+ flex-direction: column;
395
+ }
396
+
397
+ .ll-expanded--desktop {
398
+ width: 400px;
399
+ height: 560px;
400
+ border-radius: 20px;
401
+ }
402
+
403
+ .ll-expanded--mobile {
404
+ width: 100vw;
405
+ height: 80dvh;
406
+ border-top-left-radius: 20px;
407
+ border-top-right-radius: 20px;
408
+ border-bottom-left-radius: 0;
409
+ border-bottom-right-radius: 0;
410
+ }
411
+
412
+ /* ── Background layers ───────────────────────────────────────── */
413
+
414
+ .ll-expanded__bg,
415
+ .ll-expanded__video {
416
+ position: absolute;
417
+ inset: 0;
418
+ }
419
+
420
+ .ll-expanded__bg { z-index: 0; }
421
+ .ll-expanded__video { z-index: 1; pointer-events: none; }
422
+
423
+ .ll-expanded__video > video {
424
+ width: 100%;
425
+ height: 100%;
426
+ object-fit: cover;
427
+ }
428
+
429
+ .ll-expanded__bg-img {
430
+ width: 100%;
431
+ height: 100%;
432
+ object-fit: cover;
433
+ transition: filter 0.3s ease;
434
+ }
435
+
436
+ /* Idle state: blur the avatar preview so the play button pops. Also
437
+ scale up slightly to hide the blurred edge bleed. Transitions off
438
+ smoothly when the session connects. */
439
+ .ll-expanded[data-state="idle"] .ll-expanded__bg-img {
440
+ filter: blur(10px);
441
+ transform: scale(1.08);
442
+ }
443
+
444
+ /* Darkening overlay sits on top of the bg in idle so the play button
445
+ and copy stay readable against any background color. Only rendered
446
+ when the avatar image is present (via sibling selector below). */
447
+ .ll-expanded[data-state="idle"] .ll-expanded__bg::after {
448
+ content: "";
449
+ position: absolute;
450
+ inset: 0;
451
+ background: linear-gradient(
452
+ 180deg,
453
+ rgba(0, 0, 0, 0.35) 0%,
454
+ rgba(0, 0, 0, 0.25) 45%,
455
+ rgba(0, 0, 0, 0.55) 100%
456
+ );
457
+ pointer-events: none;
458
+ }
459
+
460
+ .ll-expanded__bg-fallback {
461
+ width: 100%;
462
+ height: 100%;
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ background: linear-gradient(135deg, #2a2a2a, #1a1a1a);
467
+ }
468
+
469
+ .ll-expanded__bg-initial {
470
+ font-size: 64px;
471
+ font-weight: 600;
472
+ color: rgba(255, 255, 255, 0.3);
473
+ }
474
+
475
+ .ll-expanded__bg-idle {
476
+ position: absolute;
477
+ inset: 0;
478
+ width: 100%;
479
+ height: 100%;
480
+ object-fit: cover;
481
+ }
482
+
483
+ /* ── Overlays (connecting / gesture) ────────────────────────── */
484
+
485
+ .ll-expanded__overlay {
486
+ position: absolute;
487
+ inset: 0;
488
+ z-index: 4;
489
+ display: flex;
490
+ flex-direction: column;
491
+ align-items: center;
492
+ justify-content: center;
493
+ gap: 12px;
494
+ background: rgba(0, 0, 0, 0.35);
495
+ -webkit-backdrop-filter: blur(2px);
496
+ backdrop-filter: blur(2px);
497
+ border: none;
498
+ color: #fff;
499
+ padding: 0;
500
+ font-family: inherit;
501
+ }
502
+
503
+ .ll-expanded__overlay--gesture {
504
+ cursor: pointer;
505
+ background: rgba(0, 0, 0, 0.5);
506
+ }
507
+
508
+ .ll-expanded__overlay-text {
509
+ margin: 0;
510
+ font-size: 13px;
511
+ font-weight: 500;
512
+ color: rgba(255, 255, 255, 0.9);
513
+ letter-spacing: -0.15px;
514
+ }
515
+
516
+ .ll-expanded__spinner {
517
+ width: 32px;
518
+ height: 32px;
519
+ border: 2px solid rgba(255, 255, 255, 0.3);
520
+ border-top-color: #fff;
521
+ border-radius: 50%;
522
+ animation: ll-spin 0.9s linear infinite;
523
+ }
524
+
525
+ @keyframes ll-spin {
526
+ to { transform: rotate(360deg); }
527
+ }
528
+
529
+ /* ── Idle-state header (before connect) ─────────────────────── */
530
+
531
+ .ll-expanded__header {
532
+ position: absolute;
533
+ top: 0;
534
+ left: 0;
535
+ right: 0;
536
+ z-index: 3;
537
+ display: flex;
538
+ align-items: center;
539
+ justify-content: space-between;
540
+ padding: 14px 16px;
541
+ }
542
+
543
+ .ll-expanded__header--idle {
544
+ background: linear-gradient(
545
+ to bottom,
546
+ rgba(0, 0, 0, 0.55),
547
+ rgba(0, 0, 0, 0) 100%
548
+ );
549
+ }
550
+
551
+ .ll-expanded__brand {
552
+ font-size: 14px;
553
+ font-weight: 600;
554
+ letter-spacing: -0.2px;
555
+ color: #fff;
556
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
557
+ }
558
+
559
+ .ll-expanded__header-actions {
560
+ display: flex;
561
+ gap: 6px;
562
+ }
563
+
564
+ /* ── Active top bar (pills) ─────────────────────────────────── */
565
+
566
+ .ll-expanded__topbar {
567
+ position: absolute;
568
+ top: 12px;
569
+ left: 12px;
570
+ right: 12px;
571
+ z-index: 3;
572
+ display: flex;
573
+ align-items: center;
574
+ justify-content: space-between;
575
+ gap: 8px;
576
+ }
577
+
578
+ .ll-expanded__topbar-left {
579
+ display: flex;
580
+ align-items: center;
581
+ gap: 8px;
582
+ flex-wrap: wrap;
583
+ min-width: 0;
584
+ }
585
+
586
+ .ll-expanded__pill-wrap {
587
+ position: relative;
588
+ }
589
+
590
+ /* Glass pill (header dropdown trigger) */
591
+ .ll-hpill {
592
+ display: inline-flex;
593
+ align-items: center;
594
+ gap: 6px;
595
+ height: 40px;
596
+ padding: 0 18px;
597
+ border-radius: 999px;
598
+ border: 1px solid rgba(255, 255, 255, 0.16);
599
+ background: rgba(0, 0, 0, 0.4);
600
+ -webkit-backdrop-filter: blur(20px);
601
+ backdrop-filter: blur(20px);
602
+ color: #fff;
603
+ font-family: inherit;
604
+ font-size: 15px;
605
+ font-weight: 500;
606
+ letter-spacing: -0.15px;
607
+ cursor: pointer;
608
+ transition: background 0.15s ease;
609
+ }
610
+
611
+ .ll-hpill:hover {
612
+ background: rgba(0, 0, 0, 0.55);
613
+ }
614
+
615
+ .ll-hpill__label {
616
+ white-space: nowrap;
617
+ overflow: hidden;
618
+ text-overflow: ellipsis;
619
+ max-width: 160px;
620
+ }
621
+
622
+ /* Ghost icon button (close / minimize) */
623
+ .ll-hbtn {
624
+ display: inline-flex;
625
+ align-items: center;
626
+ justify-content: center;
627
+ width: 40px;
628
+ height: 40px;
629
+ border-radius: 999px;
630
+ border: 1px solid rgba(255, 255, 255, 0.16);
631
+ background: rgba(0, 0, 0, 0.4);
632
+ -webkit-backdrop-filter: blur(20px);
633
+ backdrop-filter: blur(20px);
634
+ color: #fff;
635
+ cursor: pointer;
636
+ transition: background 0.15s ease;
637
+ padding: 0;
638
+ }
639
+
640
+ .ll-hbtn:hover {
641
+ background: rgba(0, 0, 0, 0.65);
642
+ }
643
+
644
+ .ll-hbtn--ghost {
645
+ background: rgba(0, 0, 0, 0.28);
646
+ }
647
+
648
+ /* Dropdown menu under a pill */
649
+ .ll-hmenu {
650
+ position: absolute;
651
+ top: calc(100% + 6px);
652
+ left: 0;
653
+ z-index: 20;
654
+ min-width: 200px;
655
+ padding: 6px;
656
+ border-radius: 14px;
657
+ border: 1px solid rgba(255, 255, 255, 0.14);
658
+ background: rgba(26, 26, 26, 0.96);
659
+ -webkit-backdrop-filter: blur(24px);
660
+ backdrop-filter: blur(24px);
661
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
662
+ display: flex;
663
+ flex-direction: column;
664
+ gap: 2px;
665
+ }
666
+
667
+ .ll-hmenu__item {
668
+ display: flex;
669
+ align-items: center;
670
+ gap: 10px;
671
+ padding: 8px 10px;
672
+ border-radius: 10px;
673
+ border: none;
674
+ background: transparent;
675
+ color: rgba(255, 255, 255, 0.85);
676
+ font-family: inherit;
677
+ font-size: 13px;
678
+ font-weight: 500;
679
+ text-align: left;
680
+ cursor: pointer;
681
+ transition: background 0.12s ease;
682
+ }
683
+
684
+ .ll-hmenu__item:hover {
685
+ background: rgba(255, 255, 255, 0.08);
686
+ }
687
+
688
+ .ll-hmenu__item.is-active {
689
+ color: #fff;
690
+ background: rgba(255, 255, 255, 0.1);
691
+ }
692
+
693
+ .ll-hmenu__avatar {
694
+ width: 24px;
695
+ height: 24px;
696
+ border-radius: 999px;
697
+ object-fit: cover;
698
+ flex-shrink: 0;
699
+ }
700
+
701
+ .ll-hmenu__name {
702
+ flex: 1;
703
+ }
704
+
705
+ .ll-hmenu__role {
706
+ font-size: 11px;
707
+ font-weight: 500;
708
+ color: rgba(255, 255, 255, 0.5);
709
+ letter-spacing: -0.1px;
710
+ }
711
+
712
+ /* Agent state word */
713
+ .ll-expanded__state {
714
+ font-size: 11px;
715
+ font-weight: 500;
716
+ letter-spacing: -0.1px;
717
+ text-transform: lowercase;
718
+ color: rgba(255, 255, 255, 0.6);
719
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
720
+ }
721
+
722
+ .ll-expanded__state--speaking { color: #68f5ff; }
723
+ .ll-expanded__state--thinking { color: #ffcd68; }
724
+
725
+ /* ── Idle play button ─────────────────────────────────────── */
726
+
727
+ .ll-expanded__play {
728
+ position: absolute;
729
+ inset: 0;
730
+ z-index: 2;
731
+ display: flex;
732
+ flex-direction: column;
733
+ align-items: center;
734
+ justify-content: center;
735
+ gap: 8px;
736
+ background: rgba(0, 0, 0, 0);
737
+ border: none;
738
+ color: #fff;
739
+ cursor: pointer;
740
+ padding: 0;
741
+ font-family: inherit;
742
+ transition: background 0.15s ease;
743
+ }
744
+
745
+ .ll-expanded__play:hover { background: rgba(0, 0, 0, 0.1); }
746
+
747
+ .ll-expanded__play-circle {
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: center;
751
+ width: 64px;
752
+ height: 64px;
753
+ border-radius: 999px;
754
+ background: rgba(0, 0, 0, 0.55);
755
+ -webkit-backdrop-filter: blur(8px);
756
+ backdrop-filter: blur(8px);
757
+ color: #fff;
758
+ padding-left: 3px; /* optical center the triangle */
759
+ transition: transform 0.15s ease, background 0.15s ease;
760
+ }
761
+
762
+ .ll-expanded__play:hover .ll-expanded__play-circle {
763
+ transform: scale(1.08);
764
+ background: rgba(0, 0, 0, 0.75);
765
+ }
766
+
767
+ .ll-expanded__play-label {
768
+ font-size: 14px;
769
+ font-weight: 600;
770
+ letter-spacing: -0.2px;
771
+ color: #fff;
772
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
773
+ }
774
+
775
+ .ll-expanded__play-sublabel {
776
+ font-size: 12px;
777
+ font-weight: 500;
778
+ color: rgba(255, 255, 255, 0.75);
779
+ letter-spacing: -0.15px;
780
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
781
+ }
782
+
783
+ /* ── Local PIP (camera / screen share) ──────────────────── */
784
+
785
+ .ll-expanded__pip {
786
+ position: absolute;
787
+ right: 12px;
788
+ bottom: 160px;
789
+ z-index: 5;
790
+ width: 110px;
791
+ height: 82px;
792
+ border-radius: 10px;
793
+ overflow: hidden;
794
+ border: 1px solid rgba(255, 255, 255, 0.2);
795
+ background: #000;
796
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
797
+ opacity: 0;
798
+ pointer-events: none;
799
+ transition: opacity 0.2s ease;
800
+ }
801
+
802
+ .ll-expanded__pip.is-visible {
803
+ opacity: 1;
804
+ pointer-events: auto;
805
+ }
806
+
807
+ .ll-expanded__pip-host {
808
+ width: 100%;
809
+ height: 100%;
810
+ }
811
+
812
+ .ll-expanded__pip-host.is-hidden {
813
+ display: none;
814
+ }
815
+
816
+ /* ── Bottom stack (transcript + toolbar + input) ─────────── */
817
+
818
+ .ll-expanded__bottom {
819
+ position: absolute;
820
+ left: 12px;
821
+ right: 12px;
822
+ bottom: 12px;
823
+ z-index: 3;
824
+ display: flex;
825
+ flex-direction: column;
826
+ gap: 8px;
827
+ }
828
+
829
+ .ll-expanded__bottom--idle {
830
+ /* When not yet active, render just transcript / greeting */
831
+ gap: 0;
832
+ }
833
+
834
+ .ll-expanded__transcript {
835
+ padding: 10px 16px;
836
+ border-radius: 14px;
837
+ background: rgba(0, 0, 0, 0.4);
838
+ -webkit-backdrop-filter: blur(14px);
839
+ backdrop-filter: blur(14px);
840
+ }
841
+
842
+ .ll-expanded__transcript-text {
843
+ margin: 0;
844
+ font-size: 13px;
845
+ line-height: 1.45;
846
+ color: #fff;
847
+ letter-spacing: -0.15px;
848
+ display: -webkit-box;
849
+ -webkit-line-clamp: 2;
850
+ -webkit-box-orient: vertical;
851
+ overflow: hidden;
852
+ }
853
+
854
+ /* ── Toolbar (mic / camera / screen / speaker) ──────────── */
855
+
856
+ .ll-toolbar {
857
+ display: flex;
858
+ align-items: center;
859
+ justify-content: center;
860
+ gap: 4px;
861
+ flex-wrap: wrap;
862
+ }
863
+
864
+ .ll-tool {
865
+ display: inline-flex;
866
+ align-items: center;
867
+ justify-content: center;
868
+ width: 40px;
869
+ height: 40px;
870
+ border-radius: 999px;
871
+ border: 1px solid rgba(255, 255, 255, 0.16);
872
+ background: rgba(0, 0, 0, 0.4);
873
+ -webkit-backdrop-filter: blur(20px);
874
+ backdrop-filter: blur(20px);
875
+ color: rgba(255, 255, 255, 0.85);
876
+ cursor: pointer;
877
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
878
+ padding: 0;
879
+ }
880
+
881
+ .ll-tool:hover {
882
+ color: #fff;
883
+ background: rgba(0, 0, 0, 0.55);
884
+ }
885
+
886
+ .ll-tool.is-on {
887
+ background: rgba(255, 255, 255, 0.2);
888
+ border-color: rgba(255, 255, 255, 0.4);
889
+ color: #fff;
890
+ }
891
+
892
+ .ll-tool.is-muted {
893
+ background: rgba(239, 68, 68, 0.6);
894
+ border-color: rgba(248, 113, 113, 0.4);
895
+ color: #fff;
896
+ }
897
+
898
+ /* Split button: left = toggle, right = device menu */
899
+ .ll-tool-split {
900
+ position: relative;
901
+ display: inline-flex;
902
+ align-items: center;
903
+ height: 40px;
904
+ border-radius: 999px;
905
+ overflow: hidden;
906
+ background: rgba(0, 0, 0, 0.4);
907
+ border: 1px solid rgba(255, 255, 255, 0.16);
908
+ -webkit-backdrop-filter: blur(20px);
909
+ backdrop-filter: blur(20px);
910
+ }
911
+
912
+ .ll-tool-split .ll-tool {
913
+ background: transparent;
914
+ border: none;
915
+ border-radius: 0;
916
+ height: 100%;
917
+ -webkit-backdrop-filter: none;
918
+ backdrop-filter: none;
919
+ }
920
+
921
+ .ll-tool-split .ll-tool--left {
922
+ padding-left: 14px;
923
+ padding-right: 4px;
924
+ width: auto;
925
+ }
926
+
927
+ .ll-tool-split .ll-tool--right {
928
+ padding-left: 4px;
929
+ padding-right: 10px;
930
+ width: auto;
931
+ color: rgba(255, 255, 255, 0.7);
932
+ }
933
+
934
+ .ll-tool-split:has(.is-on) {
935
+ background: rgba(255, 255, 255, 0.2);
936
+ border-color: rgba(255, 255, 255, 0.4);
937
+ }
938
+
939
+ .ll-tool-split:has(.is-muted) {
940
+ background: rgba(239, 68, 68, 0.6);
941
+ border-color: rgba(248, 113, 113, 0.4);
942
+ }
943
+
944
+ /* ── Device selection menu (mic / camera) ───────────────── */
945
+
946
+ .ll-device-menu {
947
+ position: absolute;
948
+ bottom: calc(100% + 8px);
949
+ left: 50%;
950
+ transform: translateX(-50%);
951
+ min-width: 220px;
952
+ padding: 6px;
953
+ border-radius: 12px;
954
+ border: 1px solid rgba(255, 255, 255, 0.14);
955
+ background: rgba(26, 26, 26, 0.96);
956
+ -webkit-backdrop-filter: blur(24px);
957
+ backdrop-filter: blur(24px);
958
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
959
+ z-index: 30;
960
+ display: flex;
961
+ flex-direction: column;
962
+ gap: 2px;
963
+ }
964
+
965
+ .ll-device-menu__label {
966
+ margin: 0;
967
+ padding: 6px 10px 4px;
968
+ font-size: 10px;
969
+ font-weight: 600;
970
+ letter-spacing: 0.08em;
971
+ text-transform: uppercase;
972
+ color: rgba(255, 255, 255, 0.4);
973
+ }
974
+
975
+ .ll-device-menu__item {
976
+ display: flex;
977
+ align-items: center;
978
+ gap: 8px;
979
+ padding: 8px 10px;
980
+ border-radius: 8px;
981
+ border: none;
982
+ background: transparent;
983
+ color: rgba(255, 255, 255, 0.75);
984
+ text-align: left;
985
+ font-family: inherit;
986
+ font-size: 12px;
987
+ font-weight: 500;
988
+ cursor: pointer;
989
+ transition: background 0.12s ease;
990
+ }
991
+
992
+ .ll-device-menu__item:hover {
993
+ background: rgba(255, 255, 255, 0.08);
994
+ }
995
+
996
+ .ll-device-menu__item.is-active {
997
+ color: #fff;
998
+ }
999
+
1000
+ .ll-device-menu__dot {
1001
+ color: rgba(255, 255, 255, 0.8);
1002
+ }
1003
+
1004
+ .ll-device-menu__name {
1005
+ flex: 1;
1006
+ }
1007
+
1008
+ /* ── Message input ──────────────────────────────────────── */
1009
+
1010
+ .ll-message-input {
1011
+ height: 40px;
1012
+ border-radius: 999px;
1013
+ background: rgba(0, 0, 0, 0.4);
1014
+ -webkit-backdrop-filter: blur(20px);
1015
+ backdrop-filter: blur(20px);
1016
+ border: 1px solid rgba(255, 255, 255, 0.16);
1017
+ display: flex;
1018
+ align-items: center;
1019
+ padding: 0 6px 0 18px;
1020
+ gap: 8px;
1021
+ }
1022
+
1023
+ .ll-message-input__field {
1024
+ flex: 1;
1025
+ min-width: 0;
1026
+ background: transparent;
1027
+ border: none;
1028
+ outline: none;
1029
+ color: #fff;
1030
+ font-family: inherit;
1031
+ font-size: 14px;
1032
+ font-weight: 500;
1033
+ letter-spacing: -0.14px;
1034
+ }
1035
+
1036
+ .ll-message-input__field::placeholder {
1037
+ color: rgba(255, 255, 255, 0.5);
1038
+ }
1039
+
1040
+ .ll-message-input__send {
1041
+ display: inline-flex;
1042
+ align-items: center;
1043
+ justify-content: center;
1044
+ width: 28px;
1045
+ height: 28px;
1046
+ border-radius: 999px;
1047
+ border: none;
1048
+ background: rgba(255, 255, 255, 0.18);
1049
+ color: #fff;
1050
+ cursor: pointer;
1051
+ transition: background 0.12s ease;
1052
+ }
1053
+
1054
+ .ll-message-input__send:hover {
1055
+ background: rgba(255, 255, 255, 0.3);
1056
+ }
1057
+
1058
+ /* ── End conversation (small secondary button) ──────────── */
1059
+
1060
+ .ll-expanded__end {
1061
+ align-self: center;
1062
+ padding: 6px 14px;
1063
+ border-radius: 999px;
1064
+ border: 1px solid rgba(255, 255, 255, 0.12);
1065
+ background: rgba(0, 0, 0, 0.3);
1066
+ -webkit-backdrop-filter: blur(14px);
1067
+ backdrop-filter: blur(14px);
1068
+ color: rgba(255, 255, 255, 0.7);
1069
+ font-family: inherit;
1070
+ font-size: 11px;
1071
+ font-weight: 500;
1072
+ letter-spacing: -0.1px;
1073
+ cursor: pointer;
1074
+ transition: color 0.15s ease, background 0.15s ease;
1075
+ }
1076
+
1077
+ .ll-expanded__end:hover {
1078
+ color: #fff;
1079
+ background: rgba(0, 0, 0, 0.45);
1080
+ }
1081
+
1082
+ /* ── Error banners ──────────────────────────────────────── */
1083
+
1084
+ .ll-expanded__banner {
1085
+ position: absolute;
1086
+ bottom: 210px;
1087
+ left: 12px;
1088
+ right: 12px;
1089
+ z-index: 6;
1090
+ display: flex;
1091
+ align-items: center;
1092
+ gap: 10px;
1093
+ padding: 10px 14px;
1094
+ border-radius: 12px;
1095
+ background: rgba(0, 0, 0, 0.7);
1096
+ -webkit-backdrop-filter: blur(16px);
1097
+ backdrop-filter: blur(16px);
1098
+ color: #fff;
1099
+ font-size: 12px;
1100
+ font-weight: 500;
1101
+ letter-spacing: -0.12px;
1102
+ }
1103
+
1104
+ .ll-expanded__banner--error {
1105
+ background: rgba(180, 35, 35, 0.85);
1106
+ }
1107
+
1108
+ .ll-expanded__banner-x {
1109
+ margin-left: auto;
1110
+ width: 20px;
1111
+ height: 20px;
1112
+ border: none;
1113
+ background: transparent;
1114
+ color: rgba(255, 255, 255, 0.7);
1115
+ font-size: 18px;
1116
+ line-height: 1;
1117
+ cursor: pointer;
1118
+ }
1119
+
1120
+ .ll-expanded__banner-retry {
1121
+ margin-left: auto;
1122
+ padding: 4px 12px;
1123
+ border-radius: 999px;
1124
+ border: none;
1125
+ background: rgba(255, 255, 255, 0.2);
1126
+ color: #fff;
1127
+ font-family: inherit;
1128
+ font-size: 12px;
1129
+ font-weight: 500;
1130
+ cursor: pointer;
1131
+ transition: background 0.12s ease;
1132
+ }
1133
+
1134
+ .ll-expanded__banner-retry:hover {
1135
+ background: rgba(255, 255, 255, 0.3);
1136
+ }
1137
+
1138
+ /* Mobile: tighten paddings so controls fit and avoid notch overlap. */
1139
+ .ll-expanded--mobile .ll-expanded__topbar {
1140
+ top: max(12px, env(safe-area-inset-top));
1141
+ left: 10px;
1142
+ right: 10px;
1143
+ }
1144
+
1145
+ .ll-expanded--mobile .ll-expanded__bottom {
1146
+ left: 10px;
1147
+ right: 10px;
1148
+ bottom: max(10px, env(safe-area-inset-bottom));
1149
+ }
1150
+
1151
+ .ll-expanded--mobile .ll-expanded__pip {
1152
+ right: 10px;
1153
+ width: 96px;
1154
+ height: 72px;
1155
+ }
1156
+
1157
+ /* ── Error boundary fallback ─────────────────────────────────── */
1158
+
1159
+ .ll-error-boundary {
1160
+ position: fixed;
1161
+ bottom: 16px;
1162
+ right: 16px;
1163
+ padding: 16px;
1164
+ background: var(--ll-color-bg, white);
1165
+ border: 1px solid var(--ll-color-danger, #ef4444);
1166
+ border-radius: var(--ll-radius-card, 12px);
1167
+ max-width: 320px;
1168
+ font-family: var(--ll-font);
1169
+ box-shadow: var(--ll-shadow-card);
1170
+ }
1171
+ .ll-error-boundary__title {
1172
+ font-size: 13px;
1173
+ font-weight: 600;
1174
+ color: var(--ll-color-danger, #ef4444);
1175
+ margin: 0 0 4px;
1176
+ }
1177
+ .ll-error-boundary__message {
1178
+ font-size: 12px;
1179
+ color: var(--ll-color-muted, #808080);
1180
+ margin: 0 0 8px;
1181
+ word-break: break-word;
1182
+ }
1183
+ .ll-error-boundary__retry {
1184
+ font-size: 12px;
1185
+ color: var(--ll-color-primary, #0d0d0d);
1186
+ text-decoration: underline;
1187
+ cursor: pointer;
1188
+ border: none;
1189
+ background: none;
1190
+ }
1191
+
1192
+ /* ── Focus rings (accessibility) ─────────────────────────────── */
1193
+
1194
+ .ll-widget button:focus-visible,
1195
+ .ll-widget [role="button"]:focus-visible,
1196
+ .ll-widget [role="menuitem"]:focus-visible {
1197
+ outline: 2px solid var(--ll-color-primary);
1198
+ outline-offset: 2px;
1199
+ }