aetherx-ui-inspector 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ui.js ADDED
@@ -0,0 +1,803 @@
1
+ export const INSPECTOR_ID = '__dev_inspector__'
2
+ export const OVERLAY_ID = '__dev_inspector_overlay__'
3
+ export const PANEL_ID = '__dev_inspector_panel__'
4
+ export const TOOLTIP_ID = '__dev_inspector_tooltip__'
5
+
6
+ export function injectStyles() {
7
+ if (document.getElementById('__dev_inspector_styles__')) return
8
+ const style = document.createElement('style')
9
+ style.id = '__dev_inspector_styles__'
10
+ style.textContent = `
11
+ /* ── Cog trigger button ── */
12
+ #__dev_inspector__ {
13
+ position: fixed;
14
+ bottom: 20px;
15
+ right: 20px;
16
+ z-index: 2147483640;
17
+ width: 44px;
18
+ height: 44px;
19
+ border-radius: 50%;
20
+ background: #18181b;
21
+ border: 1.5px solid #3f3f46;
22
+ color: #fff;
23
+ cursor: pointer;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ box-shadow: 0 4px 24px rgba(0,0,0,0.4);
28
+ transition: background 0.15s, transform 0.15s;
29
+ font-family: system-ui, sans-serif;
30
+ user-select: none;
31
+ }
32
+ #__dev_inspector__:hover { background: #27272a; transform: scale(1.08); }
33
+ #__dev_inspector__.active { background: #6366f1; border-color: #818cf8; }
34
+ #__dev_inspector__ svg { width: 20px; height: 20px; pointer-events: none; }
35
+
36
+ /* ── Hover highlight overlay ── */
37
+ #__dev_inspector_overlay__ {
38
+ position: fixed;
39
+ z-index: 2147483638;
40
+ pointer-events: none;
41
+ border: 2px solid #6366f1;
42
+ background: rgba(99,102,241,0.08);
43
+ border-radius: 3px;
44
+ box-sizing: border-box;
45
+ }
46
+ #__dev_inspector_overlay__.selected {
47
+ border-color: #22c55e;
48
+ background: rgba(34,197,94,0.06);
49
+ }
50
+
51
+ /* ── Hover tooltip ── */
52
+ #__dev_inspector_tooltip__ {
53
+ position: fixed;
54
+ z-index: 2147483639;
55
+ pointer-events: none;
56
+ background: #18181b;
57
+ border: 1px solid #3f3f46;
58
+ border-radius: 10px;
59
+ padding: 10px 13px;
60
+ font-family: 'SF Mono', 'Fira Code', monospace;
61
+ font-size: 11px;
62
+ color: #e4e4e7;
63
+ min-width: 220px;
64
+ max-width: 300px;
65
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
66
+ line-height: 1.6;
67
+ }
68
+ #__dev_inspector_tooltip__ .di-tag {
69
+ font-size: 10px;
70
+ color: #818cf8;
71
+ font-weight: 600;
72
+ text-transform: uppercase;
73
+ letter-spacing: 0.05em;
74
+ margin-bottom: 6px;
75
+ }
76
+ #__dev_inspector_tooltip__ .di-row {
77
+ display: flex;
78
+ justify-content: space-between;
79
+ gap: 12px;
80
+ padding: 1px 0;
81
+ }
82
+ #__dev_inspector_tooltip__ .di-key { color: #71717a; }
83
+ #__dev_inspector_tooltip__ .di-val { color: #f4f4f5; font-weight: 500; }
84
+ #__dev_inspector_tooltip__ .di-section {
85
+ margin-top: 7px;
86
+ padding-top: 7px;
87
+ border-top: 1px solid #27272a;
88
+ font-size: 10px;
89
+ color: #52525b;
90
+ font-weight: 600;
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.06em;
93
+ }
94
+ #__dev_inspector_tooltip__ .di-swatch {
95
+ display: inline-block;
96
+ width: 10px; height: 10px;
97
+ border-radius: 2px;
98
+ border: 1px solid #52525b;
99
+ vertical-align: middle;
100
+ margin-right: 4px;
101
+ }
102
+
103
+ /* ── Side edit panel ── */
104
+ #__dev_inspector_panel__ {
105
+ position: fixed;
106
+ top: 0; right: 0;
107
+ width: 300px;
108
+ min-width: 220px;
109
+ max-width: 580px;
110
+ height: 100vh;
111
+ z-index: 2147483641;
112
+ background: #18181b;
113
+ border-left: 1px solid #3f3f46;
114
+ font-family: system-ui, -apple-system, sans-serif;
115
+ font-size: 13px;
116
+ color: #e4e4e7;
117
+ display: flex;
118
+ flex-direction: column;
119
+ box-shadow: -8px 0 40px rgba(0,0,0,0.4);
120
+ transform: translateX(100%);
121
+ transition: transform 0.22s cubic-bezier(.4,0,.2,1);
122
+ }
123
+ #__dev_inspector_panel__.open { transform: translateX(0); }
124
+
125
+ /* ── Collapsed state: shrinks to a 40px strip ── */
126
+ #__dev_inspector_panel__.collapsed {
127
+ width: 40px !important;
128
+ min-width: 40px;
129
+ }
130
+ #__dev_inspector_panel__.collapsed .di-element-bar,
131
+ #__dev_inspector_panel__.collapsed #di-panel-content,
132
+ #__dev_inspector_panel__.collapsed .di-panel-footer,
133
+ #__dev_inspector_panel__.collapsed .di-panel-header h3 {
134
+ display: none !important;
135
+ }
136
+ #__dev_inspector_panel__.collapsed .di-panel-header {
137
+ flex-direction: column;
138
+ padding: 12px 0;
139
+ align-items: center;
140
+ gap: 10px;
141
+ height: 100%;
142
+ justify-content: flex-start;
143
+ }
144
+ #__dev_inspector_panel__.collapsed .di-collapse-btn svg {
145
+ transform: rotate(180deg);
146
+ }
147
+
148
+ /* ── Resize handle (drag left edge) ── */
149
+ .di-resize-handle {
150
+ position: absolute;
151
+ left: 0; top: 0;
152
+ width: 5px;
153
+ height: 100%;
154
+ cursor: ew-resize;
155
+ z-index: 10;
156
+ background: transparent;
157
+ transition: background 0.15s;
158
+ }
159
+ .di-resize-handle:hover,
160
+ .di-resize-handle.dragging { background: #6366f1; }
161
+
162
+ /* ── Collapse button ── */
163
+ .di-collapse-btn {
164
+ width: 24px; height: 24px;
165
+ background: #27272a;
166
+ border: none;
167
+ border-radius: 6px;
168
+ color: #a1a1aa;
169
+ cursor: pointer;
170
+ display: flex; align-items: center; justify-content: center;
171
+ transition: background 0.12s;
172
+ flex-shrink: 0;
173
+ padding: 0;
174
+ }
175
+ .di-collapse-btn:hover { background: #3f3f46; color: #fff; }
176
+ .di-collapse-btn svg { pointer-events: none; }
177
+
178
+ .di-panel-header {
179
+ padding: 12px 16px;
180
+ border-bottom: 1px solid #27272a;
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: space-between;
184
+ flex-shrink: 0;
185
+ gap: 8px;
186
+ }
187
+ .di-panel-header h3 {
188
+ margin: 0;
189
+ font-size: 13px;
190
+ font-weight: 600;
191
+ color: #f4f4f5;
192
+ flex: 1;
193
+ }
194
+ .di-panel-header .di-close {
195
+ width: 24px; height: 24px;
196
+ background: #27272a;
197
+ border: none;
198
+ border-radius: 6px;
199
+ color: #a1a1aa;
200
+ cursor: pointer;
201
+ display: flex; align-items: center; justify-content: center;
202
+ font-size: 14px;
203
+ transition: background 0.12s;
204
+ flex-shrink: 0;
205
+ }
206
+ .di-panel-header .di-close:hover { background: #3f3f46; color: #fff; }
207
+
208
+ /* ── Element bar (shown when element is selected) ── */
209
+ .di-element-bar {
210
+ padding: 8px 16px;
211
+ border-bottom: 1px solid #27272a;
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ flex-shrink: 0;
216
+ background: #1c1c1f;
217
+ }
218
+ .di-element-label {
219
+ flex: 1;
220
+ font-family: 'SF Mono', monospace;
221
+ font-size: 11px;
222
+ color: #818cf8;
223
+ overflow: hidden;
224
+ text-overflow: ellipsis;
225
+ white-space: nowrap;
226
+ }
227
+ .di-reinspect-btn {
228
+ flex-shrink: 0;
229
+ padding: 4px 10px;
230
+ background: #27272a;
231
+ border: 1px solid #3f3f46;
232
+ border-radius: 6px;
233
+ color: #a1a1aa;
234
+ font-size: 11px;
235
+ font-weight: 500;
236
+ cursor: pointer;
237
+ font-family: system-ui, sans-serif;
238
+ transition: border-color 0.12s, color 0.12s, background 0.12s;
239
+ white-space: nowrap;
240
+ }
241
+ .di-reinspect-btn:hover { border-color: #6366f1; color: #818cf8; background: #1e1e2e; }
242
+ .di-reinspect-btn.active { border-color: #6366f1; color: #818cf8; background: #1e1e2e; }
243
+
244
+ .di-panel-empty {
245
+ flex: 1;
246
+ display: flex;
247
+ flex-direction: column;
248
+ align-items: center;
249
+ justify-content: center;
250
+ color: #52525b;
251
+ gap: 10px;
252
+ padding: 24px;
253
+ text-align: center;
254
+ }
255
+ .di-panel-empty svg { opacity: 0.3; }
256
+ .di-panel-empty p { margin: 0; font-size: 13px; line-height: 1.5; }
257
+
258
+ /* The scrollable region is #di-panel-content — a direct flex child of the panel.
259
+ flex: 1 1 0 (base 0) + min-height: 0 forces it to shrink, enabling overflow. */
260
+ #di-panel-content {
261
+ flex: 1 1 0;
262
+ min-height: 0;
263
+ overflow-y: auto;
264
+ overscroll-behavior: contain;
265
+ }
266
+ #di-panel-content::-webkit-scrollbar { width: 4px; }
267
+ #di-panel-content::-webkit-scrollbar-track { background: transparent; }
268
+ #di-panel-content::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 2px; }
269
+
270
+ .di-section-header {
271
+ padding: 12px 16px 5px 14px;
272
+ font-size: 10px;
273
+ font-weight: 700;
274
+ color: #818cf8;
275
+ text-transform: uppercase;
276
+ letter-spacing: 0.1em;
277
+ border-left: 2px solid #6366f1;
278
+ margin-left: 0;
279
+ position: sticky;
280
+ top: 0;
281
+ background: #18181b;
282
+ z-index: 1;
283
+ }
284
+
285
+ .di-field {
286
+ padding: 5px 16px;
287
+ display: flex;
288
+ align-items: center;
289
+ gap: 8px;
290
+ }
291
+ .di-field label {
292
+ flex: 0 0 90px;
293
+ font-size: 11px;
294
+ color: #71717a;
295
+ font-family: 'SF Mono', monospace;
296
+ }
297
+ .di-field-control { flex: 1; display: flex; align-items: center; gap: 5px; min-width: 0; }
298
+
299
+ .di-input {
300
+ width: 100%;
301
+ background: #27272a;
302
+ border: 1px solid #3f3f46;
303
+ border-radius: 6px;
304
+ color: #f4f4f5;
305
+ font-size: 12px;
306
+ font-family: 'SF Mono', monospace;
307
+ padding: 4px 8px;
308
+ outline: none;
309
+ transition: border-color 0.12s;
310
+ box-sizing: border-box;
311
+ min-width: 0;
312
+ }
313
+ .di-input:focus { border-color: #6366f1; }
314
+
315
+ /* Fix 4: reset button per field */
316
+ .di-reset-btn {
317
+ flex-shrink: 0;
318
+ width: 22px; height: 22px;
319
+ background: transparent;
320
+ border: 1px solid transparent;
321
+ border-radius: 5px;
322
+ color: #3f3f46;
323
+ cursor: pointer;
324
+ font-size: 13px;
325
+ display: flex; align-items: center; justify-content: center;
326
+ transition: color 0.12s, border-color 0.12s, background 0.12s;
327
+ padding: 0;
328
+ line-height: 1;
329
+ }
330
+ .di-reset-btn:hover { color: #f97316; border-color: #3f3f46; background: #27272a; }
331
+ .di-reset-btn.changed { color: #f97316; border-color: #431407; }
332
+
333
+ /* ── Number stepper (− value +) ── */
334
+ .di-number-wrap {
335
+ display: flex;
336
+ align-items: center;
337
+ gap: 2px;
338
+ width: 100%;
339
+ }
340
+ .di-number-wrap .di-input {
341
+ text-align: center;
342
+ padding: 4px 2px;
343
+ min-width: 0;
344
+ }
345
+ .di-step-btn {
346
+ flex-shrink: 0;
347
+ width: 22px;
348
+ height: 26px;
349
+ background: #27272a;
350
+ border: 1px solid #3f3f46;
351
+ border-radius: 5px;
352
+ color: #a1a1aa;
353
+ font-size: 15px;
354
+ font-weight: 400;
355
+ cursor: pointer;
356
+ display: flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ padding: 0;
360
+ line-height: 1;
361
+ user-select: none;
362
+ transition: background 0.1s, color 0.1s;
363
+ }
364
+ .di-step-btn:hover { background: #3f3f46; color: #fff; }
365
+ .di-step-btn:active { background: #6366f1; color: #fff; border-color: #6366f1; }
366
+ /* Smaller variant for spacing grid cells */
367
+ .di-step-sm {
368
+ width: 16px;
369
+ height: 20px;
370
+ font-size: 12px;
371
+ border-radius: 3px;
372
+ }
373
+
374
+ /* ── Panel textarea (text content editor) ── */
375
+ .di-text-area {
376
+ width: 100%;
377
+ resize: vertical;
378
+ min-height: 56px;
379
+ font-family: system-ui, sans-serif;
380
+ line-height: 1.5;
381
+ font-size: 12px;
382
+ box-sizing: border-box;
383
+ }
384
+ .di-text-hint {
385
+ font-size: 10px;
386
+ color: #52525b;
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 8px;
390
+ }
391
+ .di-edit-on-page-btn {
392
+ padding: 3px 10px;
393
+ background: #27272a;
394
+ border: 1px solid #3f3f46;
395
+ border-radius: 5px;
396
+ color: #a1a1aa;
397
+ font-size: 11px;
398
+ cursor: pointer;
399
+ font-family: system-ui, sans-serif;
400
+ transition: border-color 0.12s, color 0.12s;
401
+ white-space: nowrap;
402
+ }
403
+ .di-edit-on-page-btn:hover { border-color: #6366f1; color: #818cf8; }
404
+ .di-edit-on-page-btn.editing { border-color: #6366f1; color: #818cf8; background: #1e1e2e; }
405
+
406
+ .di-color-wrap {
407
+ display: flex;
408
+ align-items: center;
409
+ gap: 5px;
410
+ width: 100%;
411
+ min-width: 0;
412
+ }
413
+ .di-color-swatch {
414
+ width: 24px; height: 24px;
415
+ border-radius: 5px;
416
+ border: 1px solid #3f3f46;
417
+ cursor: pointer;
418
+ flex-shrink: 0;
419
+ overflow: hidden;
420
+ padding: 0;
421
+ background: transparent;
422
+ position: relative;
423
+ }
424
+ .di-color-swatch input[type="color"] {
425
+ width: 36px; height: 36px;
426
+ border: none;
427
+ background: transparent;
428
+ cursor: pointer;
429
+ margin: -6px;
430
+ opacity: 0;
431
+ position: absolute;
432
+ }
433
+
434
+ .di-text-edit-btn {
435
+ width: 100%;
436
+ padding: 6px 10px;
437
+ background: #27272a;
438
+ border: 1px solid #3f3f46;
439
+ border-radius: 6px;
440
+ color: #a1a1aa;
441
+ font-size: 12px;
442
+ cursor: pointer;
443
+ text-align: left;
444
+ transition: border-color 0.12s, color 0.12s;
445
+ font-family: system-ui, sans-serif;
446
+ overflow: hidden;
447
+ text-overflow: ellipsis;
448
+ white-space: nowrap;
449
+ }
450
+ .di-text-edit-btn:hover { border-color: #6366f1; color: #f4f4f5; }
451
+ .di-text-edit-btn.editing {
452
+ border-color: #6366f1;
453
+ color: #f4f4f5;
454
+ background: #1e1e2e;
455
+ }
456
+
457
+ .di-spacing-grid {
458
+ display: grid;
459
+ grid-template-columns: 1fr 1fr;
460
+ gap: 4px;
461
+ width: 100%;
462
+ }
463
+ .di-spacing-grid .di-input { font-size: 11px; padding: 3px 6px; }
464
+
465
+ .di-panel-footer {
466
+ padding: 12px 16px;
467
+ border-top: 1px solid #27272a;
468
+ display: flex;
469
+ flex-direction: column;
470
+ gap: 8px;
471
+ flex-shrink: 0;
472
+ }
473
+
474
+ .di-btn {
475
+ width: 100%;
476
+ padding: 9px;
477
+ border-radius: 8px;
478
+ border: none;
479
+ font-size: 13px;
480
+ font-weight: 500;
481
+ cursor: pointer;
482
+ transition: opacity 0.12s, transform 0.1s;
483
+ font-family: system-ui, sans-serif;
484
+ }
485
+ .di-btn:hover { opacity: 0.88; }
486
+ .di-btn:active { transform: scale(0.98); }
487
+ .di-btn-primary { background: #6366f1; color: #fff; }
488
+ .di-btn-ghost {
489
+ background: #27272a;
490
+ color: #a1a1aa;
491
+ border: 1px solid #3f3f46;
492
+ }
493
+ .di-btn-ghost:hover { color: #f4f4f5; }
494
+
495
+ .di-feedback-count {
496
+ font-size: 11px;
497
+ color: #71717a;
498
+ text-align: center;
499
+ }
500
+
501
+ /* ── Changes toggle button ── */
502
+ .di-changes-toggle {
503
+ width: 100%;
504
+ background: #27272a;
505
+ border: 1px solid #3f3f46;
506
+ border-radius: 8px;
507
+ color: #a1a1aa;
508
+ padding: 7px 12px;
509
+ cursor: pointer;
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: space-between;
513
+ font-size: 12px;
514
+ font-family: system-ui, sans-serif;
515
+ transition: background 0.12s, color 0.12s;
516
+ text-align: left;
517
+ }
518
+ .di-changes-toggle:hover { background: #3f3f46; color: #f4f4f5; }
519
+ .di-changes-toggle.has-changes { color: #c4b5fd; border-color: #4c1d95; background: #1e1b2e; }
520
+ .di-changes-toggle.has-changes:hover { background: #2e2a42; }
521
+ .di-toggle-arrow { transition: transform 0.18s; font-size: 11px; }
522
+ .di-toggle-arrow.open { transform: rotate(180deg); }
523
+
524
+ /* ── Changes log (collapsable list) ── */
525
+ .di-changes-log {
526
+ max-height: 220px;
527
+ overflow-y: auto;
528
+ overscroll-behavior: contain;
529
+ border: 1px solid #27272a;
530
+ border-radius: 8px;
531
+ background: #0f0f11;
532
+ margin-bottom: 4px;
533
+ }
534
+ .di-changes-log::-webkit-scrollbar { width: 3px; }
535
+ .di-changes-log::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 2px; }
536
+ .di-change-group { padding: 8px 10px 6px; border-bottom: 1px solid #1c1c1f; }
537
+ .di-change-group:last-child { border-bottom: none; }
538
+ .di-change-selector {
539
+ font-family: 'SF Mono', monospace;
540
+ font-size: 10px;
541
+ color: #818cf8;
542
+ font-weight: 600;
543
+ margin-bottom: 4px;
544
+ overflow: hidden;
545
+ text-overflow: ellipsis;
546
+ white-space: nowrap;
547
+ }
548
+ .di-change-row {
549
+ display: flex;
550
+ align-items: baseline;
551
+ gap: 6px;
552
+ font-family: 'SF Mono', monospace;
553
+ font-size: 10px;
554
+ padding: 1px 0;
555
+ }
556
+ .di-change-prop { color: #71717a; flex-shrink: 0; }
557
+ .di-change-from { color: #f87171; text-decoration: line-through; max-width: 70px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
558
+ .di-change-arrow { color: #52525b; flex-shrink: 0; }
559
+ .di-change-to { color: #86efac; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
560
+
561
+ /* ── Image section ── */
562
+ .di-img-preview {
563
+ width: 100%;
564
+ max-height: 90px;
565
+ object-fit: contain;
566
+ border-radius: 6px;
567
+ background: #27272a;
568
+ display: block;
569
+ }
570
+ .di-img-meta {
571
+ font-size: 10px;
572
+ color: #52525b;
573
+ font-family: 'SF Mono', monospace;
574
+ overflow: hidden;
575
+ text-overflow: ellipsis;
576
+ white-space: nowrap;
577
+ }
578
+
579
+ /* ── Copy flash ── */
580
+ .di-copied-flash {
581
+ position: fixed;
582
+ bottom: 76px;
583
+ right: 20px;
584
+ background: #22c55e;
585
+ color: #fff;
586
+ padding: 8px 16px;
587
+ border-radius: 8px;
588
+ font-size: 13px;
589
+ font-weight: 500;
590
+ font-family: system-ui, sans-serif;
591
+ z-index: 2147483645;
592
+ animation: diFadeUp 2s ease forwards;
593
+ }
594
+ @keyframes diFadeUp {
595
+ 0% { opacity: 0; transform: translateY(6px); }
596
+ 15% { opacity: 1; transform: translateY(0); }
597
+ 75% { opacity: 1; }
598
+ 100% { opacity: 0; transform: translateY(-4px); }
599
+ }
600
+
601
+ /* ── Box model visualization overlays ── */
602
+ .di-bm-area {
603
+ position: fixed;
604
+ pointer-events: none;
605
+ z-index: 2147483636;
606
+ box-sizing: border-box;
607
+ display: none;
608
+ align-items: center;
609
+ justify-content: center;
610
+ font-family: 'SF Mono', 'Fira Code', monospace;
611
+ font-size: 9px;
612
+ font-weight: 600;
613
+ overflow: hidden;
614
+ white-space: nowrap;
615
+ }
616
+ /* Margin: orange */
617
+ .di-bm-mt, .di-bm-mr, .di-bm-mb, .di-bm-ml {
618
+ background: rgba(246, 178, 107, 0.38);
619
+ color: rgba(120, 70, 0, 0.9);
620
+ }
621
+ /* Padding: green */
622
+ .di-bm-pt, .di-bm-pr, .di-bm-pb, .di-bm-pl {
623
+ background: rgba(147, 196, 125, 0.38);
624
+ color: rgba(30, 90, 30, 0.9);
625
+ }
626
+ /* Content: blue */
627
+ .di-bm-content {
628
+ background: rgba(111, 168, 220, 0.15);
629
+ border: 1px dashed rgba(111, 168, 220, 0.5);
630
+ }
631
+ /* Gap: purple */
632
+ .di-bm-gap {
633
+ position: fixed;
634
+ pointer-events: none;
635
+ z-index: 2147483636;
636
+ background: rgba(167, 139, 250, 0.35);
637
+ display: flex;
638
+ align-items: center;
639
+ justify-content: center;
640
+ font-family: 'SF Mono', monospace;
641
+ font-size: 9px;
642
+ font-weight: 600;
643
+ color: rgba(109, 40, 217, 0.9);
644
+ overflow: hidden;
645
+ white-space: nowrap;
646
+ box-sizing: border-box;
647
+ }
648
+
649
+ /* ── Inspect cursor mode ── */
650
+ body.di-inspect-mode * { cursor: crosshair !important; }
651
+ `
652
+ document.head.appendChild(style)
653
+ }
654
+
655
+ export function createCogButton() {
656
+ const btn = document.createElement('button')
657
+ btn.id = INSPECTOR_ID
658
+ btn.title = 'Dev Inspector'
659
+ btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
660
+ <circle cx="12" cy="12" r="3"/>
661
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
662
+ </svg>`
663
+ document.body.appendChild(btn)
664
+ return btn
665
+ }
666
+
667
+ export function createOverlay() {
668
+ const el = document.createElement('div')
669
+ el.id = OVERLAY_ID
670
+ el.style.display = 'none'
671
+ document.body.appendChild(el)
672
+ return el
673
+ }
674
+
675
+ export function createTooltip() {
676
+ const el = document.createElement('div')
677
+ el.id = TOOLTIP_ID
678
+ el.style.display = 'none'
679
+ document.body.appendChild(el)
680
+ return el
681
+ }
682
+
683
+ export function createPanel() {
684
+ const el = document.createElement('div')
685
+ el.id = PANEL_ID
686
+ el.innerHTML = `
687
+ <div class="di-resize-handle" id="di-resize-handle"></div>
688
+ <div class="di-panel-header">
689
+ <h3>Dev Inspector</h3>
690
+ <button class="di-collapse-btn" id="di-collapse-btn" title="Collapse panel">
691
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
692
+ <polyline points="9 18 15 12 9 6"/>
693
+ </svg>
694
+ </button>
695
+ <button class="di-close" id="di-close-panel">✕</button>
696
+ </div>
697
+ <div class="di-element-bar" id="di-element-bar" style="display:none;">
698
+ <span class="di-element-label" id="di-element-label"></span>
699
+ <button class="di-reinspect-btn" id="di-reinspect-btn">↖ Pick</button>
700
+ </div>
701
+ <div id="di-panel-content">
702
+ <div class="di-panel-empty">
703
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#6366f1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
704
+ <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
705
+ </svg>
706
+ <p>Click any element on the page to inspect and edit it</p>
707
+ </div>
708
+ </div>
709
+ <div class="di-panel-footer" id="di-panel-footer" style="display:none;">
710
+ <div class="di-changes-log" id="di-changes-log" style="display:none;">
711
+ <div id="di-changes-list"></div>
712
+ </div>
713
+ <button class="di-changes-toggle" id="di-changes-toggle">
714
+ <span id="di-feedback-count">No changes yet</span>
715
+ <span class="di-toggle-arrow" id="di-toggle-arrow">▾</span>
716
+ </button>
717
+ <button class="di-btn di-btn-primary" id="di-copy-feedback">Copy Feedback</button>
718
+ <button class="di-btn di-btn-ghost" id="di-clear-changes">Clear All Changes</button>
719
+ </div>
720
+ `
721
+ document.body.appendChild(el)
722
+ return el
723
+ }
724
+
725
+ export function createBoxModelOverlay() {
726
+ const container = document.createElement('div')
727
+ container.id = '__dev_inspector_boxmodel__'
728
+ container.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:2147483636;'
729
+ const keys = ['mt','mr','mb','ml','pt','pr','pb','pl','content']
730
+ keys.forEach(k => {
731
+ const div = document.createElement('div')
732
+ div.id = `__di_bm_${k}__`
733
+ div.className = `di-bm-area di-bm-${k}`
734
+ container.appendChild(div)
735
+ })
736
+ document.body.appendChild(container)
737
+ return container
738
+ }
739
+
740
+ // Fix 1: use raw viewport coords (fixed positioning needs no scrollY offset)
741
+ export function updateOverlay(overlay, el) {
742
+ if (!el) { overlay.style.display = 'none'; return }
743
+ const rect = el.getBoundingClientRect()
744
+ overlay.style.display = 'block'
745
+ overlay.style.top = rect.top + 'px'
746
+ overlay.style.left = rect.left + 'px'
747
+ overlay.style.width = rect.width + 'px'
748
+ overlay.style.height = rect.height + 'px'
749
+ }
750
+
751
+ export function positionTooltip(tooltip, x, y) {
752
+ const margin = 16
753
+ const tw = tooltip.offsetWidth || 230
754
+ const th = tooltip.offsetHeight || 180
755
+ const vw = window.innerWidth
756
+ const vh = window.innerHeight
757
+
758
+ let left = x + 14
759
+ let top = y + 14
760
+
761
+ if (left + tw > vw - margin) left = x - tw - 14
762
+ if (top + th > vh - margin) top = y - th - 14
763
+ if (left < margin) left = margin
764
+ if (top < margin) top = margin
765
+
766
+ tooltip.style.left = left + 'px'
767
+ tooltip.style.top = top + 'px'
768
+ }
769
+
770
+ export function showCopiedFlash(message = 'Feedback copied!') {
771
+ const existing = document.querySelector('.di-copied-flash')
772
+ if (existing) existing.remove()
773
+ const el = document.createElement('div')
774
+ el.className = 'di-copied-flash'
775
+ el.textContent = message
776
+ document.body.appendChild(el)
777
+ setTimeout(() => el.remove(), 2100)
778
+ }
779
+
780
+ export function colorToHex(color) {
781
+ if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') return null
782
+ if (color.startsWith('#')) return color
783
+ const canvas = document.createElement('canvas')
784
+ canvas.width = canvas.height = 1
785
+ const ctx = canvas.getContext('2d')
786
+ ctx.fillStyle = color
787
+ ctx.fillRect(0, 0, 1, 1)
788
+ const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data
789
+ return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')
790
+ }
791
+
792
+ export function buildSelector(el) {
793
+ if (el.id) return `#${el.id}`
794
+ const tag = el.tagName.toLowerCase()
795
+ const classes = [...el.classList]
796
+ .filter(c => !c.startsWith('di-') && !c.startsWith('__dev'))
797
+ .slice(0, 2)
798
+ .map(c => `.${c}`)
799
+ .join('')
800
+ const parent = el.parentElement
801
+ if (!parent || parent === document.body) return `${tag}${classes}`
802
+ return `${buildSelector(parent)} > ${tag}${classes}`
803
+ }