markui-cli 0.1.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.
@@ -0,0 +1,884 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ // ── State ──────────────────────────────────────────────────────────
5
+ var annotateMode = false;
6
+ var annotations = []; // { selector, tagName, instruction, rect? }
7
+ var hoveredEl = null;
8
+ var panelOpen = false;
9
+ var toolbarCollapsed = false;
10
+
11
+ var SKIP_TAGS = ['HTML', 'HEAD', 'BODY', 'SCRIPT', 'STYLE', 'LINK', 'META', 'NOSCRIPT'];
12
+ var toolbarJustExpanded = false;
13
+
14
+ // Detect base URL for API calls — handles cross-origin (inject mode)
15
+ var MARKUI_BASE = '';
16
+ (function() {
17
+ var scripts = document.querySelectorAll('script[src*="/__markui__/overlay.js"]');
18
+ if (scripts.length > 0) {
19
+ var src = scripts[scripts.length - 1].src;
20
+ var idx = src.indexOf('/__markui__/');
21
+ if (idx !== -1) {
22
+ var origin = src.substring(0, idx);
23
+ // Only set base if it's a different origin
24
+ if (origin && origin !== location.origin) {
25
+ MARKUI_BASE = origin;
26
+ }
27
+ }
28
+ }
29
+ })();
30
+
31
+ // ── Squircle (iOS continuous corners) ────────────────────────────
32
+
33
+ function squirclePath(w, h, r) {
34
+ r = Math.min(r, w / 2, h / 2);
35
+ var p = Math.min(r * 1.28, w / 2, h / 2);
36
+ var c = p * 0.8;
37
+ function n(v) { return Math.round(v * 100) / 100; }
38
+ return 'path("' +
39
+ 'M ' + n(p) + ' 0' +
40
+ ' L ' + n(w - p) + ' 0' +
41
+ ' C ' + n(w - p + c) + ' 0 ' + n(w) + ' ' + n(p - c) + ' ' + n(w) + ' ' + n(p) +
42
+ ' L ' + n(w) + ' ' + n(h - p) +
43
+ ' C ' + n(w) + ' ' + n(h - p + c) + ' ' + n(w - p + c) + ' ' + n(h) + ' ' + n(w - p) + ' ' + n(h) +
44
+ ' L ' + n(p) + ' ' + n(h) +
45
+ ' C ' + n(p - c) + ' ' + n(h) + ' 0 ' + n(h - p + c) + ' 0 ' + n(h - p) +
46
+ ' L 0 ' + n(p) +
47
+ ' C 0 ' + n(p - c) + ' ' + n(p - c) + ' 0 ' + n(p) + ' 0' +
48
+ ' Z")';
49
+ }
50
+
51
+ function applySquircle(el, r) {
52
+ if (!el) return;
53
+ var rect = el.getBoundingClientRect();
54
+ if (rect.width === 0 || rect.height === 0) return;
55
+ el.style.clipPath = squirclePath(rect.width, rect.height, r);
56
+ }
57
+
58
+ function applyToolbarSquircles() {
59
+ applySquircle(document.getElementById('markui-toggle'), 8);
60
+ applySquircle(document.getElementById('markui-count'), 8);
61
+ applySquircle(document.getElementById('markui-trash-all'), 8);
62
+ applySquircle(document.getElementById('markui-copy'), 8);
63
+ applySquircle(document.getElementById('markui-collapse'), 8);
64
+ // Mini button has fixed 40x40 dimensions, may be hidden
65
+ // Mini button uses border-radius only (no clip-path) to preserve shadow
66
+ }
67
+
68
+ function applyPopupSquircles() {
69
+ setTimeout(function() {
70
+ applySquircle(document.getElementById('markui-popup-attach'), 8);
71
+ applySquircle(document.getElementById('markui-popup-delete'), 8);
72
+ }, 10);
73
+ }
74
+
75
+ // ── Helpers ────────────────────────────────────────────────────────
76
+
77
+ function isMarkuiEl(el) {
78
+ if (!el) return false;
79
+ if (el.className && typeof el.className === 'string' && el.className.indexOf('markui-') !== -1) return true;
80
+ if (el.id && el.id.indexOf('markui-') !== -1) return true;
81
+ var p = el.parentElement;
82
+ while (p) {
83
+ if (p.id && p.id.indexOf('markui-') !== -1) return true;
84
+ if (p.className && typeof p.className === 'string' && p.className.indexOf('markui-') !== -1) return true;
85
+ p = p.parentElement;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ function shouldSkip(el) {
91
+ if (!el || !el.tagName) return true;
92
+ if (SKIP_TAGS.indexOf(el.tagName) !== -1) return true;
93
+ return isMarkuiEl(el);
94
+ }
95
+
96
+ // ── Selector Detection ────────────────────────────────────────────
97
+
98
+ function getBestSelector(el) {
99
+ if (!el || !el.tagName) return '';
100
+ var tag = el.tagName.toLowerCase();
101
+
102
+ // 1. ID
103
+ if (el.id && !el.id.startsWith('markui-')) {
104
+ if (document.querySelectorAll('#' + CSS.escape(el.id)).length === 1) {
105
+ return '#' + el.id;
106
+ }
107
+ }
108
+
109
+ // 2. Single unique class
110
+ if (el.classList) {
111
+ for (var i = 0; i < el.classList.length; i++) {
112
+ var cls = el.classList[i];
113
+ if (cls.startsWith('markui-')) continue;
114
+ try {
115
+ if (document.querySelectorAll('.' + CSS.escape(cls)).length === 1) {
116
+ return '.' + cls;
117
+ }
118
+ } catch(e) {}
119
+ }
120
+ }
121
+
122
+ // 3. Tag + most specific class
123
+ if (el.classList && el.classList.length > 0) {
124
+ var best = null;
125
+ var bestCount = Infinity;
126
+ for (var i = 0; i < el.classList.length; i++) {
127
+ var cls = el.classList[i];
128
+ if (cls.startsWith('markui-')) continue;
129
+ try {
130
+ var count = document.querySelectorAll(tag + '.' + CSS.escape(cls)).length;
131
+ if (count < bestCount) {
132
+ bestCount = count;
133
+ best = cls;
134
+ }
135
+ } catch(e) {}
136
+ }
137
+ if (best && bestCount <= 3) {
138
+ return tag + '.' + best;
139
+ }
140
+ }
141
+
142
+ // 4. Ancestry path (max 3 levels)
143
+ var parts = [];
144
+ var current = el;
145
+ for (var depth = 0; depth < 3 && current && current !== document.body; depth++) {
146
+ var t = current.tagName.toLowerCase();
147
+ if (current.id && !current.id.startsWith('markui-')) {
148
+ parts.unshift('#' + current.id);
149
+ break;
150
+ }
151
+ var parent = current.parentElement;
152
+ if (parent) {
153
+ var siblings = Array.from(parent.children).filter(function(c) {
154
+ return c.tagName === current.tagName;
155
+ });
156
+ if (siblings.length > 1) {
157
+ var idx = siblings.indexOf(current) + 1;
158
+ t += ':nth-child(' + (Array.from(parent.children).indexOf(current) + 1) + ')';
159
+ }
160
+ }
161
+ // Add first non-markui class if available
162
+ if (current.classList) {
163
+ for (var i = 0; i < current.classList.length; i++) {
164
+ if (!current.classList[i].startsWith('markui-')) {
165
+ t += '.' + current.classList[i];
166
+ break;
167
+ }
168
+ }
169
+ }
170
+ parts.unshift(t);
171
+ current = parent;
172
+ }
173
+ return parts.join(' > ');
174
+ }
175
+
176
+ // ── Sync annotations with server ──────────────────────────────────
177
+
178
+ function saveAnnotations() {
179
+ fetch(MARKUI_BASE + '/__markui__/annotations', {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json' },
182
+ body: JSON.stringify({
183
+ page: location.pathname,
184
+ annotations: annotations
185
+ })
186
+ }).catch(function() {});
187
+ renderBadges();
188
+ updateCount();
189
+ }
190
+
191
+ function loadAnnotations() {
192
+ fetch(MARKUI_BASE + '/__markui__/annotations')
193
+ .then(function(r) { return r.json(); })
194
+ .then(function(data) {
195
+ var page = location.pathname;
196
+ if (data[page]) {
197
+ annotations = data[page];
198
+ renderBadges();
199
+ updateCount();
200
+ if (panelOpen) renderPanel();
201
+ }
202
+ })
203
+ .catch(function() {});
204
+ }
205
+
206
+ // ── UI Elements ────────────────────────────────────────────────────
207
+
208
+ // Toolbar icons (20×20 display for 36px buttons)
209
+ var ICON_SELECT = '<svg width="24" height="24" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.33317 2.50008C8.33317 2.27907 8.24537 2.06711 8.08909 1.91083C7.93281 1.75455 7.72085 1.66675 7.49984 1.66675C7.27882 1.66675 7.06686 1.75455 6.91058 1.91083C6.7543 2.06711 6.6665 2.27907 6.6665 2.50008V4.16675C6.6665 4.38776 6.7543 4.59972 6.91058 4.756C7.06686 4.91228 7.27882 5.00008 7.49984 5.00008C7.72085 5.00008 7.93281 4.91228 8.08909 4.756C8.24537 4.59972 8.33317 4.38776 8.33317 4.16675V2.50008ZM4.55317 3.37508C4.4763 3.29549 4.38434 3.232 4.28267 3.18833C4.181 3.14466 4.07165 3.12167 3.96101 3.12071C3.85036 3.11974 3.74062 3.14083 3.63821 3.18273C3.5358 3.22463 3.44275 3.28651 3.36451 3.36475C3.28626 3.443 3.22439 3.53604 3.18249 3.63845C3.14059 3.74087 3.1195 3.8506 3.12046 3.96125C3.12142 4.0719 3.14441 4.18125 3.18809 4.28292C3.23176 4.38459 3.29525 4.47654 3.37484 4.55342L4.55317 5.73342C4.63065 5.8109 4.72263 5.87236 4.82387 5.91429C4.9251 5.95622 5.0336 5.9778 5.14317 5.9778C5.25274 5.9778 5.36124 5.95622 5.46248 5.91429C5.56371 5.87236 5.65569 5.8109 5.73317 5.73342C5.81065 5.65594 5.87211 5.56395 5.91404 5.46272C5.95597 5.36149 5.97756 5.25299 5.97756 5.14342C5.97756 5.03384 5.95597 4.92534 5.91404 4.82411C5.87211 4.72288 5.81065 4.6309 5.73317 4.55342L4.55317 3.37508ZM8.159 6.84175C7.344 6.57008 6.56984 7.34425 6.84067 8.15842L9.78484 16.9901C10.0823 17.8834 11.3182 17.9551 11.7173 17.1017L13.4298 13.4301L17.1015 11.7176C17.9548 11.3192 17.8832 10.0826 16.9898 9.78508L8.159 6.84175ZM11.6248 3.37508C11.7811 3.53135 11.8688 3.74328 11.8688 3.96425C11.8688 4.18522 11.7811 4.39714 11.6248 4.55342L10.4465 5.73342C10.3691 5.81084 10.2772 5.87226 10.176 5.91416C10.0748 5.95606 9.96642 5.97763 9.85692 5.97763C9.74743 5.97763 9.639 5.95606 9.53784 5.91416C9.43668 5.87226 9.34476 5.81084 9.26734 5.73342C9.18991 5.65599 9.1285 5.56407 9.08659 5.46291C9.04469 5.36175 9.02312 5.25333 9.02312 5.14383C9.02312 5.03434 9.04469 4.92591 9.08659 4.82475C9.1285 4.72359 9.18991 4.63167 9.26734 4.55425L10.4465 3.37592C10.6028 3.21969 10.8147 3.13193 11.0357 3.13193C11.2566 3.13193 11.4686 3.21886 11.6248 3.37508ZM1.6665 7.50008C1.6665 7.27907 1.7543 7.06711 1.91058 6.91083C2.06686 6.75455 2.27882 6.66675 2.49984 6.66675H4.1665C4.38752 6.66675 4.59948 6.75455 4.75576 6.91083C4.91204 7.06711 4.99984 7.27907 4.99984 7.50008C4.99984 7.7211 4.91204 7.93306 4.75576 8.08934C4.59948 8.24562 4.38752 8.33342 4.1665 8.33342H2.49984C2.27882 8.33342 2.06686 8.24562 1.91058 8.08934C1.7543 7.93306 1.6665 7.7211 1.6665 7.50008ZM5.73234 10.4467C5.80976 10.3693 5.87118 10.2774 5.91308 10.1762C5.95498 10.0751 5.97655 9.96666 5.97655 9.85717C5.97655 9.74767 5.95498 9.63925 5.91308 9.53809C5.87118 9.43692 5.80976 9.34501 5.73234 9.26758C5.65491 9.19016 5.563 9.12874 5.46183 9.08684C5.36067 9.04494 5.25225 9.02337 5.14275 9.02337C5.03326 9.02337 4.92483 9.04494 4.82367 9.08684C4.72251 9.12874 4.6306 9.19016 4.55317 9.26758L3.37484 10.4467C3.29525 10.5236 3.23176 10.6156 3.18809 10.7172C3.14441 10.8189 3.12142 10.9283 3.12046 11.0389C3.1195 11.1496 3.14059 11.2593 3.18249 11.3617C3.22439 11.4641 3.28626 11.5572 3.36451 11.6354C3.44275 11.7137 3.5358 11.7755 3.63821 11.8174C3.74062 11.8593 3.85036 11.8804 3.96101 11.8795C4.07165 11.8785 4.181 11.8555 4.28267 11.8118C4.38434 11.7682 4.4763 11.7047 4.55317 11.6251L5.73234 10.4467Z" fill="currentColor"/></svg>';
210
+ var ICON_TRASH_TOOLBAR = '<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.83319 4.11125C1.83319 3.80459 2.06319 3.55525 2.34719 3.55525H4.12385C4.47652 3.54525 4.78785 3.30325 4.90785 2.94459L4.92785 2.88125L5.00452 2.63325C5.05119 2.48125 5.09185 2.34859 5.14919 2.22992C5.37452 1.76192 5.79185 1.43725 6.27385 1.35392C6.39652 1.33325 6.52519 1.33325 6.67385 1.33325H8.99252C9.14119 1.33325 9.27052 1.33325 9.39252 1.35392C9.87452 1.43725 10.2925 1.76192 10.5172 2.22992C10.5745 2.34859 10.6152 2.48125 10.6619 2.63325L10.7385 2.88125L10.7585 2.94459C10.8785 3.30325 11.2519 3.54592 11.6052 3.55525H13.3185C13.6032 3.55525 13.8332 3.80392 13.8332 4.11125C13.8332 4.41859 13.6032 4.66659 13.3192 4.66659H2.34652C2.06252 4.66659 1.83319 4.41792 1.83319 4.11125ZM7.73785 14.6666H8.26252C10.0672 14.6666 10.9692 14.6666 11.5565 14.0913C12.1432 13.5153 12.2032 12.5713 12.3232 10.6839L12.4965 7.96325C12.5619 6.93859 12.5945 6.42659 12.2999 6.10192C12.0052 5.77725 11.5085 5.77725 10.5139 5.77725H5.48652C4.49252 5.77725 3.99519 5.77725 3.70052 6.10192C3.40585 6.42659 3.43919 6.93859 3.50385 7.96325L3.67719 10.6833C3.79719 12.5719 3.85719 13.5153 4.44385 14.0913C5.03052 14.6673 5.93319 14.6666 7.73785 14.6666Z" fill="currentColor"/></svg>';
211
+ var ICON_COPY = '<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.16 1.33325H7.564C6.388 1.33325 5.456 1.33325 4.72733 1.43192C3.97667 1.53325 3.36933 1.74659 2.89067 2.22725C2.41133 2.70792 2.19867 3.31792 2.098 4.07125C2 4.80325 2 5.73859 2 6.91925V10.8113C2 11.8166 2.61333 12.6779 3.48467 13.0393C3.44 12.4326 3.44 11.5826 3.44 10.8746V7.53459C3.44 6.68059 3.44 5.94392 3.51867 5.35459C3.60333 4.72259 3.794 4.11725 4.28333 3.62592C4.77267 3.13459 5.376 2.94325 6.00533 2.85792C6.592 2.77925 7.32533 2.77925 8.17667 2.77925H10.2233C11.074 2.77925 11.806 2.77925 12.3933 2.85792C12.2174 2.40878 11.9102 2.02308 11.5118 1.75111C11.1135 1.47914 10.6424 1.33352 10.16 1.33325Z" fill="currentColor"/><path d="M4.3999 7.59801C4.3999 5.78068 4.3999 4.87201 4.96257 4.30734C5.52457 3.74268 6.42924 3.74268 8.2399 3.74268H10.1599C11.9699 3.74268 12.8752 3.74268 13.4379 4.30734C14.0006 4.87201 13.9999 5.78068 13.9999 7.59801V10.8113C13.9999 12.6287 13.9999 13.5373 13.4379 14.102C12.8752 14.6667 11.9699 14.6667 10.1599 14.6667H8.2399C6.4299 14.6667 5.52457 14.6667 4.96257 14.102C4.3999 13.5373 4.3999 12.6287 4.3999 10.8113V7.59801Z" fill="currentColor"/></svg>';
212
+ var ICON_X_TOOLBAR = '<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.2 3.80668C11.94 3.54668 11.52 3.54668 11.26 3.80668L7.99998 7.06001L4.73998 3.80001C4.47998 3.54001 4.05998 3.54001 3.79998 3.80001C3.53998 4.06001 3.53998 4.48001 3.79998 4.74001L7.05998 8.00001L3.79998 11.26C3.53998 11.52 3.53998 11.94 3.79998 12.2C4.05998 12.46 4.47998 12.46 4.73998 12.2L7.99998 8.94001L11.26 12.2C11.52 12.46 11.94 12.46 12.2 12.2C12.46 11.94 12.46 11.52 12.2 11.26L8.93998 8.00001L12.2 4.74001C12.4533 4.48668 12.4533 4.06001 12.2 3.80668Z" fill="currentColor"/></svg>';
213
+ var ICON_MARKUI_MINI = ICON_SELECT;
214
+
215
+ // Toolbar
216
+ var toolbar = document.createElement('div');
217
+ toolbar.id = 'markui-toolbar';
218
+ toolbar.className = 'markui-toolbar';
219
+ toolbar.innerHTML =
220
+ '<button class="markui-pill-btn markui-annotate-btn" id="markui-toggle" title="Annotate">' + ICON_SELECT + '</button>' +
221
+ '<button class="markui-pill-btn markui-badge-wrap" id="markui-count" title="Annotations"><span class="markui-count-badge">0</span></button>' +
222
+ '<button class="markui-pill-btn markui-trash-btn" id="markui-trash-all" title="Clear all annotations">' + ICON_TRASH_TOOLBAR + '</button>' +
223
+ '<button class="markui-pill-btn markui-copy-btn" id="markui-copy" title="Copy for agent">' + ICON_COPY + '</button>' +
224
+ '<div class="markui-pill-sep"></div>' +
225
+ '<button class="markui-pill-btn markui-close-btn" id="markui-collapse" title="Close">' + ICON_X_TOOLBAR + '</button>';
226
+
227
+ // Collapsed mini button
228
+ var miniBtn = document.createElement('button');
229
+ miniBtn.id = 'markui-mini';
230
+ miniBtn.className = 'markui-mini';
231
+ miniBtn.innerHTML = ICON_MARKUI_MINI;
232
+ miniBtn.title = 'Open MarkUI';
233
+
234
+ // Highlight overlay
235
+ var highlight = document.createElement('div');
236
+ highlight.id = 'markui-highlight';
237
+ highlight.className = 'markui-highlight';
238
+
239
+ // Tooltip
240
+ var tooltip = document.createElement('div');
241
+ tooltip.id = 'markui-tooltip';
242
+ tooltip.className = 'markui-tooltip';
243
+
244
+ // Popup
245
+ var popup = document.createElement('div');
246
+ popup.id = 'markui-popup';
247
+ popup.className = 'markui-popup';
248
+
249
+ // Panel
250
+ var panel = document.createElement('div');
251
+ panel.id = 'markui-panel';
252
+ panel.className = 'markui-panel';
253
+
254
+ // Toast
255
+ var toast = document.createElement('div');
256
+ toast.id = 'markui-toast';
257
+ toast.className = 'markui-toast';
258
+
259
+ // Badge container
260
+ var badgeContainer = document.createElement('div');
261
+ badgeContainer.id = 'markui-badges';
262
+ badgeContainer.className = 'markui-badges';
263
+
264
+ // Hidden file input for attachments
265
+ var fileInput = document.createElement('input');
266
+ fileInput.type = 'file';
267
+ fileInput.id = 'markui-file-input';
268
+ fileInput.accept = 'image/*';
269
+ fileInput.style.display = 'none';
270
+
271
+ // Toolbar tooltip (real DOM, avoids clip-path clipping)
272
+ var toolbarTip = document.createElement('div');
273
+ toolbarTip.className = 'markui-toolbar-tooltip';
274
+ var toolbarTipCaret = document.createElement('div');
275
+ toolbarTipCaret.className = 'markui-toolbar-tooltip-caret';
276
+
277
+ var tipSourceBtn = null;
278
+
279
+ function showToolbarTip(btn) {
280
+ var text = btn.getAttribute('title');
281
+ if (!text) {
282
+ // Title may have been moved to data-tip already
283
+ text = btn.getAttribute('data-tip');
284
+ }
285
+ if (!text) return;
286
+ // Suppress native tooltip by moving title to data-tip
287
+ if (btn.hasAttribute('title')) {
288
+ btn.setAttribute('data-tip', text);
289
+ btn.removeAttribute('title');
290
+ }
291
+ tipSourceBtn = btn;
292
+ toolbarTip.textContent = text;
293
+ toolbarTip.style.clipPath = '';
294
+ toolbarTip.classList.add('markui-toolbar-tooltip-visible');
295
+ var rect = btn.getBoundingClientRect();
296
+ var centerX = rect.left + rect.width / 2;
297
+ toolbarTip.style.left = centerX + 'px';
298
+ toolbarTip.style.top = (rect.top - 6 - 8) + 'px'; // 6px caret + 8px gap
299
+ toolbarTip.style.transform = 'translate(-50%, -100%)';
300
+ // Apply squircle to tooltip body
301
+ var tipRect = toolbarTip.getBoundingClientRect();
302
+ if (tipRect.width > 0 && tipRect.height > 0) {
303
+ toolbarTip.style.clipPath = squirclePath(tipRect.width, tipRect.height, 8);
304
+ }
305
+ // Position caret below tooltip
306
+ toolbarTipCaret.style.left = (centerX - 6) + 'px';
307
+ toolbarTipCaret.style.top = (tipRect.bottom) + 'px';
308
+ toolbarTipCaret.classList.add('markui-toolbar-tooltip-caret-visible');
309
+ }
310
+
311
+ function hideToolbarTip() {
312
+ toolbarTip.classList.remove('markui-toolbar-tooltip-visible');
313
+ toolbarTipCaret.classList.remove('markui-toolbar-tooltip-caret-visible');
314
+ // Restore title attribute
315
+ if (tipSourceBtn) {
316
+ var text = tipSourceBtn.getAttribute('data-tip');
317
+ if (text) {
318
+ tipSourceBtn.setAttribute('title', text);
319
+ tipSourceBtn.removeAttribute('data-tip');
320
+ }
321
+ tipSourceBtn = null;
322
+ }
323
+ }
324
+
325
+ // ── Mount ──────────────────────────────────────────────────────────
326
+
327
+ function mount() {
328
+ document.body.appendChild(toolbar);
329
+ document.body.appendChild(miniBtn);
330
+ document.body.appendChild(highlight);
331
+ document.body.appendChild(tooltip);
332
+ document.body.appendChild(popup);
333
+ document.body.appendChild(panel);
334
+ document.body.appendChild(toast);
335
+ document.body.appendChild(badgeContainer);
336
+ document.body.appendChild(fileInput);
337
+ document.body.appendChild(toolbarTip);
338
+ document.body.appendChild(toolbarTipCaret);
339
+ loadAnnotations();
340
+
341
+ // Apply squircle corners after elements are in DOM
342
+ setTimeout(applyToolbarSquircles, 0);
343
+
344
+ // Toolbar tooltip hover handlers
345
+ toolbar.addEventListener('mouseover', function(e) {
346
+ var btn = e.target.closest('[title], [data-tip]');
347
+ if (!btn || !toolbar.contains(btn)) { hideToolbarTip(); return; }
348
+ showToolbarTip(btn);
349
+ });
350
+ toolbar.addEventListener('mouseleave', function() {
351
+ hideToolbarTip();
352
+ });
353
+
354
+ // File input change handler for attachments
355
+ fileInput.addEventListener('change', function(e) {
356
+ var file = e.target.files && e.target.files[0];
357
+ if (!file) return;
358
+ var reader = new FileReader();
359
+ reader.onload = function(ev) {
360
+ currentAttachment = { dataUrl: ev.target.result, name: file.name };
361
+ var preview = document.getElementById('markui-popup-preview');
362
+ var img = document.getElementById('markui-popup-preview-img');
363
+ var nameEl = document.getElementById('markui-popup-preview-name');
364
+ if (preview && img) {
365
+ img.src = ev.target.result;
366
+ if (nameEl) nameEl.textContent = file.name;
367
+ preview.style.display = 'flex';
368
+ }
369
+ };
370
+ reader.readAsDataURL(file);
371
+ fileInput.value = ''; // reset so same file can be re-selected
372
+ });
373
+ }
374
+
375
+ if (document.readyState === 'loading') {
376
+ document.addEventListener('DOMContentLoaded', mount);
377
+ } else {
378
+ mount();
379
+ }
380
+
381
+ // ── Toolbar Actions ────────────────────────────────────────────────
382
+
383
+ function updateCount() {
384
+ var wrap = document.getElementById('markui-count');
385
+ var badge = wrap ? wrap.querySelector('.markui-count-badge') : null;
386
+ if (badge) {
387
+ badge.textContent = annotations.length;
388
+ }
389
+ if (wrap) {
390
+ wrap.style.display = annotations.length > 0 ? 'inline-flex' : 'none';
391
+ }
392
+ }
393
+
394
+ document.addEventListener('click', function(e) {
395
+ var target = e.target;
396
+
397
+ // Toggle annotate mode
398
+ if (target.id === 'markui-toggle' || target.closest('#markui-toggle')) {
399
+ annotateMode = !annotateMode;
400
+ var toggleBtn = document.getElementById('markui-toggle');
401
+ toggleBtn.classList.toggle('markui-active', annotateMode);
402
+ if (!annotateMode) hideHighlight();
403
+ return;
404
+ }
405
+
406
+ // Count badge → open annotations panel
407
+ if (target.id === 'markui-count' || target.closest('#markui-count')) {
408
+ panelOpen = !panelOpen;
409
+ panel.classList.toggle('markui-panel-open', panelOpen);
410
+ if (panelOpen) renderPanel();
411
+ return;
412
+ }
413
+
414
+ // Copy for Claude
415
+ if (target.id === 'markui-copy' || target.closest('#markui-copy')) {
416
+ copyForClaude();
417
+ return;
418
+ }
419
+
420
+ // Toolbar trash — clear all annotations
421
+ if (target.id === 'markui-trash-all' || target.closest('#markui-trash-all')) {
422
+ if (annotations.length > 0) {
423
+ annotations = [];
424
+ saveAnnotations();
425
+ if (panelOpen) renderPanel();
426
+ }
427
+ return;
428
+ }
429
+
430
+ // Collapse toolbar — animate from annotate button
431
+ if (target.id === 'markui-collapse' || target.closest('#markui-collapse')) {
432
+ toolbarCollapsed = true;
433
+ var toggleEl = document.getElementById('markui-toggle');
434
+ if (annotateMode) {
435
+ annotateMode = false;
436
+ if (toggleEl) toggleEl.classList.remove('markui-active');
437
+ hideHighlight();
438
+ }
439
+ toolbar.classList.add('markui-toolbar-hidden');
440
+ // Small delay so the pill starts shrinking before mini appears
441
+ setTimeout(function() {
442
+ miniBtn.classList.add('markui-mini-visible');
443
+ }, 80);
444
+ return;
445
+ }
446
+
447
+ // Expand toolbar from mini button
448
+ if (target.id === 'markui-mini' || target.closest('#markui-mini')) {
449
+ toolbarCollapsed = false;
450
+ miniBtn.classList.remove('markui-mini-visible');
451
+ // Small delay so mini shrinks before pill expands
452
+ setTimeout(function() {
453
+ toolbar.classList.remove('markui-toolbar-hidden');
454
+ // Activate annotate mode on expand
455
+ annotateMode = true;
456
+ var toggleBtn = document.getElementById('markui-toggle');
457
+ if (toggleBtn) toggleBtn.classList.add('markui-active');
458
+ }, 80);
459
+ return;
460
+ }
461
+
462
+ // Toggle element styles in popup
463
+ if (target.classList.contains('markui-popup-selector') || target.closest('.markui-popup-selector')) {
464
+ var stylesDiv = document.getElementById('markui-popup-styles');
465
+ if (stylesDiv) {
466
+ stylesDiv.remove();
467
+ } else {
468
+ try {
469
+ var el = document.querySelector(currentPopupSelector);
470
+ if (el) {
471
+ var cs = window.getComputedStyle(el);
472
+ var props = [
473
+ 'display', 'position', 'width', 'height',
474
+ 'padding', 'margin',
475
+ 'font-family', 'font-size', 'font-weight', 'line-height', 'letter-spacing', 'color',
476
+ 'background-color',
477
+ 'border', 'border-radius',
478
+ 'opacity', 'box-shadow', 'overflow'
479
+ ];
480
+ var html = '<div id="markui-popup-styles" class="markui-popup-styles">';
481
+ for (var i = 0; i < props.length; i++) {
482
+ var val = cs.getPropertyValue(props[i]);
483
+ if (val && val !== 'none' && val !== 'normal' && val !== '' && val !== '0px') {
484
+ html += '<div class="markui-popup-style-row">' +
485
+ '<span class="markui-popup-style-prop">' + props[i] + '</span>' +
486
+ '<span class="markui-popup-style-val">' + escapeHtml(val) + '</span>' +
487
+ '</div>';
488
+ }
489
+ }
490
+ html += '</div>';
491
+ var header = popup.querySelector('.markui-popup-header');
492
+ if (header) header.insertAdjacentHTML('afterend', html);
493
+ }
494
+ } catch(e) {}
495
+ }
496
+ return;
497
+ }
498
+
499
+ // Popup save (submit arrow button)
500
+ if (target.id === 'markui-popup-save' || target.closest('#markui-popup-save')) {
501
+ savePopup();
502
+ return;
503
+ }
504
+
505
+ // Popup cancel
506
+ if (target.id === 'markui-popup-cancel' || target.closest('#markui-popup-cancel')) {
507
+ hidePopup();
508
+ return;
509
+ }
510
+
511
+ // Popup delete
512
+ if (target.id === 'markui-popup-delete' || target.closest('#markui-popup-delete')) {
513
+ deleteFromPopup();
514
+ return;
515
+ }
516
+
517
+ // Popup attach — trigger file picker
518
+ if (target.id === 'markui-popup-attach' || target.closest('#markui-popup-attach')) {
519
+ document.getElementById('markui-file-input').click();
520
+ return;
521
+ }
522
+
523
+ // Remove attachment preview
524
+ if (target.id === 'markui-popup-preview-remove' || target.closest('#markui-popup-preview-remove')) {
525
+ currentAttachment = null;
526
+ var preview = document.getElementById('markui-popup-preview');
527
+ if (preview) preview.style.display = 'none';
528
+ return;
529
+ }
530
+
531
+ // Panel close button
532
+ if (target.id === 'markui-panel-close' || target.closest('#markui-panel-close')) {
533
+ panelOpen = false;
534
+ panel.classList.remove('markui-panel-open');
535
+ return;
536
+ }
537
+
538
+ // Copy all from panel
539
+ if (target.id === 'markui-copy-all' || target.closest('#markui-copy-all')) {
540
+ copyForClaude();
541
+ return;
542
+ }
543
+
544
+ // Clear all annotations
545
+ if (target.id === 'markui-clear-all' || target.closest('#markui-clear-all')) {
546
+ annotations = [];
547
+ saveAnnotations();
548
+ renderPanel();
549
+ return;
550
+ }
551
+
552
+ // Panel edit buttons
553
+ var editBtn = target.classList.contains('markui-panel-edit') ? target : target.closest('.markui-panel-edit');
554
+ if (editBtn) {
555
+ var idx = parseInt(editBtn.getAttribute('data-index'), 10);
556
+ var a = annotations[idx];
557
+ if (a) {
558
+ try {
559
+ var el = document.querySelector(a.selector);
560
+ if (el) {
561
+ showPopup(el);
562
+ }
563
+ } catch(e) {}
564
+ }
565
+ return;
566
+ }
567
+
568
+ // Panel delete buttons
569
+ var deleteBtn = target.classList.contains('markui-panel-delete') ? target : target.closest('.markui-panel-delete');
570
+ if (deleteBtn) {
571
+ var idx = parseInt(deleteBtn.getAttribute('data-index'), 10);
572
+ annotations.splice(idx, 1);
573
+ saveAnnotations();
574
+ renderPanel();
575
+ return;
576
+ }
577
+
578
+ // Annotation mode click
579
+ if (annotateMode && !isMarkuiEl(target) && !shouldSkip(target)) {
580
+ e.preventDefault();
581
+ e.stopPropagation();
582
+ showPopup(target, e.clientX, e.clientY);
583
+ return;
584
+ }
585
+ }, true);
586
+
587
+ // ── Hover Highlight ────────────────────────────────────────────────
588
+
589
+ document.addEventListener('mousemove', function(e) {
590
+ if (!annotateMode) return;
591
+ var el = document.elementFromPoint(e.clientX, e.clientY);
592
+ if (!el || shouldSkip(el) || isMarkuiEl(el)) {
593
+ hideHighlight();
594
+ return;
595
+ }
596
+ if (el === hoveredEl) return;
597
+ hoveredEl = el;
598
+ showHighlight(el);
599
+ });
600
+
601
+ function showHighlight(el) {
602
+ var rect = el.getBoundingClientRect();
603
+ highlight.style.display = 'block';
604
+ highlight.style.top = (rect.top + window.scrollY) + 'px';
605
+ highlight.style.left = (rect.left + window.scrollX) + 'px';
606
+ highlight.style.width = rect.width + 'px';
607
+ highlight.style.height = rect.height + 'px';
608
+
609
+ var sel = getBestSelector(el);
610
+ var tag = el.tagName.toLowerCase();
611
+ tooltip.textContent = '<' + tag + '> — ' + sel;
612
+ tooltip.style.display = 'block';
613
+ tooltip.style.top = (rect.top + window.scrollY - 30) + 'px';
614
+ tooltip.style.left = (rect.left + window.scrollX) + 'px';
615
+ }
616
+
617
+ function hideHighlight() {
618
+ highlight.style.display = 'none';
619
+ tooltip.style.display = 'none';
620
+ hoveredEl = null;
621
+ }
622
+
623
+ // ── Annotation Popup ──────────────────────────────────────────────
624
+
625
+ var currentPopupSelector = '';
626
+ var currentPopupTag = '';
627
+ var editingIndex = -1;
628
+ var currentAttachment = null; // { dataUrl, name }
629
+
630
+ function showPopup(el, clickX, clickY) {
631
+ var rect = el.getBoundingClientRect();
632
+ var sel = getBestSelector(el);
633
+ var tag = el.tagName.toLowerCase();
634
+
635
+ currentPopupSelector = sel;
636
+ currentPopupTag = tag;
637
+
638
+ // Check if already annotated
639
+ editingIndex = -1;
640
+ for (var i = 0; i < annotations.length; i++) {
641
+ if (annotations[i].selector === sel) {
642
+ editingIndex = i;
643
+ break;
644
+ }
645
+ }
646
+
647
+ var existing = editingIndex >= 0 ? annotations[editingIndex].instruction : '';
648
+ // Restore attachment if editing
649
+ currentAttachment = (editingIndex >= 0 && annotations[editingIndex].attachment)
650
+ ? annotations[editingIndex].attachment
651
+ : null;
652
+
653
+ var ICON_SUBMIT = '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 14V4M9 4L4.5 8.5M9 4L13.5 8.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
654
+ var ICON_PLUS = '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 3.75V14.25M3.75 9H14.25" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>';
655
+ var ICON_TRASH_POPUP = '<svg width="18" height="18" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.6668 4.00094H2.3335M12.5555 5.6676L12.2488 10.2676C12.1308 12.037 12.0722 12.9216 11.4955 13.461C10.9188 14.0003 10.0315 14.001 8.25816 14.001H7.74216C5.96882 14.001 5.0815 14.001 4.50482 13.461C3.92816 12.9216 3.86882 12.037 3.7515 10.2676L3.44482 5.6676M6.1135 2.6676C6.25122 2.27725 6.50665 1.93922 6.84456 1.70013C7.18247 1.46103 7.58622 1.33264 8.00016 1.33264C8.4141 1.33264 8.81785 1.46103 9.15576 1.70013C9.49367 1.93922 9.7491 2.27725 9.88682 2.6676" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>';
656
+
657
+ popup.innerHTML =
658
+ '<div class="markui-popup-header">' +
659
+ '<span class="markui-popup-selector">&lt;' + tag + '&gt; - ' + sel + '</span>' +
660
+ '</div>' +
661
+ '<textarea id="markui-popup-textarea" class="markui-popup-textarea" placeholder="Describe your change" rows="2">' +
662
+ escapeHtml(existing) +
663
+ '</textarea>' +
664
+ '<div id="markui-popup-preview" class="markui-popup-preview" style="display:' + (currentAttachment ? 'flex' : 'none') + '">' +
665
+ '<img id="markui-popup-preview-img" class="markui-popup-preview-img"' +
666
+ (currentAttachment ? ' src="' + currentAttachment.dataUrl + '"' : '') + ' />' +
667
+ '<div class="markui-popup-preview-info">' +
668
+ '<span id="markui-popup-preview-name" class="markui-popup-preview-name">' + (currentAttachment ? escapeHtml(currentAttachment.name) : '') + '</span>' +
669
+ '<button id="markui-popup-preview-remove" class="markui-popup-preview-remove">&times;</button>' +
670
+ '</div>' +
671
+ '</div>' +
672
+ '<div class="markui-popup-actions">' +
673
+ '<button id="markui-popup-attach" class="markui-popup-plus" title="Attach">' + ICON_PLUS + '</button>' +
674
+ (editingIndex >= 0
675
+ ? '<button id="markui-popup-delete" class="markui-popup-delete-btn" title="Delete">' + ICON_TRASH_POPUP + '</button>'
676
+ : '') +
677
+ '<button id="markui-popup-cancel" class="markui-popup-cancel">Cancel</button>' +
678
+ '<button id="markui-popup-save" class="markui-popup-submit">' + ICON_SUBMIT + '</button>' +
679
+ '</div>';
680
+
681
+ // Position at click point, or fallback to element position
682
+ var popupTop, popupLeft;
683
+ if (clickX !== undefined && clickY !== undefined) {
684
+ popupTop = clickY + window.scrollY + 12;
685
+ popupLeft = clickX + window.scrollX;
686
+ } else {
687
+ popupTop = rect.bottom + window.scrollY + 8;
688
+ popupLeft = rect.left + window.scrollX;
689
+ }
690
+ // Keep in viewport horizontally
691
+ if (popupLeft + 300 > window.innerWidth) popupLeft = window.innerWidth - 320;
692
+ if (popupLeft < 10) popupLeft = 10;
693
+
694
+ popup.style.left = popupLeft + 'px';
695
+ popup.style.display = 'block';
696
+
697
+ // If popup would overflow below viewport, open above the click point
698
+ var popupHeight = popup.offsetHeight;
699
+ if (clickY !== undefined && clickY + 12 + popupHeight > window.innerHeight) {
700
+ popupTop = (clickY + window.scrollY) - popupHeight - 12;
701
+ }
702
+ if (popupTop < window.scrollY + 10) popupTop = window.scrollY + 10;
703
+
704
+ popup.style.top = popupTop + 'px';
705
+
706
+ // Apply squircle corners to popup buttons
707
+ applyPopupSquircles();
708
+
709
+ setTimeout(function() {
710
+ var ta = document.getElementById('markui-popup-textarea');
711
+ if (ta) ta.focus();
712
+ }, 50);
713
+ }
714
+
715
+ function hidePopup() {
716
+ popup.style.display = 'none';
717
+ editingIndex = -1;
718
+ currentAttachment = null;
719
+ }
720
+
721
+ function savePopup() {
722
+ var ta = document.getElementById('markui-popup-textarea');
723
+ var instruction = ta ? ta.value.trim() : '';
724
+ if (!instruction) {
725
+ hidePopup();
726
+ return;
727
+ }
728
+
729
+ if (editingIndex >= 0) {
730
+ annotations[editingIndex].instruction = instruction;
731
+ annotations[editingIndex].attachment = currentAttachment || null;
732
+ } else {
733
+ annotations.push({
734
+ selector: currentPopupSelector,
735
+ tagName: currentPopupTag,
736
+ instruction: instruction,
737
+ attachment: currentAttachment || null
738
+ });
739
+ }
740
+
741
+ saveAnnotations();
742
+ hidePopup();
743
+ if (panelOpen) renderPanel();
744
+ }
745
+
746
+ function deleteFromPopup() {
747
+ if (editingIndex >= 0) {
748
+ annotations.splice(editingIndex, 1);
749
+ saveAnnotations();
750
+ if (panelOpen) renderPanel();
751
+ }
752
+ hidePopup();
753
+ }
754
+
755
+ // ── Annotations Panel ─────────────────────────────────────────────
756
+
757
+ var ICON_PAPERCLIP = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M13.5 7.1L7.85 12.75a3.18 3.18 0 0 1-4.5-4.5L9 2.6a2.12 2.12 0 0 1 3 3L6.35 11.25a1.06 1.06 0 0 1-1.5-1.5L10.5 4.1"/></svg>';
758
+
759
+ var ICON_CLOSE = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>';
760
+ var ICON_COPY_PANEL = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 7.33325C4 5.44792 4 4.50459 4.586 3.91925C5.17133 3.33325 6.11467 3.33325 8 3.33325H10C11.8853 3.33325 12.8287 3.33325 13.414 3.91925C14 4.50459 14 5.44792 14 7.33325V10.6666C14 12.5519 14 13.4953 13.414 14.0806C12.8287 14.6666 11.8853 14.6666 10 14.6666H8C6.11467 14.6666 5.17133 14.6666 4.586 14.0806C4 13.4953 4 12.5519 4 10.6666V7.33325Z" stroke="currentColor" stroke-width="1.2"/><path d="M4 12.6666C3.46957 12.6666 2.96086 12.4559 2.58579 12.0808C2.21071 11.7057 2 11.197 2 10.6666V6.66658C2 4.15259 2 2.89525 2.78133 2.11459C3.56267 1.33392 4.81933 1.33325 7.33333 1.33325H10C10.5304 1.33325 11.0391 1.54397 11.4142 1.91904C11.7893 2.29411 12 2.80282 12 3.33325" stroke="currentColor" stroke-width="1.2"/></svg>';
761
+ var ICON_TRASH = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M5 4V2.5A.5.5 0 0 1 5.5 2h5a.5.5 0 0 1 .5.5V4"/><path d="M6.5 7v5"/><path d="M9.5 7v5"/><path d="M3 4l1 10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1l1-10"/></svg>';
762
+ var ICON_EDIT = '<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 1.5l3 3L5 14H2v-3z"/></svg>';
763
+ var ICON_DELETE = '<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M5 4V2.5A.5.5 0 0 1 5.5 2h5a.5.5 0 0 1 .5.5V4"/><path d="M6.5 7v5"/><path d="M9.5 7v5"/><path d="M3 4l1 10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1l1-10"/></svg>';
764
+
765
+ function renderPanel() {
766
+ var html =
767
+ '<div class="markui-panel-header">' +
768
+ '<span>Annotations (' + annotations.length + ')</span>' +
769
+ '<div class="markui-panel-header-actions">' +
770
+ (annotations.length > 0
771
+ ? '<button class="markui-panel-icon-btn markui-panel-copy" id="markui-copy-all" title="Copy all">' + ICON_COPY_PANEL + '</button>' +
772
+ '<button class="markui-panel-icon-btn" id="markui-clear-all" title="Clear all">' + ICON_TRASH + '</button>'
773
+ : '') +
774
+ '<button class="markui-panel-icon-btn" id="markui-panel-close" title="Close">' + ICON_CLOSE + '</button>' +
775
+ '</div>' +
776
+ '</div>' +
777
+ '<div class="markui-panel-list">';
778
+
779
+ if (annotations.length === 0) {
780
+ html += '<div class="markui-panel-empty">No annotations yet. Click "Annotate" to start.</div>';
781
+ }
782
+
783
+ for (var i = 0; i < annotations.length; i++) {
784
+ var a = annotations[i];
785
+ html +=
786
+ '<div class="markui-panel-item">' +
787
+ '<span class="markui-panel-num">' + (i + 1) + '</span>' +
788
+ '<div class="markui-panel-info">' +
789
+ '<div class="markui-panel-selector">&lt;' + escapeHtml(a.tagName) + '&gt; — ' + escapeHtml(a.selector) + '</div>' +
790
+ '<div class="markui-panel-instruction">' + escapeHtml(a.instruction) + '</div>' +
791
+ (a.attachment
792
+ ? '<div class="markui-panel-attachment">' + ICON_PAPERCLIP + ' ' + escapeHtml(a.attachment.name) + '</div>'
793
+ : '') +
794
+ '</div>' +
795
+ '<div class="markui-panel-item-actions">' +
796
+ '<button class="markui-panel-icon-btn markui-panel-edit" data-index="' + i + '" title="Edit">' + ICON_EDIT + '</button>' +
797
+ '<button class="markui-panel-icon-btn markui-panel-delete" data-index="' + i + '" title="Delete">' + ICON_DELETE + '</button>' +
798
+ '</div>' +
799
+ '</div>';
800
+ }
801
+
802
+ html += '</div>';
803
+ panel.innerHTML = html;
804
+ }
805
+
806
+ // ── Numbered Badges ────────────────────────────────────────────────
807
+
808
+ function renderBadges() {
809
+ badgeContainer.innerHTML = '';
810
+ for (var i = 0; i < annotations.length; i++) {
811
+ var a = annotations[i];
812
+ try {
813
+ var el = document.querySelector(a.selector);
814
+ if (!el) continue;
815
+ var rect = el.getBoundingClientRect();
816
+ var badge = document.createElement('div');
817
+ badge.className = 'markui-badge-num';
818
+ badge.textContent = i + 1;
819
+ badge.style.top = (rect.top + window.scrollY) + 'px';
820
+ badge.style.left = (rect.left + window.scrollX) + 'px';
821
+ badgeContainer.appendChild(badge);
822
+ } catch(e) {}
823
+ }
824
+ }
825
+
826
+ // Reposition badges on scroll/resize
827
+ window.addEventListener('scroll', renderBadges);
828
+ window.addEventListener('resize', renderBadges);
829
+
830
+ // ── Copy for Claude ────────────────────────────────────────────────
831
+
832
+ function copyForClaude() {
833
+ if (annotations.length === 0) {
834
+ showToast('No annotations to copy');
835
+ return;
836
+ }
837
+
838
+ var text = 'Please make the following UI changes:\n';
839
+ for (var i = 0; i < annotations.length; i++) {
840
+ var a = annotations[i];
841
+ text += '\n' + (i + 1) + '. Element: <' + a.tagName + '> \u2014 ' + a.selector +
842
+ '\n Selector: ' + a.selector +
843
+ '\n Change: ' + a.instruction;
844
+ if (a.attachment) {
845
+ text += '\n Attachment: ' + a.attachment.name + ' (screenshot attached \u2014 see image)';
846
+ }
847
+ text += '\n';
848
+ }
849
+
850
+ navigator.clipboard.writeText(text).then(function() {
851
+ showToast('Copied!');
852
+ }).catch(function() {
853
+ // Fallback
854
+ var ta = document.createElement('textarea');
855
+ ta.value = text;
856
+ ta.style.position = 'fixed';
857
+ ta.style.left = '-9999px';
858
+ document.body.appendChild(ta);
859
+ ta.select();
860
+ document.execCommand('copy');
861
+ document.body.removeChild(ta);
862
+ showToast('Copied!');
863
+ });
864
+ }
865
+
866
+ // ── Toast ──────────────────────────────────────────────────────────
867
+
868
+ function showToast(msg) {
869
+ toast.textContent = msg;
870
+ toast.classList.add('markui-toast-show');
871
+ setTimeout(function() {
872
+ toast.classList.remove('markui-toast-show');
873
+ }, 2000);
874
+ }
875
+
876
+ // ── Utilities ──────────────────────────────────────────────────────
877
+
878
+ function escapeHtml(str) {
879
+ var div = document.createElement('div');
880
+ div.appendChild(document.createTextNode(str));
881
+ return div.innerHTML;
882
+ }
883
+
884
+ })();