@tekyzinc/gsd-t 2.70.16 → 2.71.11

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,941 @@
1
+ /**
2
+ * GSD-T Design Review — Inject Script
3
+ * Runs inside the proxied app page (iframe). Provides:
4
+ * - Hover highlight overlay
5
+ * - Click to lock element selection
6
+ * - Sends computed styles to parent review UI via postMessage
7
+ * - Receives style changes from parent and applies them live
8
+ */
9
+ (function () {
10
+ "use strict";
11
+ if (window.__gsdtReviewInjected) return;
12
+ window.__gsdtReviewInjected = true;
13
+
14
+ let active = false;
15
+ let locked = false;
16
+ let lockedEl = null;
17
+ let currentEl = null;
18
+
19
+ // ── Overlay elements ────────────────────────────────────────────
20
+ const overlay = document.createElement("div");
21
+ overlay.id = "__gsdt-overlay";
22
+ Object.assign(overlay.style, {
23
+ position: "fixed",
24
+ pointerEvents: "none",
25
+ zIndex: "999998",
26
+ border: "2px solid #3b82f6",
27
+ backgroundColor: "rgba(59, 130, 246, 0.08)",
28
+ transition: "all 0.1s ease",
29
+ display: "none",
30
+ });
31
+ document.body.appendChild(overlay);
32
+
33
+ const label = document.createElement("div");
34
+ label.id = "__gsdt-label";
35
+ Object.assign(label.style, {
36
+ position: "fixed",
37
+ zIndex: "999999",
38
+ pointerEvents: "none",
39
+ backgroundColor: "#1e293b",
40
+ color: "#f8fafc",
41
+ fontSize: "11px",
42
+ fontFamily: "monospace",
43
+ padding: "2px 6px",
44
+ borderRadius: "3px",
45
+ whiteSpace: "nowrap",
46
+ display: "none",
47
+ });
48
+ document.body.appendChild(label);
49
+
50
+ // ── Margin/padding guides ───────────────────────────────────────
51
+ const marginOverlay = document.createElement("div");
52
+ marginOverlay.id = "__gsdt-margin";
53
+ Object.assign(marginOverlay.style, {
54
+ position: "fixed",
55
+ pointerEvents: "none",
56
+ zIndex: "999997",
57
+ border: "1px dashed #f97316",
58
+ backgroundColor: "rgba(249, 115, 22, 0.06)",
59
+ display: "none",
60
+ });
61
+ document.body.appendChild(marginOverlay);
62
+
63
+ const paddingOverlay = document.createElement("div");
64
+ paddingOverlay.id = "__gsdt-padding";
65
+ Object.assign(paddingOverlay.style, {
66
+ position: "fixed",
67
+ pointerEvents: "none",
68
+ zIndex: "999997",
69
+ border: "1px dashed #22c55e",
70
+ backgroundColor: "rgba(34, 197, 94, 0.06)",
71
+ display: "none",
72
+ });
73
+ document.body.appendChild(paddingOverlay);
74
+
75
+ // ── Zone highlight flash ────────────────────────────────────────
76
+ const zoneContainer = document.createElement("div");
77
+ zoneContainer.id = "__gsdt-zone-container";
78
+ Object.assign(zoneContainer.style, {
79
+ position: "fixed",
80
+ top: "0", left: "0",
81
+ width: "100%", height: "100%",
82
+ pointerEvents: "none",
83
+ zIndex: "999999",
84
+ transition: "opacity 0.3s ease",
85
+ });
86
+ document.body.appendChild(zoneContainer);
87
+
88
+ let zoneFlashTimer = null;
89
+
90
+ function clearFlash() {
91
+ zoneContainer.innerHTML = "";
92
+ zoneContainer.style.opacity = "1";
93
+ }
94
+
95
+ function scheduleFlashFade() {
96
+ clearTimeout(zoneFlashTimer);
97
+ zoneFlashTimer = setTimeout(() => {
98
+ zoneContainer.style.opacity = "0";
99
+ setTimeout(clearFlash, 300);
100
+ }, 1500);
101
+ }
102
+
103
+ function addFlashDiv(top, left, width, height, styles) {
104
+ if (height < 1) height = 3;
105
+ if (width < 1) width = 3;
106
+ const div = document.createElement("div");
107
+ Object.assign(div.style, {
108
+ position: "fixed",
109
+ top: top + "px", left: left + "px",
110
+ width: width + "px", height: height + "px",
111
+ pointerEvents: "none",
112
+ boxSizing: "border-box",
113
+ ...styles,
114
+ });
115
+ zoneContainer.appendChild(div);
116
+ return div;
117
+ }
118
+
119
+ function flashZone(el, property) {
120
+ if (!el) return;
121
+ clearFlash();
122
+
123
+ const rect = el.getBoundingClientRect();
124
+ const s = getComputedStyle(el);
125
+ const pt = parseFloat(s.paddingTop) || 0;
126
+ const pr = parseFloat(s.paddingRight) || 0;
127
+ const pb = parseFloat(s.paddingBottom) || 0;
128
+ const pl = parseFloat(s.paddingLeft) || 0;
129
+ const mt = parseFloat(s.marginTop) || 0;
130
+ const mr = parseFloat(s.marginRight) || 0;
131
+ const mb = parseFloat(s.marginBottom) || 0;
132
+ const ml = parseFloat(s.marginLeft) || 0;
133
+
134
+ // Content box coordinates
135
+ const cx = rect.left + pl, cy = rect.top + pt;
136
+ const cw = rect.width - pl - pr, ch = rect.height - pt - pb;
137
+
138
+ // ── Margin zones (orange) ──
139
+ if (property === "marginTop") {
140
+ addFlashDiv(rect.top - mt, rect.left, rect.width, mt, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
141
+ } else if (property === "marginBottom") {
142
+ addFlashDiv(rect.bottom, rect.left, rect.width, mb, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
143
+ } else if (property === "marginLeft") {
144
+ addFlashDiv(rect.top, rect.left - ml, ml, rect.height, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
145
+ } else if (property === "marginRight") {
146
+ addFlashDiv(rect.top, rect.right, mr, rect.height, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
147
+
148
+ // ── Padding zones (green) ──
149
+ } else if (property === "paddingTop") {
150
+ addFlashDiv(rect.top, rect.left, rect.width, pt, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
151
+ } else if (property === "paddingBottom") {
152
+ addFlashDiv(rect.bottom - pb, rect.left, rect.width, pb, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
153
+ } else if (property === "paddingLeft") {
154
+ addFlashDiv(rect.top, rect.left, pl, rect.height, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
155
+ } else if (property === "paddingRight") {
156
+ addFlashDiv(rect.top, rect.right - pr, pr, rect.height, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
157
+
158
+ // ── Height: horizontal edge lines + light fill ──
159
+ } else if (property === "height") {
160
+ // Light fill for content area
161
+ addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(59, 130, 246, 0.15)" });
162
+ // Top edge — bright blue line
163
+ addFlashDiv(cy, cx - 20, cw + 40, 2, { backgroundColor: "#3b82f6" });
164
+ // Bottom edge — bright blue line
165
+ addFlashDiv(cy + ch - 2, cx - 20, cw + 40, 2, { backgroundColor: "#3b82f6" });
166
+ // Dimension label
167
+ const lbl = addFlashDiv(cy + ch / 2 - 9, cx + cw + 6, 50, 18, {
168
+ backgroundColor: "#1e293b", borderRadius: "3px",
169
+ fontSize: "10px", color: "#3b82f6", fontFamily: "monospace",
170
+ fontWeight: "600", textAlign: "center", lineHeight: "18px",
171
+ });
172
+ lbl.textContent = Math.round(ch) + "px";
173
+
174
+ // ── Width: vertical edge lines + light fill ──
175
+ } else if (property === "width") {
176
+ addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(59, 130, 246, 0.15)" });
177
+ // Left edge
178
+ addFlashDiv(cy - 20, cx, 2, ch + 40, { backgroundColor: "#3b82f6" });
179
+ // Right edge
180
+ addFlashDiv(cy - 20, cx + cw - 2, 2, ch + 40, { backgroundColor: "#3b82f6" });
181
+ // Dimension label
182
+ const lbl = addFlashDiv(cy - 18, cx + cw / 2 - 20, 50, 16, {
183
+ backgroundColor: "#1e293b", borderRadius: "3px",
184
+ fontSize: "10px", color: "#3b82f6", fontFamily: "monospace",
185
+ fontWeight: "600", textAlign: "center", lineHeight: "16px",
186
+ });
187
+ lbl.textContent = Math.round(cw) + "px";
188
+
189
+ // ── lineHeight: amber horizontal lines at each text line ──
190
+ } else if (property === "lineHeight") {
191
+ const lh = parseFloat(s.lineHeight) || parseFloat(s.fontSize) * 1.2;
192
+ const lines = Math.max(1, Math.round(ch / lh));
193
+ for (let i = 0; i <= lines; i++) {
194
+ const y = cy + i * lh;
195
+ if (y > cy + ch + 1) break;
196
+ addFlashDiv(y, cx - 10, cw + 20, 2, {
197
+ backgroundColor: i === 0 || i === lines ? "#f59e0b" : "rgba(245, 158, 11, 0.5)",
198
+ });
199
+ }
200
+ // Fill between first two lines to show one line-height unit
201
+ if (lines >= 1) {
202
+ addFlashDiv(cy, cx, cw, Math.min(lh, ch), { backgroundColor: "rgba(245, 158, 11, 0.15)" });
203
+ const lbl = addFlashDiv(cy + 2, cx + cw + 6, 50, 16, {
204
+ backgroundColor: "#1e293b", borderRadius: "3px",
205
+ fontSize: "10px", color: "#f59e0b", fontFamily: "monospace",
206
+ fontWeight: "600", textAlign: "center", lineHeight: "16px",
207
+ });
208
+ lbl.textContent = Math.round(lh) + "px";
209
+ }
210
+
211
+ // ── fontSize: cyan underline beneath text ──
212
+ } else if (property === "fontSize") {
213
+ const fs = parseFloat(s.fontSize) || 14;
214
+ // Highlight text area with cyan
215
+ addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(6, 182, 212, 0.15)" });
216
+ // Underline at text baseline
217
+ addFlashDiv(cy + ch - 2, cx, cw, 2, { backgroundColor: "#06b6d4" });
218
+ const lbl = addFlashDiv(cy - 16, cx, 40, 14, {
219
+ backgroundColor: "#1e293b", borderRadius: "3px",
220
+ fontSize: "10px", color: "#06b6d4", fontFamily: "monospace",
221
+ fontWeight: "600", textAlign: "center", lineHeight: "14px",
222
+ });
223
+ lbl.textContent = Math.round(fs) + "px";
224
+
225
+ // ── fontWeight / fontFamily / letterSpacing: highlight text with cyan outline ──
226
+ } else if (property === "fontWeight" || property === "fontFamily" || property === "letterSpacing") {
227
+ addFlashDiv(cy, cx, cw, ch, {
228
+ border: "2px solid #06b6d4",
229
+ backgroundColor: "rgba(6, 182, 212, 0.1)",
230
+ borderRadius: "2px",
231
+ });
232
+
233
+ // ── textAlign: arrow showing alignment direction ──
234
+ } else if (property === "textAlign") {
235
+ const align = s.textAlign;
236
+ let arrowX = cx;
237
+ if (align === "center") arrowX = cx + cw / 2 - 15;
238
+ else if (align === "right" || align === "end") arrowX = cx + cw - 35;
239
+ // Arrow indicator
240
+ addFlashDiv(cy + ch / 2 - 1, cx, cw, 2, { backgroundColor: "rgba(6, 182, 212, 0.4)" });
241
+ const arrow = addFlashDiv(cy + ch / 2 - 9, arrowX, 30, 18, {
242
+ backgroundColor: "#06b6d4", borderRadius: "3px",
243
+ fontSize: "10px", color: "#fff", fontFamily: "monospace",
244
+ fontWeight: "700", textAlign: "center", lineHeight: "18px",
245
+ });
246
+ arrow.textContent = align === "center" ? "◆" : align === "right" || align === "end" ? "→▐" : "▐←";
247
+
248
+ // ── color: colored border around text content ──
249
+ } else if (property === "color") {
250
+ addFlashDiv(cy, cx, cw, ch, {
251
+ border: "2px solid " + s.color,
252
+ backgroundColor: "transparent",
253
+ borderRadius: "2px",
254
+ });
255
+
256
+ // ── gap: purple bars in the spaces between children ──
257
+ } else if (property === "gap") {
258
+ const gap = parseFloat(s.gap) || 0;
259
+ const isCol = s.flexDirection === "column";
260
+ const children = Array.from(el.children).filter(c => !["SCRIPT", "STYLE"].includes(c.tagName));
261
+ for (let i = 0; i < children.length - 1; i++) {
262
+ const a = children[i].getBoundingClientRect();
263
+ const b = children[i + 1].getBoundingClientRect();
264
+ if (isCol) {
265
+ addFlashDiv(a.bottom, rect.left, rect.width, b.top - a.bottom, {
266
+ backgroundColor: "rgba(168, 85, 247, 0.4)",
267
+ });
268
+ } else {
269
+ addFlashDiv(rect.top, a.right, b.left - a.right, rect.height, {
270
+ backgroundColor: "rgba(168, 85, 247, 0.4)",
271
+ });
272
+ }
273
+ }
274
+ if (gap > 0) {
275
+ const lbl = addFlashDiv(rect.top - 16, rect.left, 40, 14, {
276
+ backgroundColor: "#1e293b", borderRadius: "3px",
277
+ fontSize: "10px", color: "#a855f7", fontFamily: "monospace",
278
+ fontWeight: "600", textAlign: "center", lineHeight: "14px",
279
+ });
280
+ lbl.textContent = Math.round(gap) + "px";
281
+ }
282
+
283
+ // ── border / borderRadius: dashed outline ──
284
+ } else if (property === "border" || property === "borderRadius") {
285
+ addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
286
+ border: "2px dashed #eab308",
287
+ backgroundColor: "transparent",
288
+ borderRadius: property === "borderRadius" ? s.borderRadius : "0",
289
+ });
290
+
291
+ // ── backgroundColor: flash fill ──
292
+ } else if (property === "backgroundColor") {
293
+ addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
294
+ backgroundColor: "rgba(234, 179, 8, 0.25)",
295
+ border: "2px solid rgba(234, 179, 8, 0.6)",
296
+ });
297
+
298
+ // ── boxShadow / opacity: subtle full-element glow ──
299
+ } else if (property === "boxShadow" || property === "opacity") {
300
+ addFlashDiv(rect.top - 4, rect.left - 4, rect.width + 8, rect.height + 8, {
301
+ border: "2px solid rgba(168, 85, 247, 0.5)",
302
+ backgroundColor: "rgba(168, 85, 247, 0.08)",
303
+ borderRadius: "4px",
304
+ });
305
+
306
+ // ── display / flexDirection / alignItems / justifyContent: layout arrow ──
307
+ } else if (property === "display" || property === "flexDirection" ||
308
+ property === "alignItems" || property === "justifyContent" ||
309
+ property === "gridTemplateColumns" || property === "gridTemplateRows") {
310
+ addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
311
+ border: "2px solid rgba(168, 85, 247, 0.6)",
312
+ backgroundColor: "rgba(168, 85, 247, 0.08)",
313
+ });
314
+ if (property === "flexDirection") {
315
+ const isCol = s.flexDirection === "column";
316
+ const arrow = addFlashDiv(
317
+ isCol ? cy : cy + ch / 2 - 9,
318
+ isCol ? cx + cw / 2 - 9 : cx,
319
+ isCol ? 18 : cw,
320
+ isCol ? ch : 18,
321
+ {
322
+ fontSize: "14px", color: "#a855f7", fontWeight: "700",
323
+ textAlign: "center", lineHeight: isCol ? ch + "px" : "18px",
324
+ }
325
+ );
326
+ arrow.textContent = isCol ? "↓" : "→";
327
+ }
328
+
329
+ // ── position / top / left / overflow: dotted outline ──
330
+ } else if (property === "position" || property === "top" || property === "left" || property === "overflow") {
331
+ addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
332
+ border: "2px dotted rgba(148, 163, 184, 0.6)",
333
+ backgroundColor: "rgba(148, 163, 184, 0.08)",
334
+ });
335
+
336
+ // ── Generic fallback ──
337
+ } else {
338
+ addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
339
+ backgroundColor: "rgba(59, 130, 246, 0.2)",
340
+ border: "1px solid rgba(59, 130, 246, 0.4)",
341
+ });
342
+ }
343
+
344
+ scheduleFlashFade();
345
+ }
346
+
347
+ // ── Style extraction ────────────────────────────────────────────
348
+ function getElementPath(el) {
349
+ const parts = [];
350
+ let cur = el;
351
+ while (cur && cur !== document.body) {
352
+ let desc = cur.tagName.toLowerCase();
353
+ if (cur.id) desc += `#${cur.id}`;
354
+ else if (cur.className && typeof cur.className === "string") {
355
+ const cls = cur.className.trim().split(/\s+/).slice(0, 3).join(".");
356
+ if (cls) desc += `.${cls}`;
357
+ }
358
+ parts.unshift(desc);
359
+ cur = cur.parentElement;
360
+ }
361
+ return parts.join(" > ");
362
+ }
363
+
364
+ function extractStyles(el) {
365
+ const s = getComputedStyle(el);
366
+ const rect = el.getBoundingClientRect();
367
+ return {
368
+ // Box model
369
+ width: Math.round(rect.width) + "px",
370
+ height: Math.round(rect.height) + "px",
371
+ paddingTop: s.paddingTop,
372
+ paddingRight: s.paddingRight,
373
+ paddingBottom: s.paddingBottom,
374
+ paddingLeft: s.paddingLeft,
375
+ marginTop: s.marginTop,
376
+ marginRight: s.marginRight,
377
+ marginBottom: s.marginBottom,
378
+ marginLeft: s.marginLeft,
379
+ // Layout
380
+ display: s.display,
381
+ flexDirection: s.flexDirection,
382
+ alignItems: s.alignItems,
383
+ justifyContent: s.justifyContent,
384
+ gap: s.gap,
385
+ gridTemplateColumns: s.gridTemplateColumns,
386
+ gridTemplateRows: s.gridTemplateRows,
387
+ // Position
388
+ position: s.position,
389
+ top: s.top,
390
+ left: s.left,
391
+ // Visual
392
+ backgroundColor: s.backgroundColor,
393
+ color: s.color,
394
+ borderRadius: s.borderRadius,
395
+ border: s.border,
396
+ boxShadow: s.boxShadow,
397
+ // Typography
398
+ fontSize: s.fontSize,
399
+ fontWeight: s.fontWeight,
400
+ fontFamily: s.fontFamily,
401
+ lineHeight: s.lineHeight,
402
+ letterSpacing: s.letterSpacing,
403
+ textAlign: s.textAlign,
404
+ // Overflow
405
+ overflow: s.overflow,
406
+ // Opacity
407
+ opacity: s.opacity,
408
+ };
409
+ }
410
+
411
+ function extractBoxModel(el) {
412
+ const s = getComputedStyle(el);
413
+ const rect = el.getBoundingClientRect();
414
+ return {
415
+ content: { width: Math.round(rect.width), height: Math.round(rect.height) },
416
+ padding: {
417
+ top: parseFloat(s.paddingTop) || 0,
418
+ right: parseFloat(s.paddingRight) || 0,
419
+ bottom: parseFloat(s.paddingBottom) || 0,
420
+ left: parseFloat(s.paddingLeft) || 0,
421
+ },
422
+ margin: {
423
+ top: parseFloat(s.marginTop) || 0,
424
+ right: parseFloat(s.marginRight) || 0,
425
+ bottom: parseFloat(s.marginBottom) || 0,
426
+ left: parseFloat(s.marginLeft) || 0,
427
+ },
428
+ border: {
429
+ top: parseFloat(s.borderTopWidth) || 0,
430
+ right: parseFloat(s.borderRightWidth) || 0,
431
+ bottom: parseFloat(s.borderBottomWidth) || 0,
432
+ left: parseFloat(s.borderLeftWidth) || 0,
433
+ },
434
+ };
435
+ }
436
+
437
+ // ── Overlay positioning ─────────────────────────────────────────
438
+ function positionOverlay(el) {
439
+ if (!el) return;
440
+ const rect = el.getBoundingClientRect();
441
+ const s = getComputedStyle(el);
442
+
443
+ // Main element overlay
444
+ Object.assign(overlay.style, {
445
+ display: "block",
446
+ top: rect.top + "px",
447
+ left: rect.left + "px",
448
+ width: rect.width + "px",
449
+ height: rect.height + "px",
450
+ });
451
+
452
+ // Label
453
+ const tagName = el.tagName.toLowerCase();
454
+ const cls = (typeof el.className === "string" ? el.className : "").trim().split(/\s+/).slice(0, 2).join(".");
455
+ const dim = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
456
+ label.textContent = `${tagName}${cls ? "." + cls : ""} — ${dim}`;
457
+ Object.assign(label.style, {
458
+ display: "block",
459
+ top: Math.max(0, rect.top - 20) + "px",
460
+ left: rect.left + "px",
461
+ });
462
+
463
+ // Margin overlay
464
+ const mt = parseFloat(s.marginTop) || 0;
465
+ const mr = parseFloat(s.marginRight) || 0;
466
+ const mb = parseFloat(s.marginBottom) || 0;
467
+ const ml = parseFloat(s.marginLeft) || 0;
468
+ if (mt || mr || mb || ml) {
469
+ Object.assign(marginOverlay.style, {
470
+ display: "block",
471
+ top: (rect.top - mt) + "px",
472
+ left: (rect.left - ml) + "px",
473
+ width: (rect.width + ml + mr) + "px",
474
+ height: (rect.height + mt + mb) + "px",
475
+ });
476
+ } else {
477
+ marginOverlay.style.display = "none";
478
+ }
479
+
480
+ // Padding overlay
481
+ const pt = parseFloat(s.paddingTop) || 0;
482
+ const pr = parseFloat(s.paddingRight) || 0;
483
+ const pb = parseFloat(s.paddingBottom) || 0;
484
+ const pl = parseFloat(s.paddingLeft) || 0;
485
+ if (pt || pr || pb || pl) {
486
+ Object.assign(paddingOverlay.style, {
487
+ display: "block",
488
+ top: (rect.top + pt) + "px",
489
+ left: (rect.left + pl) + "px",
490
+ width: (rect.width - pl - pr) + "px",
491
+ height: (rect.height - pt - pb) + "px",
492
+ });
493
+ } else {
494
+ paddingOverlay.style.display = "none";
495
+ }
496
+ }
497
+
498
+ function hideOverlay() {
499
+ overlay.style.display = "none";
500
+ label.style.display = "none";
501
+ marginOverlay.style.display = "none";
502
+ paddingOverlay.style.display = "none";
503
+ }
504
+
505
+ // ── Reposition on scroll/resize/zoom ─────────────────────────────
506
+ let rafPending = false;
507
+ function refreshOverlay() {
508
+ if (rafPending) return;
509
+ rafPending = true;
510
+ requestAnimationFrame(() => {
511
+ rafPending = false;
512
+ const el = locked ? lockedEl : currentEl;
513
+ if (el) positionOverlay(el);
514
+ });
515
+ }
516
+
517
+ window.addEventListener("scroll", refreshOverlay, { passive: true, capture: true });
518
+ window.addEventListener("resize", refreshOverlay, { passive: true });
519
+ document.addEventListener("scroll", refreshOverlay, { passive: true, capture: true });
520
+
521
+ // ── Event handlers ──────────────────────────────────────────────
522
+ function isOwnElement(el) {
523
+ return el === overlay || el === label || el === marginOverlay || el === paddingOverlay;
524
+ }
525
+
526
+ document.addEventListener("mousemove", (e) => {
527
+ if (!active || locked) return;
528
+ const el = document.elementFromPoint(e.clientX, e.clientY);
529
+ if (!el || isOwnElement(el)) return;
530
+ if (el === currentEl) return;
531
+ currentEl = el;
532
+ positionOverlay(el);
533
+ // Send styles to parent
534
+ window.parent.postMessage({
535
+ type: "gsdt-hover",
536
+ path: getElementPath(el),
537
+ styles: extractStyles(el),
538
+ boxModel: extractBoxModel(el),
539
+ tagName: el.tagName.toLowerCase(),
540
+ className: typeof el.className === "string" ? el.className : "",
541
+ textContent: (el.textContent || "").trim().substring(0, 100),
542
+ childCount: el.children.length,
543
+ }, "*");
544
+ }, { passive: true });
545
+
546
+ document.addEventListener("click", (e) => {
547
+ if (!active) return;
548
+ const el = document.elementFromPoint(e.clientX, e.clientY);
549
+ if (!el || isOwnElement(el)) return;
550
+
551
+ e.preventDefault();
552
+ e.stopPropagation();
553
+
554
+ if (locked && lockedEl === el) {
555
+ // Unlock
556
+ locked = false;
557
+ lockedEl = null;
558
+ overlay.style.borderColor = "#3b82f6";
559
+ overlay.style.backgroundColor = "rgba(59, 130, 246, 0.08)";
560
+ return;
561
+ }
562
+
563
+ // Lock to this element
564
+ locked = true;
565
+ lockedEl = el;
566
+ currentEl = el;
567
+ overlay.style.borderColor = "#ef4444";
568
+ overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
569
+ positionOverlay(el);
570
+
571
+ window.parent.postMessage({
572
+ type: "gsdt-select",
573
+ path: getElementPath(el),
574
+ styles: extractStyles(el),
575
+ boxModel: extractBoxModel(el),
576
+ tagName: el.tagName.toLowerCase(),
577
+ className: typeof el.className === "string" ? el.className : "",
578
+ textContent: (el.textContent || "").trim().substring(0, 100),
579
+ childCount: el.children.length,
580
+ }, "*");
581
+ }, { capture: true });
582
+
583
+ // Keyboard: Escape to unlock, Ctrl+Shift+I to toggle
584
+ document.addEventListener("keydown", (e) => {
585
+ if (e.key === "Escape" && locked) {
586
+ locked = false;
587
+ lockedEl = null;
588
+ overlay.style.borderColor = "#3b82f6";
589
+ overlay.style.backgroundColor = "rgba(59, 130, 246, 0.08)";
590
+ }
591
+ });
592
+
593
+ // ── Messages from parent ────────────────────────────────────────
594
+ window.addEventListener("message", (e) => {
595
+ const msg = e.data;
596
+ if (!msg || !msg.type) return;
597
+
598
+ switch (msg.type) {
599
+ case "gsdt-activate":
600
+ active = true;
601
+ document.body.style.cursor = "crosshair";
602
+ break;
603
+
604
+ case "gsdt-deactivate":
605
+ active = false;
606
+ locked = false;
607
+ lockedEl = null;
608
+ currentEl = null;
609
+ document.body.style.cursor = "";
610
+ hideOverlay();
611
+ break;
612
+
613
+ case "gsdt-set-style":
614
+ // Apply a style change to the locked element (+ smart propagation)
615
+ if (lockedEl && msg.property && msg.value !== undefined) {
616
+ const cssName = msg.property.replace(/([A-Z])/g, "-$1").toLowerCase();
617
+ const propagate = msg.propagate !== false; // default true
618
+ let propagatedCount = 0;
619
+
620
+ // Apply to primary element
621
+ lockedEl.style.setProperty(cssName, msg.value, "important");
622
+
623
+ // Typography props cascade to descendants
624
+ const inheritProps = new Set(["font-size", "font-weight", "font-family", "line-height",
625
+ "letter-spacing", "text-align", "color"]);
626
+ if (inheritProps.has(cssName)) {
627
+ for (const child of lockedEl.querySelectorAll("*")) {
628
+ child.style.setProperty(cssName, msg.value, "important");
629
+ }
630
+ }
631
+
632
+ // Smart propagation for table and flex elements
633
+ if (propagate) {
634
+ const tag = lockedEl.tagName.toLowerCase();
635
+ const parent = lockedEl.parentElement;
636
+
637
+ // Column-scoped props: apply to same column position across all rows
638
+ const columnProps = new Set(["text-align", "width", "min-width", "max-width"]);
639
+ // Row-scoped props: apply to the tr (or all trs), not individual cells
640
+ const rowProps = new Set(["height", "min-height", "max-height"]);
641
+
642
+ if (tag === "td" || tag === "th") {
643
+ const section = lockedEl.closest("tbody") || lockedEl.closest("thead") || lockedEl.closest("tfoot");
644
+ const row = lockedEl.closest("tr");
645
+
646
+ if (columnProps.has(cssName) && section && row) {
647
+ // Column-scoped: find this cell's column index, apply to same column in all rows
648
+ const colIdx = Array.from(row.children).indexOf(lockedEl);
649
+ const rows = section.querySelectorAll("tr");
650
+ for (const r of rows) {
651
+ const cell = r.children[colIdx];
652
+ if (!cell || cell === lockedEl) continue;
653
+ cell.style.setProperty(cssName, msg.value, "important");
654
+ if (inheritProps.has(cssName)) {
655
+ for (const child of cell.querySelectorAll("*")) {
656
+ child.style.setProperty(cssName, msg.value, "important");
657
+ }
658
+ }
659
+ propagatedCount++;
660
+ }
661
+ } else if (rowProps.has(cssName) && section) {
662
+ // Row-scoped: set on the parent tr, propagate to all trs in section
663
+ if (row) {
664
+ row.style.setProperty(cssName, msg.value, "important");
665
+ }
666
+ const rows = section.querySelectorAll("tr");
667
+ for (const r of rows) {
668
+ if (r === row) continue;
669
+ r.style.setProperty(cssName, msg.value, "important");
670
+ propagatedCount++;
671
+ }
672
+ } else if (section) {
673
+ // Everything else (padding, font-size, color, etc.): all cells in section
674
+ const cells = section.querySelectorAll(tag);
675
+ for (const cell of cells) {
676
+ if (cell === lockedEl) continue;
677
+ cell.style.setProperty(cssName, msg.value, "important");
678
+ if (inheritProps.has(cssName)) {
679
+ for (const child of cell.querySelectorAll("*")) {
680
+ child.style.setProperty(cssName, msg.value, "important");
681
+ }
682
+ }
683
+ propagatedCount++;
684
+ }
685
+ }
686
+ } else if (tag === "tr") {
687
+ // Propagate to all rows in the same section (tbody, thead, or tfoot)
688
+ const section = lockedEl.closest("tbody") || lockedEl.closest("thead") || lockedEl.closest("tfoot");
689
+ if (section) {
690
+ const rows = section.querySelectorAll("tr");
691
+ for (const row of rows) {
692
+ if (row === lockedEl) continue;
693
+ row.style.setProperty(cssName, msg.value, "important");
694
+ propagatedCount++;
695
+ }
696
+ }
697
+ } else if (tag === "div" && parent) {
698
+ // For bar segments: propagate to sibling divs in the same flex container
699
+ const parentStyle = getComputedStyle(parent);
700
+ if ((parentStyle.display === "flex" || parentStyle.display === "inline-flex") &&
701
+ parent.classList.contains("overflow-hidden")) {
702
+ for (const sib of parent.children) {
703
+ if (sib === lockedEl || sib.tagName !== "DIV") continue;
704
+ sib.style.setProperty(cssName, msg.value, "important");
705
+ propagatedCount++;
706
+ }
707
+ }
708
+ }
709
+ }
710
+
711
+ // Determine propagation scope label
712
+ let propagateScope = "";
713
+ if (propagatedCount > 0) {
714
+ const tag = lockedEl.tagName.toLowerCase();
715
+ const columnProps = new Set(["text-align", "width", "min-width", "max-width"]);
716
+ const rowProps = new Set(["height", "min-height", "max-height"]);
717
+ if ((tag === "td" || tag === "th") && columnProps.has(cssName)) {
718
+ const row = lockedEl.closest("tr");
719
+ const colIdx = row ? Array.from(row.children).indexOf(lockedEl) + 1 : 0;
720
+ propagateScope = "Col " + colIdx;
721
+ } else if ((tag === "td" || tag === "th") && rowProps.has(cssName)) {
722
+ propagateScope = "all rows";
723
+ } else if (tag === "tr") {
724
+ propagateScope = "all rows";
725
+ } else {
726
+ propagateScope = "similar";
727
+ }
728
+ }
729
+
730
+ positionOverlay(lockedEl);
731
+ window.parent.postMessage({
732
+ type: "gsdt-style-updated",
733
+ path: getElementPath(lockedEl),
734
+ property: msg.property,
735
+ value: msg.value,
736
+ styles: extractStyles(lockedEl),
737
+ boxModel: extractBoxModel(lockedEl),
738
+ propagated: propagatedCount,
739
+ propagateScope,
740
+ }, "*");
741
+ }
742
+ break;
743
+
744
+ case "gsdt-highlight-zone":
745
+ if (lockedEl && msg.property) {
746
+ flashZone(lockedEl, msg.property);
747
+ }
748
+ break;
749
+
750
+ case "gsdt-reset-styles":
751
+ // Reset all inline style overrides on the locked element and its children
752
+ if (lockedEl) {
753
+ lockedEl.style.cssText = "";
754
+ for (const child of lockedEl.querySelectorAll("*")) {
755
+ child.style.cssText = "";
756
+ }
757
+ positionOverlay(lockedEl);
758
+ window.parent.postMessage({
759
+ type: "gsdt-style-updated",
760
+ path: getElementPath(lockedEl),
761
+ styles: extractStyles(lockedEl),
762
+ boxModel: extractBoxModel(lockedEl),
763
+ }, "*");
764
+ }
765
+ break;
766
+
767
+ case "gsdt-scroll-to":
768
+ // Scroll to a specific element by selector
769
+ if (msg.selector) {
770
+ const el = document.querySelector(msg.selector);
771
+ if (el) {
772
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
773
+ // Auto-select it
774
+ locked = true;
775
+ lockedEl = el;
776
+ currentEl = el;
777
+ overlay.style.borderColor = "#ef4444";
778
+ overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
779
+ positionOverlay(el);
780
+ window.parent.postMessage({
781
+ type: "gsdt-select",
782
+ path: getElementPath(el),
783
+ styles: extractStyles(el),
784
+ boxModel: extractBoxModel(el),
785
+ tagName: el.tagName.toLowerCase(),
786
+ className: typeof el.className === "string" ? el.className : "",
787
+ textContent: (el.textContent || "").trim().substring(0, 100),
788
+ childCount: el.children.length,
789
+ }, "*");
790
+ }
791
+ }
792
+ break;
793
+
794
+ case "gsdt-get-tree":
795
+ // Return the DOM subtree of a component for the tree view
796
+ if (msg.selector) {
797
+ const root = document.querySelector(msg.selector);
798
+ if (root) {
799
+ // Store element references by key for select-by-key
800
+ window.__gsdtTreeElements = new Map();
801
+ let keyCounter = 0;
802
+
803
+ function buildTree(el, depth) {
804
+ if (depth > 4) return null; // limit depth
805
+ const tag = el.tagName.toLowerCase();
806
+ const s = getComputedStyle(el);
807
+ const rect = el.getBoundingClientRect();
808
+ const text = el.children.length === 0 ? (el.textContent || "").trim().substring(0, 40) : "";
809
+
810
+ // Smart label with structural context
811
+ let label = tag;
812
+ if (tag === "table") {
813
+ const cols = el.querySelector("tr") ? el.querySelector("tr").children.length : 0;
814
+ const rows = el.querySelectorAll("tbody tr").length || el.querySelectorAll("tr").length;
815
+ label = "table " + rows + "×" + cols;
816
+ } else if (tag === "thead") {
817
+ label = "header";
818
+ } else if (tag === "tbody") {
819
+ label = "body (" + el.querySelectorAll("tr").length + " rows)";
820
+ } else if (tag === "tfoot") {
821
+ label = "footer";
822
+ } else if (tag === "tr") {
823
+ const section = el.closest("tbody") || el.closest("thead") || el.closest("tfoot");
824
+ const rows = section ? Array.from(section.querySelectorAll("tr")) : [];
825
+ const rowIdx = rows.indexOf(el) + 1;
826
+ const sectionName = section ? section.tagName.toLowerCase().replace("thead", "hdr").replace("tbody", "").replace("tfoot", "ftr") : "";
827
+ label = (sectionName ? sectionName + " " : "") + "row " + rowIdx;
828
+ } else if (tag === "th") {
829
+ const row = el.closest("tr");
830
+ const colIdx = row ? Array.from(row.children).indexOf(el) + 1 : 0;
831
+ label = "Col " + colIdx;
832
+ if (text && text.length < 15) label += ' "' + text + '"';
833
+ } else if (tag === "td") {
834
+ const row = el.closest("tr");
835
+ const colIdx = row ? Array.from(row.children).indexOf(el) + 1 : 0;
836
+ label = "Col " + colIdx;
837
+ if (text && text.length < 15) label += ' "' + text + '"';
838
+ } else if (tag === "svg") {
839
+ label = "svg";
840
+ } else if (tag === "circle") {
841
+ label = "arc/circle";
842
+ } else if (tag === "path") {
843
+ label = "path";
844
+ } else if (s.display === "flex" || s.display === "inline-flex") {
845
+ label = "flex " + (s.flexDirection === "column" ? "col" : "row");
846
+ } else if (s.display === "grid") {
847
+ label = "grid";
848
+ }
849
+ if (tag !== "th" && tag !== "td" && text && text.length < 20) label += ' "' + text + '"';
850
+
851
+ const key = "n" + (keyCounter++);
852
+ window.__gsdtTreeElements.set(key, el);
853
+
854
+ const node = {
855
+ tag, label, text,
856
+ width: Math.round(rect.width),
857
+ height: Math.round(rect.height),
858
+ display: s.display,
859
+ key,
860
+ props: {},
861
+ };
862
+
863
+ // Add contextual props based on element type
864
+ if (tag === "table" || tag === "thead" || tag === "tbody") {
865
+ node.props.rows = el.querySelectorAll("tr").length;
866
+ }
867
+ if (tag === "tr") {
868
+ node.props.cells = el.children.length;
869
+ node.props.height = Math.round(rect.height) + "px";
870
+ }
871
+ if (tag === "th" || tag === "td") {
872
+ node.props.width = Math.round(rect.width) + "px";
873
+ node.props.textAlign = s.textAlign;
874
+ node.props.padding = s.padding;
875
+ node.props.fontSize = s.fontSize;
876
+ node.props.fontWeight = s.fontWeight;
877
+ if (tag === "th") node.props.background = s.backgroundColor;
878
+ }
879
+ if (tag === "circle") {
880
+ node.props.stroke = el.getAttribute("stroke");
881
+ node.props.strokeWidth = el.getAttribute("stroke-width");
882
+ node.props.r = el.getAttribute("r");
883
+ }
884
+ if (s.display === "flex" || s.display === "inline-flex") {
885
+ node.props.gap = s.gap;
886
+ node.props.alignItems = s.alignItems;
887
+ node.props.justifyContent = s.justifyContent;
888
+ }
889
+ if (s.backgroundColor && s.backgroundColor !== "rgba(0, 0, 0, 0)") {
890
+ node.props.background = s.backgroundColor;
891
+ }
892
+
893
+ // Recurse into children (skip text-only nodes, limit count)
894
+ const children = Array.from(el.children)
895
+ .filter(c => !["SCRIPT", "STYLE"].includes(c.tagName))
896
+ .slice(0, 20);
897
+ if (children.length > 0) {
898
+ node.children = children.map(c => buildTree(c, depth + 1)).filter(Boolean);
899
+ }
900
+
901
+ return node;
902
+ }
903
+ window.parent.postMessage({
904
+ type: "gsdt-tree",
905
+ tree: buildTree(root, 0),
906
+ selector: msg.selector,
907
+ }, "*");
908
+ }
909
+ }
910
+ break;
911
+
912
+ case "gsdt-select-by-key":
913
+ // Select an element by its key from the tree element map
914
+ if (msg.key && window.__gsdtTreeElements) {
915
+ const el = window.__gsdtTreeElements.get(msg.key);
916
+ if (!el) break;
917
+ locked = true;
918
+ lockedEl = el;
919
+ currentEl = el;
920
+ overlay.style.borderColor = "#ef4444";
921
+ overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
922
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
923
+ positionOverlay(el);
924
+ window.parent.postMessage({
925
+ type: "gsdt-select",
926
+ path: getElementPath(el),
927
+ styles: extractStyles(el),
928
+ boxModel: extractBoxModel(el),
929
+ tagName: el.tagName.toLowerCase(),
930
+ className: typeof el.className === "string" ? el.className : "",
931
+ textContent: (el.textContent || "").trim().substring(0, 100),
932
+ childCount: el.children.length,
933
+ }, "*");
934
+ }
935
+ break;
936
+ }
937
+ });
938
+
939
+ // Notify parent we're ready
940
+ window.parent.postMessage({ type: "gsdt-inject-ready" }, "*");
941
+ })();