@dryui/feedback 0.0.2

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.
Files changed (79) hide show
  1. package/dist/components/annotation-marker.svelte +163 -0
  2. package/dist/components/annotation-marker.svelte.d.ts +11 -0
  3. package/dist/components/annotation-popup.svelte +669 -0
  4. package/dist/components/annotation-popup.svelte.d.ts +42 -0
  5. package/dist/components/highlight-overlay.svelte +48 -0
  6. package/dist/components/highlight-overlay.svelte.d.ts +8 -0
  7. package/dist/components/settings-panel.svelte +446 -0
  8. package/dist/components/settings-panel.svelte.d.ts +24 -0
  9. package/dist/components/toolbar.svelte +1111 -0
  10. package/dist/components/toolbar.svelte.d.ts +46 -0
  11. package/dist/constants.d.ts +9 -0
  12. package/dist/constants.js +37 -0
  13. package/dist/feedback.svelte +2879 -0
  14. package/dist/feedback.svelte.d.ts +4 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.js +7 -0
  17. package/dist/layout-mode/catalog.d.ts +16 -0
  18. package/dist/layout-mode/catalog.js +81 -0
  19. package/dist/layout-mode/component-actions.svelte +84 -0
  20. package/dist/layout-mode/component-actions.svelte.d.ts +18 -0
  21. package/dist/layout-mode/component-picker.svelte +73 -0
  22. package/dist/layout-mode/component-picker.svelte.d.ts +10 -0
  23. package/dist/layout-mode/design-mode.svelte +1115 -0
  24. package/dist/layout-mode/design-mode.svelte.d.ts +24 -0
  25. package/dist/layout-mode/design-palette.svelte +396 -0
  26. package/dist/layout-mode/design-palette.svelte.d.ts +20 -0
  27. package/dist/layout-mode/element-heuristics.d.ts +5 -0
  28. package/dist/layout-mode/element-heuristics.js +51 -0
  29. package/dist/layout-mode/freeze.d.ts +6 -0
  30. package/dist/layout-mode/freeze.js +163 -0
  31. package/dist/layout-mode/generated-library.d.ts +940 -0
  32. package/dist/layout-mode/generated-library.js +1445 -0
  33. package/dist/layout-mode/geometry.d.ts +38 -0
  34. package/dist/layout-mode/geometry.js +133 -0
  35. package/dist/layout-mode/history.d.ts +10 -0
  36. package/dist/layout-mode/history.js +45 -0
  37. package/dist/layout-mode/index.d.ts +23 -0
  38. package/dist/layout-mode/index.js +18 -0
  39. package/dist/layout-mode/live-mount.d.ts +20 -0
  40. package/dist/layout-mode/live-mount.js +70 -0
  41. package/dist/layout-mode/output.d.ts +26 -0
  42. package/dist/layout-mode/output.js +550 -0
  43. package/dist/layout-mode/placement-skeleton.d.ts +9 -0
  44. package/dist/layout-mode/placement-skeleton.js +535 -0
  45. package/dist/layout-mode/rearrange-overlay.svelte +1293 -0
  46. package/dist/layout-mode/rearrange-overlay.svelte.d.ts +18 -0
  47. package/dist/layout-mode/responsive-bar.svelte +39 -0
  48. package/dist/layout-mode/responsive-bar.svelte.d.ts +8 -0
  49. package/dist/layout-mode/route-creator.svelte +70 -0
  50. package/dist/layout-mode/route-creator.svelte.d.ts +8 -0
  51. package/dist/layout-mode/section-detection.d.ts +6 -0
  52. package/dist/layout-mode/section-detection.js +214 -0
  53. package/dist/layout-mode/spatial.d.ts +42 -0
  54. package/dist/layout-mode/spatial.js +156 -0
  55. package/dist/layout-mode/types.d.ts +144 -0
  56. package/dist/layout-mode/types.js +84 -0
  57. package/dist/types.d.ts +157 -0
  58. package/dist/types.js +1 -0
  59. package/dist/utils/dryui-detection.d.ts +1 -0
  60. package/dist/utils/dryui-detection.js +219 -0
  61. package/dist/utils/element-id.d.ts +12 -0
  62. package/dist/utils/element-id.js +333 -0
  63. package/dist/utils/freeze.d.ts +7 -0
  64. package/dist/utils/freeze.js +168 -0
  65. package/dist/utils/output.d.ts +15 -0
  66. package/dist/utils/output.js +245 -0
  67. package/dist/utils/selection.d.ts +22 -0
  68. package/dist/utils/selection.js +58 -0
  69. package/dist/utils/shadow-dom.d.ts +4 -0
  70. package/dist/utils/shadow-dom.js +39 -0
  71. package/dist/utils/storage.d.ts +30 -0
  72. package/dist/utils/storage.js +206 -0
  73. package/dist/utils/svelte-detection.d.ts +8 -0
  74. package/dist/utils/svelte-detection.js +86 -0
  75. package/dist/utils/svelte-meta.d.ts +6 -0
  76. package/dist/utils/svelte-meta.js +69 -0
  77. package/dist/utils/sync.d.ts +18 -0
  78. package/dist/utils/sync.js +62 -0
  79. package/package.json +65 -0
@@ -0,0 +1,1111 @@
1
+ <script lang="ts">
2
+ import { onMount, tick } from 'svelte';
3
+ import { Badge } from '@dryui/ui/badge';
4
+ import { Drawer } from '@dryui/ui/drawer';
5
+ import { Toolbar } from '@dryui/ui/toolbar';
6
+ import type { ConnectionStatus, FeedbackSettings } from '../types.js';
7
+ import { loadSettings } from '../utils/storage.js';
8
+ import SettingsPanel from './settings-panel.svelte';
9
+
10
+ interface Props {
11
+ annotationCount: number;
12
+ hasOutput?: boolean;
13
+ placementCount?: number;
14
+ sectionCount?: number;
15
+ copyLabel?: string;
16
+ copyState?: 'idle' | 'copied';
17
+ copyDisabled?: boolean;
18
+ submitLabel?: string;
19
+ submitState?: 'idle' | 'sending' | 'sent' | 'failed';
20
+ submitDisabled?: boolean;
21
+ active: boolean;
22
+ hidden?: boolean;
23
+ paused?: boolean;
24
+ layoutActive?: boolean;
25
+ rearrangeActive?: boolean;
26
+ markersVisible?: boolean;
27
+ connectionStatus?: ConnectionStatus;
28
+ endpoint?: string;
29
+ sessionId?: string | null;
30
+ settings?: FeedbackSettings;
31
+ webhookUrl?: string;
32
+ shortcut?: string;
33
+ onToggleActive: () => void;
34
+ onCopy: () => void;
35
+ canSubmit?: boolean;
36
+ onSubmit?: () => void;
37
+ onClear: () => void;
38
+ onSettingsChange?: (settings: FeedbackSettings) => void;
39
+ onLayout?: () => void;
40
+ onRearrange?: () => void;
41
+ onPause?: () => void;
42
+ onToggleMarkers?: () => void;
43
+ onHide?: () => void;
44
+ canUndo?: boolean;
45
+ canRedo?: boolean;
46
+ onUndo?: () => void;
47
+ onRedo?: () => void;
48
+ onNewPage?: () => void;
49
+ class?: string;
50
+ }
51
+
52
+ interface ToolbarPosition {
53
+ x: number;
54
+ y: number;
55
+ }
56
+
57
+ interface DragState {
58
+ x: number;
59
+ y: number;
60
+ toolbarX: number;
61
+ toolbarY: number;
62
+ }
63
+
64
+ const POSITION_KEY = 'dryui-feedback-toolbar-position';
65
+ const WRAPPER_WIDTH = 337;
66
+ const DRAG_THRESHOLD = 10;
67
+ const ENTRANCE_DURATION = 500;
68
+ const HIDE_DURATION = 400;
69
+
70
+ let {
71
+ annotationCount,
72
+ hasOutput = annotationCount > 0,
73
+ placementCount = 0,
74
+ sectionCount = 0,
75
+ copyLabel,
76
+ copyState = 'idle',
77
+ copyDisabled,
78
+ submitLabel,
79
+ submitState = 'idle',
80
+ submitDisabled,
81
+ active,
82
+ hidden = false,
83
+ paused = false,
84
+ layoutActive = false,
85
+ rearrangeActive = false,
86
+ markersVisible = true,
87
+ connectionStatus = 'disconnected',
88
+ endpoint,
89
+ sessionId = null,
90
+ settings: initialSettings = loadSettings(),
91
+ webhookUrl = '',
92
+ shortcut,
93
+ onToggleActive,
94
+ onCopy,
95
+ onSubmit,
96
+ canSubmit = true,
97
+ onClear,
98
+ onSettingsChange,
99
+ onLayout,
100
+ onRearrange,
101
+ onPause,
102
+ onToggleMarkers,
103
+ onHide,
104
+ canUndo = false,
105
+ canRedo = false,
106
+ onUndo,
107
+ onRedo,
108
+ onNewPage,
109
+ class: className,
110
+ }: Props = $props();
111
+
112
+ let settingsOpen = $state(false);
113
+ let toolbarPosition = $state<ToolbarPosition | null>(null);
114
+ let dragState = $state<DragState | null>(null);
115
+ let dragging = $state(false);
116
+ let viewportWidth = $state(0);
117
+ let tooltipsHidden = $state(false);
118
+ let tooltipSessionActive = $state(false);
119
+ let showEntranceAnimation = $state(false);
120
+ let hiding = $state(false);
121
+ let justFinishedDrag = false;
122
+
123
+ let entranceTimer: ReturnType<typeof setTimeout> | undefined;
124
+ let hideTimer: ReturnType<typeof setTimeout> | undefined;
125
+ let shellEl = $state<HTMLDivElement | undefined>();
126
+
127
+ const settings = $derived({ ...initialSettings });
128
+ const toggleTitle = $derived(
129
+ `${active ? 'Stop annotating' : 'Start annotating'}${shortcut ? ` (${shortcut})` : ''}`,
130
+ );
131
+ const settingsAvailable = $derived(active && !hidden);
132
+ const autoSendActive = $derived(
133
+ Boolean((settings.webhookUrl || webhookUrl).trim()) && settings.webhooksEnabled,
134
+ );
135
+ const showSubmit = $derived(canSubmit && onSubmit !== undefined && !autoSendActive);
136
+ const copyStateLabel = $derived(copyLabel ?? (copyState === 'copied' ? 'Copied' : 'Copy output'));
137
+ const submitStateLabel = $derived(
138
+ submitLabel ?? (
139
+ submitState === 'sending'
140
+ ? 'Sending...'
141
+ : submitState === 'sent'
142
+ ? 'Sent'
143
+ : submitState === 'failed'
144
+ ? 'Send failed'
145
+ : 'Send to agent'
146
+ ),
147
+ );
148
+ const isCopyDisabled = $derived(copyDisabled ?? !hasOutput);
149
+ const isSubmitDisabled = $derived(submitDisabled ?? (!hasOutput || submitState === 'sending'));
150
+ const isCopyActive = $derived(copyState === 'copied');
151
+ const isSubmitActive = $derived(submitState !== 'idle');
152
+ const markersLabel = $derived(markersVisible ? 'Hide markers' : 'Show markers');
153
+ const markersDisabled = $derived(annotationCount === 0 || layoutActive || rearrangeActive);
154
+ const lightTheme = $derived(settings.theme === 'light');
155
+ const settingsVisible = $derived(settingsOpen && settingsAvailable);
156
+ const toolbarNearTop = $derived(Boolean(toolbarPosition && toolbarPosition.y < 230));
157
+ const toolbarNearLeft = $derived(Boolean(toolbarPosition && toolbarPosition.x < 120));
158
+ const toolbarNearRight = $derived(Boolean(toolbarPosition && viewportWidth > 0 && toolbarPosition.x > viewportWidth - 120));
159
+ const connectionDotVisible = $derived(Boolean(endpoint) && connectionStatus !== 'disconnected' && !settingsVisible);
160
+ const shellClass = $derived(
161
+ [
162
+ 'toolbar-shell',
163
+ active ? 'expanded' : 'collapsed',
164
+ lightTheme ? 'light' : 'dark',
165
+ showEntranceAnimation ? 'entrance' : '',
166
+ hiding ? 'hiding' : '',
167
+ dragging ? 'dragging' : '',
168
+ ]
169
+ .filter(Boolean)
170
+ .join(' '),
171
+ );
172
+ const controlsClass = $derived(
173
+ [
174
+ 'controls-content',
175
+ active ? 'visible' : 'hidden',
176
+ toolbarNearTop ? 'tooltip-below' : '',
177
+ tooltipsHidden || settingsVisible ? 'tooltips-hidden' : '',
178
+ tooltipSessionActive ? 'tooltips-in-session' : '',
179
+ ]
180
+ .filter(Boolean)
181
+ .join(' '),
182
+ );
183
+ const shellStyle = $derived(
184
+ toolbarPosition
185
+ ? `left: ${toolbarPosition.x}px; top: ${toolbarPosition.y}px; right: auto; bottom: auto; width: ${WRAPPER_WIDTH}px;`
186
+ : `right: var(--dry-space-4, 16px); bottom: var(--dry-space-4, 16px); width: ${WRAPPER_WIDTH}px;`,
187
+ );
188
+
189
+ function clearTimers() {
190
+ if (entranceTimer) clearTimeout(entranceTimer);
191
+ if (hideTimer) clearTimeout(hideTimer);
192
+ }
193
+
194
+ function hideTooltipsUntilMouseLeave() {
195
+ tooltipsHidden = true;
196
+ tooltipSessionActive = true;
197
+ }
198
+
199
+ function resetTooltipSession() {
200
+ tooltipsHidden = false;
201
+ tooltipSessionActive = false;
202
+ }
203
+
204
+ function closeSettings() {
205
+ settingsOpen = false;
206
+ resetTooltipSession();
207
+ }
208
+
209
+ function loadToolbarPosition(): ToolbarPosition | null {
210
+ if (typeof window === 'undefined') return null;
211
+
212
+ try {
213
+ const raw = localStorage.getItem(POSITION_KEY);
214
+ if (!raw) return null;
215
+ const parsed = JSON.parse(raw) as Partial<ToolbarPosition>;
216
+ if (typeof parsed.x === 'number' && typeof parsed.y === 'number') {
217
+ return { x: parsed.x, y: parsed.y };
218
+ }
219
+ } catch {
220
+ return null;
221
+ }
222
+
223
+ return null;
224
+ }
225
+
226
+ function saveToolbarPosition(position: ToolbarPosition | null) {
227
+ if (typeof window === 'undefined' || !position) return;
228
+
229
+ try {
230
+ localStorage.setItem(POSITION_KEY, JSON.stringify(position));
231
+ } catch {
232
+ // localStorage can be unavailable
233
+ }
234
+ }
235
+
236
+ function getContentWidth() {
237
+ const shell = document.querySelector('[data-feedback-toolbar] .toolbar-shell') as HTMLElement | null;
238
+ return shell?.getBoundingClientRect().width ?? (active ? 297 : 44);
239
+ }
240
+
241
+ function clampPosition(x: number, y: number): ToolbarPosition {
242
+ const padding = 20;
243
+ const contentWidth = getContentWidth();
244
+ const contentOffset = WRAPPER_WIDTH - contentWidth;
245
+ const minX = padding - contentOffset;
246
+ const maxX = window.innerWidth - padding - WRAPPER_WIDTH;
247
+
248
+ return {
249
+ x: Math.max(minX, Math.min(maxX, x)),
250
+ y: Math.max(padding, Math.min(window.innerHeight - 44 - padding, y)),
251
+ };
252
+ }
253
+
254
+ function constrainToolbarPosition() {
255
+ if (!toolbarPosition || typeof window === 'undefined') return;
256
+ toolbarPosition = clampPosition(toolbarPosition.x, toolbarPosition.y);
257
+ }
258
+
259
+ async function toggleActiveState(event: MouseEvent | KeyboardEvent) {
260
+ event.stopPropagation();
261
+ if (active) {
262
+ settingsOpen = false;
263
+ hideTooltipsUntilMouseLeave();
264
+ }
265
+ onToggleActive();
266
+ await tick();
267
+ constrainToolbarPosition();
268
+ }
269
+
270
+ function handleSettingsChange(next: FeedbackSettings) {
271
+ onSettingsChange?.(next);
272
+ queueMicrotask(() => {
273
+ tick().then(() => {
274
+ constrainToolbarPosition();
275
+ });
276
+ });
277
+ }
278
+
279
+ async function toggleSettings(event: MouseEvent) {
280
+ event.stopPropagation();
281
+ if (!settingsAvailable) {
282
+ closeSettings();
283
+ return;
284
+ }
285
+ if (settingsOpen) {
286
+ closeSettings();
287
+ return;
288
+ }
289
+ if (layoutActive || rearrangeActive) {
290
+ onLayout?.();
291
+ await tick();
292
+ }
293
+ hideTooltipsUntilMouseLeave();
294
+ settingsOpen = true;
295
+ }
296
+
297
+ function runAction(handler?: () => void, options?: { closeSettings?: boolean }) {
298
+ return (event: MouseEvent) => {
299
+ event.stopPropagation();
300
+ hideTooltipsUntilMouseLeave();
301
+ if (options?.closeSettings) closeSettings();
302
+ handler?.();
303
+ };
304
+ }
305
+
306
+ function handleHide() {
307
+ if (hiding) return;
308
+ closeSettings();
309
+ hideTooltipsUntilMouseLeave();
310
+ hiding = true;
311
+ if (hideTimer) clearTimeout(hideTimer);
312
+ hideTimer = setTimeout(() => {
313
+ onHide?.();
314
+ }, HIDE_DURATION);
315
+ }
316
+
317
+ function handleShellClick(event: MouseEvent) {
318
+ if (active) return;
319
+ if (justFinishedDrag) {
320
+ justFinishedDrag = false;
321
+ event.preventDefault();
322
+ return;
323
+ }
324
+ void toggleActiveState(event);
325
+ }
326
+
327
+ function handleShellKeydown(event: KeyboardEvent) {
328
+ if (active) return;
329
+ if (event.key !== 'Enter' && event.key !== ' ') return;
330
+ event.preventDefault();
331
+ void toggleActiveState(event);
332
+ }
333
+
334
+ function handleShellMouseDown(event: MouseEvent) {
335
+ if (event.button !== 0) return;
336
+
337
+ const target = event.target as HTMLElement;
338
+ if (target.closest('button')) {
339
+ return;
340
+ }
341
+
342
+ const wrapper = (event.currentTarget as HTMLElement | null)?.closest('[data-feedback-toolbar]') as HTMLDivElement | null;
343
+ if (!wrapper) return;
344
+
345
+ const rect = wrapper.getBoundingClientRect();
346
+ dragState = {
347
+ x: event.clientX,
348
+ y: event.clientY,
349
+ toolbarX: toolbarPosition?.x ?? rect.left,
350
+ toolbarY: toolbarPosition?.y ?? rect.top,
351
+ };
352
+ dragging = false;
353
+ }
354
+
355
+ function handleWindowMouseMove(event: MouseEvent) {
356
+ if (!dragState || typeof window === 'undefined') return;
357
+
358
+ const deltaX = event.clientX - dragState.x;
359
+ const deltaY = event.clientY - dragState.y;
360
+ if (!dragging && Math.hypot(deltaX, deltaY) < DRAG_THRESHOLD) {
361
+ return;
362
+ }
363
+
364
+ dragging = true;
365
+ toolbarPosition = clampPosition(dragState.toolbarX + deltaX, dragState.toolbarY + deltaY);
366
+ }
367
+
368
+ function handleWindowMouseUp() {
369
+ if (dragging) {
370
+ saveToolbarPosition(toolbarPosition);
371
+ justFinishedDrag = true;
372
+ window.setTimeout(() => {
373
+ justFinishedDrag = false;
374
+ }, 0);
375
+ }
376
+
377
+ dragState = null;
378
+ dragging = false;
379
+ }
380
+
381
+ onMount(() => {
382
+ viewportWidth = window.innerWidth;
383
+ toolbarPosition = loadToolbarPosition();
384
+ showEntranceAnimation = true;
385
+ entranceTimer = setTimeout(() => {
386
+ showEntranceAnimation = false;
387
+ }, ENTRANCE_DURATION);
388
+
389
+ const handleResize = () => {
390
+ viewportWidth = window.innerWidth;
391
+ constrainToolbarPosition();
392
+ };
393
+
394
+ // Attach click/keydown directly (not via Svelte delegation) so the
395
+ // handler fires reliably even when the toolbar lives inside a Portal
396
+ // that has moved this element to document.body after initial render.
397
+ shellEl?.addEventListener('click', handleShellClick);
398
+ shellEl?.addEventListener('keydown', handleShellKeydown as EventListener);
399
+
400
+ window.addEventListener('mousemove', handleWindowMouseMove);
401
+ window.addEventListener('mouseup', handleWindowMouseUp);
402
+ window.addEventListener('resize', handleResize);
403
+
404
+ return () => {
405
+ shellEl?.removeEventListener('click', handleShellClick);
406
+ shellEl?.removeEventListener('keydown', handleShellKeydown as EventListener);
407
+ window.removeEventListener('mousemove', handleWindowMouseMove);
408
+ window.removeEventListener('mouseup', handleWindowMouseUp);
409
+ window.removeEventListener('resize', handleResize);
410
+ clearTimers();
411
+ };
412
+ });
413
+
414
+ $effect(() => {
415
+ if (!settingsAvailable && settingsOpen) {
416
+ closeSettings();
417
+ }
418
+ });
419
+ </script>
420
+
421
+ {#if !hidden}
422
+ <div
423
+ class={['feedback-toolbar-shell', className].filter(Boolean).join(' ')}
424
+ data-feedback-toolbar
425
+ data-dryui-feedback
426
+ data-feedback-toolbar-theme={lightTheme ? 'light' : 'dark'}
427
+ style={shellStyle}
428
+ >
429
+ <div
430
+ class={shellClass}
431
+ data-testid="toggle-btn"
432
+ role="button"
433
+ tabindex={!active ? 0 : -1}
434
+ aria-label={!active ? toggleTitle : 'Feedback toolbar'}
435
+ aria-pressed={active}
436
+ title={!active ? 'Start feedback mode' : undefined}
437
+ bind:this={shellEl}
438
+ onmousedown={handleShellMouseDown}
439
+ >
440
+ <div class:hidden={active} class:visible={!active} class="toggle-content">
441
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
442
+ <path d="M5 7.5h9M5 12h11M5 16.5h8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
443
+ <path d="M18.5 6.5l1 2.2 2.2 1-2.2 1-1 2.3-1-2.3-2.3-1 2.3-1 1-2.2Z" fill="currentColor" />
444
+ </svg>
445
+
446
+ {#if annotationCount > 0}
447
+ <Badge
448
+ class="toggle-badge"
449
+ data-testid="annotation-count"
450
+ variant="solid"
451
+ size="sm"
452
+ color="blue"
453
+ aria-label={`${annotationCount} annotations`}
454
+ >
455
+ {annotationCount}
456
+ </Badge>
457
+ {/if}
458
+ </div>
459
+
460
+ <div class={controlsClass}>
461
+ <Toolbar.Root aria-label="Feedback toolbar" class="controls-toolbar" onmouseenter={resetTooltipSession} onmouseleave={resetTooltipSession}>
462
+ <div class="toolbar-item" class:align-left={toolbarNearLeft}>
463
+ <Toolbar.Button
464
+ data-testid="pause-btn"
465
+ title={paused ? 'Resume animations' : 'Pause animations'}
466
+ aria-label={paused ? 'Resume animations' : 'Pause animations'}
467
+ aria-pressed={paused}
468
+ onclick={runAction(onPause)}
469
+ style="min-width: 2.125rem; min-height: 2.125rem;"
470
+ >
471
+ {#if paused}
472
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
473
+ <path d="M5 4.5v7M11 4.5v7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" />
474
+ </svg>
475
+ {:else}
476
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
477
+ <path d="M5.5 4.5l6 3.5-6 3.5v-7Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
478
+ </svg>
479
+ {/if}
480
+ </Toolbar.Button>
481
+ <span class="toolbar-tooltip">Pause animations <span class="shortcut">P</span></span>
482
+ </div>
483
+
484
+ <div class="toolbar-item">
485
+ <Toolbar.Button
486
+ data-testid="layout-btn"
487
+ title="Toggle layout mode"
488
+ aria-label="Toggle layout mode"
489
+ aria-pressed={layoutActive}
490
+ onclick={runAction(onLayout, { closeSettings: true })}
491
+ style="min-width: 2.125rem; min-height: 2.125rem;"
492
+ >
493
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
494
+ <rect x="2" y="3" width="12" height="2.5" rx="1" stroke="currentColor" stroke-width="1.2" />
495
+ <rect x="2" y="6.75" width="7" height="6" rx="1" stroke="currentColor" stroke-width="1.2" />
496
+ <rect x="10" y="6.75" width="4" height="6" rx="1" stroke="currentColor" stroke-width="1.2" />
497
+ </svg>
498
+ </Toolbar.Button>
499
+ <span class="toolbar-tooltip">{layoutActive ? 'Exit layout mode' : 'Layout mode'} <span class="shortcut">L</span></span>
500
+ </div>
501
+
502
+ <div class="toolbar-item">
503
+ <Toolbar.Button
504
+ data-testid="rearrange-btn"
505
+ title="Toggle rearrange mode"
506
+ aria-label="Toggle rearrange mode"
507
+ aria-pressed={rearrangeActive}
508
+ onclick={runAction(onRearrange, { closeSettings: true })}
509
+ style="min-width: 2.125rem; min-height: 2.125rem;"
510
+ >
511
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
512
+ <path d="M5.5 3.5L3.5 5.5L5.5 7.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
513
+ <path d="M10.5 8.5L12.5 10.5L10.5 12.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
514
+ <path d="M3.5 5.5h6a3 3 0 0 1 3 3v2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
515
+ </svg>
516
+ </Toolbar.Button>
517
+ <span class="toolbar-tooltip">{rearrangeActive ? 'Exit rearrange mode' : 'Rearrange mode'} <span class="shortcut">R</span></span>
518
+ </div>
519
+
520
+ {#if onNewPage && layoutActive}
521
+ <div class="toolbar-item">
522
+ <Toolbar.Button
523
+ data-testid="new-page-btn"
524
+ title="New page"
525
+ aria-label="New page"
526
+ onclick={runAction(onNewPage)}
527
+ style="min-width: 2.125rem; min-height: 2.125rem;"
528
+ >
529
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
530
+ <rect x="3" y="2" width="10" height="12" rx="1.5" stroke="currentColor" stroke-width="1.2" />
531
+ <path d="M8 6v4M6 8h4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
532
+ </svg>
533
+ </Toolbar.Button>
534
+ <span class="toolbar-tooltip">New page</span>
535
+ </div>
536
+ {/if}
537
+
538
+ <div class="toolbar-item">
539
+ <Toolbar.Button
540
+ data-testid="markers-btn"
541
+ title={markersLabel}
542
+ aria-label={markersLabel}
543
+ aria-pressed={markersVisible}
544
+ disabled={markersDisabled}
545
+ onclick={runAction(onToggleMarkers)}
546
+ style="min-width: 2.125rem; min-height: 2.125rem;"
547
+ >
548
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
549
+ <path d="M2 8c1.4-2.5 3.6-4 6-4s4.6 1.5 6 4c-1.4 2.5-3.6 4-6 4S3.4 10.5 2 8Z" stroke="currentColor" stroke-width="1.2" />
550
+ <circle cx="8" cy="8" r="1.7" stroke="currentColor" stroke-width="1.2" />
551
+ {#if !markersVisible}
552
+ <path d="M3 13L13 3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
553
+ {/if}
554
+ </svg>
555
+ </Toolbar.Button>
556
+ <span class="toolbar-tooltip">{markersLabel} <span class="shortcut">H</span></span>
557
+ </div>
558
+
559
+ <div class="toolbar-item">
560
+ <Toolbar.Button
561
+ data-testid="copy-btn"
562
+ data-copy-state={copyState}
563
+ title={copyStateLabel}
564
+ aria-label={copyStateLabel}
565
+ aria-pressed={isCopyActive}
566
+ data-active={isCopyActive}
567
+ disabled={isCopyDisabled}
568
+ onclick={runAction(onCopy)}
569
+ style="min-width: 2.125rem; min-height: 2.125rem;"
570
+ >
571
+ {#if isCopyActive}
572
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
573
+ <path d="M3.5 8.5l2.5 2.5 6-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
574
+ <circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.2" fill="none" />
575
+ </svg>
576
+ {:else}
577
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
578
+ <rect x="5" y="5" width="8" height="8" rx="1.5" stroke="currentColor" stroke-width="1.5" />
579
+ <path d="M3 11V3a1.5 1.5 0 011.5-1.5H11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
580
+ </svg>
581
+ {/if}
582
+ </Toolbar.Button>
583
+ <span class="toolbar-tooltip">{copyStateLabel} <span class="shortcut">C</span></span>
584
+ </div>
585
+
586
+ {#if onSubmit}
587
+ <div class={['toolbar-item', 'send-button-wrapper', showSubmit ? 'send-button-visible' : ''].filter(Boolean).join(' ')} data-testid="submit-slot">
588
+ {#if showSubmit}
589
+ <Toolbar.Button
590
+ data-testid="submit-btn"
591
+ data-submit-state={submitState}
592
+ title={submitStateLabel}
593
+ aria-label={submitStateLabel}
594
+ aria-pressed={isSubmitActive}
595
+ data-active={isSubmitActive}
596
+ disabled={isSubmitDisabled}
597
+ onclick={runAction(onSubmit)}
598
+ style="min-width: 2.125rem; min-height: 2.125rem;"
599
+ >
600
+ {#if submitState === 'sending'}
601
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
602
+ <circle cx="8" cy="8" r="5.5" stroke="currentColor" stroke-width="1.2" opacity="0.35" />
603
+ <path d="M8 2.5a5.5 5.5 0 0 1 5.5 5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
604
+ </svg>
605
+ {:else if submitState === 'sent'}
606
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
607
+ <path d="M3.5 8.5l2.5 2.5 6-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
608
+ </svg>
609
+ {:else if submitState === 'failed'}
610
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
611
+ <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
612
+ </svg>
613
+ {:else}
614
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
615
+ <path d="M2 8h9m0 0L7.5 4.5M11 8l-3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
616
+ </svg>
617
+ {/if}
618
+ </Toolbar.Button>
619
+ {/if}
620
+ <span class="toolbar-tooltip">{submitStateLabel} <span class="shortcut">S</span></span>
621
+ </div>
622
+ {/if}
623
+
624
+ {#if onUndo && layoutActive}
625
+ <div class="toolbar-item">
626
+ <Toolbar.Button
627
+ data-testid="undo-btn"
628
+ title="Undo"
629
+ aria-label="Undo"
630
+ disabled={!canUndo}
631
+ onclick={runAction(onUndo)}
632
+ style="min-width: 2.125rem; min-height: 2.125rem;"
633
+ >
634
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
635
+ <path d="M4 6h5.5a3 3 0 0 1 0 6H8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
636
+ <path d="M6.5 3.5L4 6l2.5 2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
637
+ </svg>
638
+ </Toolbar.Button>
639
+ <span class="toolbar-tooltip">Undo <span class="shortcut">&#8984;Z</span></span>
640
+ </div>
641
+ {/if}
642
+
643
+ {#if onRedo && layoutActive}
644
+ <div class="toolbar-item">
645
+ <Toolbar.Button
646
+ data-testid="redo-btn"
647
+ title="Redo"
648
+ aria-label="Redo"
649
+ disabled={!canRedo}
650
+ onclick={runAction(onRedo)}
651
+ style="min-width: 2.125rem; min-height: 2.125rem;"
652
+ >
653
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
654
+ <path d="M12 6H6.5a3 3 0 0 0 0 6H8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
655
+ <path d="M9.5 3.5L12 6l-2.5 2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
656
+ </svg>
657
+ </Toolbar.Button>
658
+ <span class="toolbar-tooltip">Redo <span class="shortcut">&#8984;&#8679;Z</span></span>
659
+ </div>
660
+ {/if}
661
+
662
+ <div class="toolbar-item">
663
+ <Toolbar.Button
664
+ data-testid="clear-btn"
665
+ title="Clear annotations"
666
+ aria-label="Clear annotations"
667
+ disabled={!hasOutput}
668
+ onclick={runAction(onClear)}
669
+ style="min-width: 2.125rem; min-height: 2.125rem;"
670
+ >
671
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
672
+ <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
673
+ </svg>
674
+ </Toolbar.Button>
675
+ <span class="toolbar-tooltip">Clear all <span class="shortcut">X</span></span>
676
+ </div>
677
+
678
+ <div class="toolbar-item settings-item">
679
+ <Toolbar.Button
680
+ data-testid="settings-btn"
681
+ title="Open settings"
682
+ aria-label="Open settings"
683
+ aria-pressed={settingsVisible}
684
+ onclick={toggleSettings}
685
+ style="min-width: 2.125rem; min-height: 2.125rem;"
686
+ >
687
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
688
+ <path d="M6.5 2.7h3l.4 1.3c.2.1.4.2.6.3l1.2-.6 2 2-.6 1.2c.1.2.2.4.3.6l1.3.4v3l-1.3.4c-.1.2-.2.4-.3.6l.6 1.2-2 2-1.2-.6c-.2.1-.4.2-.6.3l-.4 1.3h-3l-.4-1.3c-.2-.1-.4-.2-.6-.3l-1.2.6-2-2 .6-1.2c-.1-.2-.2-.4-.3-.6l-1.3-.4v-3l1.3-.4c.1-.2.2-.4.3-.6l-.6-1.2 2-2 1.2.6c.2-.1.4-.2.6-.3l.4-1.3Z" stroke="currentColor" stroke-width="1.1" />
689
+ <circle cx="8" cy="8" r="2.2" stroke="currentColor" stroke-width="1.2" />
690
+ </svg>
691
+ </Toolbar.Button>
692
+ {#if connectionDotVisible}
693
+ <span
694
+ class={['connection-indicator', connectionStatus].filter(Boolean).join(' ')}
695
+ data-testid="connection-status"
696
+ aria-label={`Server sync ${connectionStatus}`}
697
+ ></span>
698
+ {/if}
699
+ <span class="toolbar-tooltip">Settings</span>
700
+ </div>
701
+
702
+ <div class="divider" aria-hidden="true"></div>
703
+
704
+ <div class="toolbar-item" class:align-right={toolbarNearRight}>
705
+ <Toolbar.Button
706
+ data-testid="exit-btn"
707
+ title="Exit feedback"
708
+ aria-label="Exit feedback"
709
+ onclick={(event) => {
710
+ void toggleActiveState(event);
711
+ }}
712
+ style="min-width: 2.125rem; min-height: 2.125rem;"
713
+ >
714
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
715
+ <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
716
+ </svg>
717
+ </Toolbar.Button>
718
+ <span class="toolbar-tooltip">Exit <span class="shortcut">Esc</span></span>
719
+ </div>
720
+ </Toolbar.Root>
721
+ </div>
722
+ </div>
723
+ </div>
724
+
725
+ {#if settingsVisible}
726
+ <Drawer.Root open={true} side="right">
727
+ <Drawer.Overlay
728
+ data-feedback-settings-overlay
729
+ onclick={() => {
730
+ closeSettings();
731
+ }}
732
+ />
733
+ <Drawer.Content
734
+ class="settings-drawer"
735
+ data-feedback-settings-drawer
736
+ aria-label="Feedback settings"
737
+ onkeydown={(event) => {
738
+ if (event.key === 'Escape') {
739
+ event.preventDefault();
740
+ event.stopPropagation();
741
+ closeSettings();
742
+ }
743
+ }}
744
+ >
745
+ <SettingsPanel
746
+ {settings}
747
+ {markersVisible}
748
+ onChange={handleSettingsChange}
749
+ onClose={() => {
750
+ closeSettings();
751
+ }}
752
+ {placementCount}
753
+ {sectionCount}
754
+ onPause={onPause}
755
+ onHide={handleHide}
756
+ onLayout={() => {
757
+ closeSettings();
758
+ onLayout?.();
759
+ }}
760
+ onRearrange={() => {
761
+ closeSettings();
762
+ onRearrange?.();
763
+ }}
764
+ onToggleMarkers={onToggleMarkers}
765
+ {paused}
766
+ {hidden}
767
+ {layoutActive}
768
+ {rearrangeActive}
769
+ {connectionStatus}
770
+ {endpoint}
771
+ {sessionId}
772
+ />
773
+ </Drawer.Content>
774
+ </Drawer.Root>
775
+ {/if}
776
+ {/if}
777
+
778
+ <style>
779
+ @keyframes toolbar-enter {
780
+ 0% {
781
+ opacity: 0;
782
+ transform: scale(0.72);
783
+ }
784
+
785
+ 100% {
786
+ opacity: 1;
787
+ transform: scale(1);
788
+ }
789
+ }
790
+
791
+ @keyframes toolbar-hide {
792
+ 0% {
793
+ opacity: 1;
794
+ transform: scale(1);
795
+ }
796
+
797
+ 100% {
798
+ opacity: 0;
799
+ transform: scale(0.72);
800
+ }
801
+ }
802
+
803
+ .feedback-toolbar-shell {
804
+ position: fixed;
805
+ z-index: 10002;
806
+ pointer-events: none;
807
+ }
808
+
809
+ .toolbar-shell {
810
+ position: relative;
811
+ margin-left: auto;
812
+ display: flex;
813
+ align-items: center;
814
+ justify-content: center;
815
+ pointer-events: auto;
816
+ user-select: none;
817
+ transition:
818
+ width 0.4s cubic-bezier(0.19, 1, 0.22, 1),
819
+ transform 0.4s cubic-bezier(0.19, 1, 0.22, 1),
820
+ background-color 0.18s ease;
821
+ }
822
+
823
+ .toolbar-shell.dark {
824
+ background: rgba(15, 23, 42, 0.96);
825
+ color: rgba(248, 250, 252, 0.94);
826
+ box-shadow:
827
+ 0 2px 8px rgba(15, 23, 42, 0.2),
828
+ 0 4px 16px rgba(15, 23, 42, 0.12);
829
+ }
830
+
831
+ .toolbar-shell.light {
832
+ background: rgba(255, 255, 255, 0.98);
833
+ color: rgba(15, 23, 42, 0.9);
834
+ box-shadow:
835
+ 0 2px 10px rgba(15, 23, 42, 0.12),
836
+ 0 16px 30px rgba(15, 23, 42, 0.12);
837
+ }
838
+
839
+ .toolbar-shell.entrance {
840
+ animation: toolbar-enter 0.5s cubic-bezier(0.34, 1.2, 0.64, 1) forwards;
841
+ }
842
+
843
+ .toolbar-shell.hiding {
844
+ animation: toolbar-hide 0.4s cubic-bezier(0.4, 0, 1, 1) forwards;
845
+ pointer-events: none;
846
+ }
847
+
848
+ .toolbar-shell.dragging {
849
+ cursor: grabbing;
850
+ }
851
+
852
+ .toolbar-shell.collapsed {
853
+ width: 44px;
854
+ height: 44px;
855
+ border-radius: 22px;
856
+ cursor: grab;
857
+ }
858
+
859
+ .toolbar-shell.collapsed:hover {
860
+ background: rgba(30, 41, 59, 0.98);
861
+ }
862
+
863
+ .toolbar-shell.light.collapsed:hover {
864
+ background: rgba(241, 245, 249, 0.98);
865
+ }
866
+
867
+ .toolbar-shell.collapsed:active {
868
+ transform: scale(0.95);
869
+ }
870
+
871
+ .toolbar-shell.expanded {
872
+ min-height: 44px;
873
+ padding: 0.375rem;
874
+ border-radius: 1.5rem;
875
+ cursor: grab;
876
+ }
877
+
878
+ .toggle-content {
879
+ position: absolute;
880
+ inset: 0;
881
+ display: flex;
882
+ align-items: center;
883
+ justify-content: center;
884
+ transition: opacity 0.1s cubic-bezier(0.19, 1, 0.22, 1);
885
+ }
886
+
887
+ .toggle-content.visible {
888
+ opacity: 1;
889
+ visibility: visible;
890
+ pointer-events: auto;
891
+ }
892
+
893
+ .toggle-content.hidden {
894
+ opacity: 0;
895
+ pointer-events: none;
896
+ }
897
+
898
+ .toggle-badge {
899
+ position: absolute;
900
+ top: -0.75rem;
901
+ right: -0.75rem;
902
+ }
903
+
904
+ .controls-content {
905
+ display: flex;
906
+ align-items: center;
907
+ gap: 0.375rem;
908
+ transition:
909
+ filter 0.8s cubic-bezier(0.19, 1, 0.22, 1),
910
+ opacity 0.8s cubic-bezier(0.19, 1, 0.22, 1),
911
+ transform 0.6s cubic-bezier(0.19, 1, 0.22, 1);
912
+ }
913
+
914
+ .controls-content.visible {
915
+ display: flex;
916
+ opacity: 1;
917
+ filter: blur(0);
918
+ transform: scale(1);
919
+ visibility: visible;
920
+ pointer-events: auto;
921
+ }
922
+
923
+ .controls-content.hidden {
924
+ display: flex;
925
+ opacity: 0;
926
+ filter: blur(10px);
927
+ transform: scale(0.4);
928
+ visibility: hidden;
929
+ pointer-events: none;
930
+ }
931
+
932
+ .controls-toolbar {
933
+ width: 100%;
934
+ }
935
+
936
+ .toolbar-item {
937
+ position: relative;
938
+ display: flex;
939
+ align-items: center;
940
+ justify-content: center;
941
+ }
942
+
943
+ .toolbar-item :global(button) {
944
+ color: inherit;
945
+ }
946
+
947
+ .toolbar-item:hover .toolbar-tooltip {
948
+ opacity: 1;
949
+ visibility: visible;
950
+ transform: translateX(-50%) scale(1);
951
+ transition-delay: 0.85s;
952
+ }
953
+
954
+ .tooltips-in-session .toolbar-item:hover .toolbar-tooltip {
955
+ transition-delay: 0s;
956
+ }
957
+
958
+ .toolbar-tooltip {
959
+ position: absolute;
960
+ bottom: calc(100% + 14px);
961
+ left: 50%;
962
+ transform: translateX(-50%) scale(0.95);
963
+ padding: 6px 10px;
964
+ border-radius: 8px;
965
+ white-space: nowrap;
966
+ font-size: 12px;
967
+ font-weight: 500;
968
+ opacity: 0;
969
+ visibility: hidden;
970
+ pointer-events: none;
971
+ z-index: 10003;
972
+ background: rgba(15, 23, 42, 0.98);
973
+ color: rgba(248, 250, 252, 0.96);
974
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.3);
975
+ transition:
976
+ opacity 0.135s ease,
977
+ transform 0.135s ease,
978
+ visibility 0.135s ease;
979
+ }
980
+
981
+ .toolbar-tooltip::after {
982
+ content: '';
983
+ position: absolute;
984
+ top: calc(100% - 4px);
985
+ left: 50%;
986
+ width: 8px;
987
+ height: 8px;
988
+ transform: translateX(-50%) rotate(45deg);
989
+ background: rgba(15, 23, 42, 0.98);
990
+ border-radius: 0 0 2px 0;
991
+ }
992
+
993
+ .tooltip-below .toolbar-tooltip {
994
+ top: calc(100% + 14px);
995
+ bottom: auto;
996
+ }
997
+
998
+ .tooltip-below .toolbar-tooltip::after {
999
+ top: -4px;
1000
+ bottom: auto;
1001
+ border-radius: 2px 0 0 0;
1002
+ }
1003
+
1004
+ .toolbar-item.align-left .toolbar-tooltip {
1005
+ transform: translateX(-12px) scale(0.95);
1006
+ }
1007
+
1008
+ .toolbar-item.align-left .toolbar-tooltip::after {
1009
+ left: 16px;
1010
+ }
1011
+
1012
+ .toolbar-item.align-left:hover .toolbar-tooltip {
1013
+ transform: translateX(-12px) scale(1);
1014
+ }
1015
+
1016
+ .toolbar-item.align-right .toolbar-tooltip {
1017
+ transform: translateX(calc(-100% + 12px)) scale(0.95);
1018
+ }
1019
+
1020
+ .toolbar-item.align-right .toolbar-tooltip::after {
1021
+ left: auto;
1022
+ right: 8px;
1023
+ }
1024
+
1025
+ .toolbar-item.align-right:hover .toolbar-tooltip {
1026
+ transform: translateX(calc(-100% + 12px)) scale(1);
1027
+ }
1028
+
1029
+ .tooltips-hidden .toolbar-tooltip {
1030
+ opacity: 0 !important;
1031
+ visibility: hidden !important;
1032
+ transition: none !important;
1033
+ }
1034
+
1035
+ .shortcut {
1036
+ margin-left: 4px;
1037
+ opacity: 0.5;
1038
+ }
1039
+
1040
+ .send-button-wrapper {
1041
+ width: 0;
1042
+ margin-left: -0.375rem;
1043
+ overflow: hidden;
1044
+ opacity: 0;
1045
+ pointer-events: none;
1046
+ transition:
1047
+ width 0.4s cubic-bezier(0.19, 1, 0.22, 1),
1048
+ opacity 0.3s cubic-bezier(0.19, 1, 0.22, 1),
1049
+ margin 0.4s cubic-bezier(0.19, 1, 0.22, 1);
1050
+ }
1051
+
1052
+ .send-button-wrapper :global(button) {
1053
+ transform: scale(0.8);
1054
+ transition: transform 0.4s cubic-bezier(0.19, 1, 0.22, 1);
1055
+ }
1056
+
1057
+ .send-button-wrapper.send-button-visible {
1058
+ width: 34px;
1059
+ margin-left: 0;
1060
+ overflow: visible;
1061
+ opacity: 1;
1062
+ pointer-events: auto;
1063
+ }
1064
+
1065
+ .send-button-wrapper.send-button-visible :global(button) {
1066
+ transform: scale(1);
1067
+ }
1068
+
1069
+ .settings-item {
1070
+ margin-left: 0.125rem;
1071
+ }
1072
+
1073
+ .connection-indicator {
1074
+ position: absolute;
1075
+ top: 5px;
1076
+ right: 5px;
1077
+ width: 7px;
1078
+ height: 7px;
1079
+ border-radius: 999px;
1080
+ background: #94a3b8;
1081
+ box-shadow: 0 0 0 2px rgba(15, 23, 42, 0.96);
1082
+ }
1083
+
1084
+ .toolbar-shell.light .connection-indicator {
1085
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.98);
1086
+ }
1087
+
1088
+ .connection-indicator.connected {
1089
+ background: #22c55e;
1090
+ }
1091
+
1092
+ .connection-indicator.connecting {
1093
+ background: #f59e0b;
1094
+ }
1095
+
1096
+ .divider {
1097
+ width: 1px;
1098
+ height: 12px;
1099
+ margin: 0 0.125rem;
1100
+ background: rgba(255, 255, 255, 0.16);
1101
+ }
1102
+
1103
+ .toolbar-shell.light .divider {
1104
+ background: rgba(15, 23, 42, 0.14);
1105
+ }
1106
+
1107
+ .settings-drawer {
1108
+ width: min(24rem, calc(100vw - 1.5rem));
1109
+ max-width: 24rem;
1110
+ }
1111
+ </style>