opencode-plannotator-tui 0.2.2 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1910 @@
1
+ // =============================================================================
2
+ // Annotate.js — Standalone visual feedback tool for AI coding agents
3
+ // Inspired by Agentation (benjitaylor/agentation), rebuilt for plain HTML.
4
+ // No React. No build step. Just drop a <script> tag.
5
+ //
6
+ // Usage: <script src="annotate.js"></script>
7
+ // =============================================================================
8
+
9
+ (function () {
10
+ "use strict";
11
+
12
+ if (typeof window === "undefined") return;
13
+
14
+ // ═══════════════════════════════════════════
15
+ // Double-injection guard
16
+ // ═══════════════════════════════════════════
17
+ // If annotate.js has already booted on this page (e.g. Chrome extension
18
+ // clicked twice, or script tag + extension), do NOT build a second toolbar.
19
+ // Instead, ask the existing instance to toggle its visibility and bail.
20
+ if (window.__annotateHtml && typeof window.__annotateHtml.toggle === "function") {
21
+ try { window.__annotateHtml.toggle(); } catch (e) {}
22
+ return;
23
+ }
24
+
25
+ // ═══════════════════════════════════════════
26
+ // State
27
+ // ═══════════════════════════════════════════
28
+ const state = {
29
+ active: false,
30
+ annotations: [],
31
+ hoveredEl: null,
32
+ detailLevel: "detailed",
33
+ counter: 0,
34
+ markersHidden: false,
35
+ toolbarHidden: false,
36
+ rulerDragging: false,
37
+ markerColor: "#C96442",
38
+ };
39
+
40
+ var MARKER_COLORS = [
41
+ "#C96442", "#EF4444", "#F59E0B",
42
+ "#22C55E", "#06B6D4", "#3B82F6", "#8B5CF6",
43
+ ];
44
+
45
+ // ═══════════════════════════════════════════
46
+ // Element Identification (ported from agentation)
47
+ // ═══════════════════════════════════════════
48
+
49
+ function getElementPath(target, maxDepth) {
50
+ maxDepth = maxDepth || 4;
51
+ const parts = [];
52
+ let current = target;
53
+ let depth = 0;
54
+ while (current && depth < maxDepth) {
55
+ const tag = current.tagName.toLowerCase();
56
+ if (tag === "html" || tag === "body") break;
57
+ let id = tag;
58
+ if (current.id) {
59
+ id = "#" + current.id;
60
+ } else if (current.className && typeof current.className === "string") {
61
+ const cls = current.className
62
+ .split(/\s+/)
63
+ .find(function (c) {
64
+ return c.length > 2 && !c.match(/^[a-z]{1,2}$/) && !c.match(/[A-Z0-9]{5,}/);
65
+ });
66
+ if (cls) id = "." + cls.split("_")[0];
67
+ }
68
+ parts.unshift(id);
69
+ current = current.parentElement;
70
+ depth++;
71
+ }
72
+ return parts.join(" > ");
73
+ }
74
+
75
+ function identifyElement(target) {
76
+ const path = getElementPath(target);
77
+ const tag = target.tagName.toLowerCase();
78
+
79
+ if (target.dataset && target.dataset.element) return { name: target.dataset.element, path: path };
80
+
81
+ // SVG
82
+ if (["path", "circle", "rect", "line", "g", "svg"].indexOf(tag) !== -1) {
83
+ return { name: "graphic element", path: path };
84
+ }
85
+ // Buttons
86
+ if (tag === "button") {
87
+ var text = (target.textContent || "").trim();
88
+ var ariaLabel = target.getAttribute("aria-label");
89
+ if (ariaLabel) return { name: 'button [' + ariaLabel + ']', path: path };
90
+ return { name: text ? 'button "' + text.slice(0, 25) + '"' : "button", path: path };
91
+ }
92
+ // Links
93
+ if (tag === "a") {
94
+ var linkText = (target.textContent || "").trim();
95
+ return { name: linkText ? 'link "' + linkText.slice(0, 25) + '"' : "link", path: path };
96
+ }
97
+ // Inputs
98
+ if (tag === "input") {
99
+ var type = target.getAttribute("type") || "text";
100
+ var ph = target.getAttribute("placeholder");
101
+ if (ph) return { name: 'input "' + ph + '"', path: path };
102
+ return { name: type + " input", path: path };
103
+ }
104
+ // Headings
105
+ if (/^h[1-6]$/.test(tag)) {
106
+ var hText = (target.textContent || "").trim();
107
+ return { name: hText ? tag + ' "' + hText.slice(0, 35) + '"' : tag, path: path };
108
+ }
109
+ // Paragraphs
110
+ if (tag === "p") {
111
+ var pText = (target.textContent || "").trim();
112
+ if (pText) return { name: 'paragraph: "' + pText.slice(0, 40) + (pText.length > 40 ? '...' : '') + '"', path: path };
113
+ return { name: "paragraph", path: path };
114
+ }
115
+ // Spans / labels
116
+ if (tag === "span" || tag === "label") {
117
+ var sText = (target.textContent || "").trim();
118
+ if (sText && sText.length < 40) return { name: '"' + sText + '"', path: path };
119
+ return { name: tag, path: path };
120
+ }
121
+ // Images
122
+ if (tag === "img") {
123
+ var alt = target.getAttribute("alt");
124
+ return { name: alt ? 'image "' + alt.slice(0, 30) + '"' : "image", path: path };
125
+ }
126
+ // Containers
127
+ if (["div", "section", "article", "nav", "header", "footer", "aside", "main"].indexOf(tag) !== -1) {
128
+ var role = target.getAttribute("role");
129
+ var ariaL = target.getAttribute("aria-label");
130
+ if (ariaL) return { name: tag + " [" + ariaL + "]", path: path };
131
+ if (role) return { name: role, path: path };
132
+ if (typeof target.className === "string" && target.className) {
133
+ var words = target.className.split(/[\s_-]+/)
134
+ .map(function (c) { return c.replace(/[A-Z0-9]{5,}.*$/, ""); })
135
+ .filter(function (c) { return c.length > 2; })
136
+ .slice(0, 2);
137
+ if (words.length > 0) return { name: words.join(" "), path: path };
138
+ }
139
+ return { name: tag === "div" ? "container" : tag, path: path };
140
+ }
141
+
142
+ return { name: tag, path: path };
143
+ }
144
+
145
+ function getNearbyText(el) {
146
+ var texts = [];
147
+ var own = (el.textContent || "").trim();
148
+ if (own && own.length < 100) texts.push(own);
149
+ var prev = el.previousElementSibling;
150
+ if (prev) {
151
+ var pt = (prev.textContent || "").trim();
152
+ if (pt && pt.length < 50) texts.unshift('[before: "' + pt.slice(0, 40) + '"]');
153
+ }
154
+ var next = el.nextElementSibling;
155
+ if (next) {
156
+ var nt = (next.textContent || "").trim();
157
+ if (nt && nt.length < 50) texts.push('[after: "' + nt.slice(0, 40) + '"]');
158
+ }
159
+ return texts.join(" ");
160
+ }
161
+
162
+ function getClasses(el) {
163
+ if (typeof el.className !== "string" || !el.className) return "";
164
+ return el.className.split(/\s+/).filter(function (c) { return c.length > 0; }).join(", ");
165
+ }
166
+
167
+ function getComputedSnapshot(el) {
168
+ var s = window.getComputedStyle(el);
169
+ var parts = [];
170
+ if (s.color && s.color !== "rgb(0, 0, 0)") parts.push("color: " + s.color);
171
+ var bg = s.backgroundColor;
172
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") parts.push("bg: " + bg);
173
+ if (s.fontSize) parts.push("font: " + s.fontSize);
174
+ if (s.fontWeight && s.fontWeight !== "400" && s.fontWeight !== "normal") parts.push("weight: " + s.fontWeight);
175
+ if (s.padding && s.padding !== "0px") parts.push("padding: " + s.padding);
176
+ if (s.margin && s.margin !== "0px") parts.push("margin: " + s.margin);
177
+ var d = s.display;
178
+ if (d && d !== "block" && d !== "inline") parts.push("display: " + d);
179
+ if (s.position && s.position !== "static") parts.push("position: " + s.position);
180
+ if (s.borderRadius && s.borderRadius !== "0px") parts.push("radius: " + s.borderRadius);
181
+ return parts.join(", ");
182
+ }
183
+
184
+ function getFullPath(el) {
185
+ var parts = [];
186
+ var cur = el;
187
+ while (cur && cur.tagName && cur.tagName.toLowerCase() !== "html") {
188
+ var tag = cur.tagName.toLowerCase();
189
+ var ident = tag;
190
+ if (cur.id) ident = tag + "#" + cur.id;
191
+ else if (cur.className && typeof cur.className === "string") {
192
+ var cls = cur.className.split(/\s+/).find(function (c) { return c.length > 2; });
193
+ if (cls) ident = tag + "." + cls;
194
+ }
195
+ parts.unshift(ident);
196
+ cur = cur.parentElement;
197
+ }
198
+ return parts.join(" > ");
199
+ }
200
+
201
+ function getA11yInfo(el) {
202
+ var parts = [];
203
+ var role = el.getAttribute("role");
204
+ var ariaLabel = el.getAttribute("aria-label");
205
+ var tabIndex = el.getAttribute("tabindex");
206
+ var ariaHidden = el.getAttribute("aria-hidden");
207
+ if (role) parts.push('role="' + role + '"');
208
+ if (ariaLabel) parts.push('aria-label="' + ariaLabel + '"');
209
+ if (tabIndex) parts.push("tabindex=" + tabIndex);
210
+ if (ariaHidden === "true") parts.push("aria-hidden");
211
+ if (el.matches("a, button, input, select, textarea, [tabindex]")) parts.push("focusable");
212
+ return parts.join(", ");
213
+ }
214
+
215
+ // ═══════════════════════════════════════════
216
+ // Output Generation (ported from agentation)
217
+ // ═══════════════════════════════════════════
218
+
219
+ function generateOutput() {
220
+ if (state.annotations.length === 0) return "";
221
+ var viewport = window.innerWidth + "\u00d7" + window.innerHeight;
222
+ var pathname = window.location.pathname;
223
+ var dl = state.detailLevel;
224
+ var out = "## Page Feedback: " + pathname + "\n";
225
+
226
+ if (dl === "forensic") {
227
+ out += "\n**Environment:**\n";
228
+ out += "- Viewport: " + viewport + "\n";
229
+ out += "- URL: " + window.location.href + "\n";
230
+ out += "- Timestamp: " + new Date().toISOString() + "\n";
231
+ out += "\n---\n";
232
+ } else if (dl !== "compact") {
233
+ out += "**Viewport:** " + viewport + "\n";
234
+ }
235
+ out += "\n";
236
+
237
+ state.annotations.forEach(function (a, i) {
238
+ if (dl === "compact") {
239
+ out += (i + 1) + ". **" + a.element + "**: " + a.comment;
240
+ if (a.selectedText) out += ' (re: "' + a.selectedText.slice(0, 30) + '")';
241
+ out += "\n";
242
+ } else if (dl === "forensic") {
243
+ out += "### " + (i + 1) + ". " + a.element + "\n";
244
+ if (a.fullPath) out += "**Full DOM Path:** " + a.fullPath + "\n";
245
+ if (a.cssClasses) out += "**CSS Classes:** " + a.cssClasses + "\n";
246
+ if (a.boundingBox) {
247
+ var bb = a.boundingBox;
248
+ out += "**Position:** x:" + Math.round(bb.x) + ", y:" + Math.round(bb.y) +
249
+ " (" + Math.round(bb.width) + "\u00d7" + Math.round(bb.height) + "px)\n";
250
+ }
251
+ if (a.selectedText) out += '**Selected text:** "' + a.selectedText + '"\n';
252
+ if (a.nearbyText && !a.selectedText) out += "**Context:** " + a.nearbyText.slice(0, 100) + "\n";
253
+ if (a.computedStyles) out += "**Computed Styles:** " + a.computedStyles + "\n";
254
+ if (a.accessibility) out += "**Accessibility:** " + a.accessibility + "\n";
255
+ out += "**Feedback:** " + a.comment + "\n\n";
256
+ } else {
257
+ out += "### " + (i + 1) + ". " + a.element + "\n";
258
+ out += "**Location:** " + a.elementPath + "\n";
259
+ if (dl === "detailed") {
260
+ if (a.cssClasses) out += "**Classes:** " + a.cssClasses + "\n";
261
+ if (a.boundingBox) {
262
+ var b = a.boundingBox;
263
+ out += "**Position:** " + Math.round(b.x) + "px, " + Math.round(b.y) +
264
+ "px (" + Math.round(b.width) + "\u00d7" + Math.round(b.height) + "px)\n";
265
+ }
266
+ if (a.nearbyText && !a.selectedText) out += "**Context:** " + a.nearbyText.slice(0, 100) + "\n";
267
+ }
268
+ if (a.selectedText) out += '**Selected text:** "' + a.selectedText + '"\n';
269
+ out += "**Feedback:** " + a.comment + "\n\n";
270
+ }
271
+ });
272
+
273
+ return out.trim();
274
+ }
275
+
276
+ // ═══════════════════════════════════════════
277
+ // UI — Styles (injected once)
278
+ // ═══════════════════════════════════════════
279
+
280
+ function injectStyles() {
281
+ if (document.getElementById("annotate-js-styles")) return;
282
+ var style = document.createElement("style");
283
+ style.id = "annotate-js-styles";
284
+ style.textContent = [
285
+ // Design tokens (Claude-native palette)
286
+ // --ann-bg: #FAF9F5 cream surface
287
+ // --ann-bg-alt: #F5F4ED recessed surface
288
+ // --ann-ink: #1F1E1D primary text
289
+ // --ann-ink-mute: #75726B secondary text
290
+ // --ann-line: rgba(31,30,29,.08) hairline
291
+ // --ann-accent: #C96442 Claude orange
292
+ // --ann-accent-bg: rgba(201,100,66,.10)
293
+
294
+ // Toolbar
295
+ '#ann-toolbar{position:fixed;bottom:24px;right:24px;z-index:99999;',
296
+ 'font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;',
297
+ '-webkit-font-smoothing:antialiased;font-feature-settings:"ss01","cv11";',
298
+ 'font-size:13px;display:flex;align-items:center;gap:4px;',
299
+ 'background:#FAF9F5;color:#1F1E1D;padding:6px;border-radius:14px;',
300
+ 'height:44px;max-width:600px;overflow:hidden;',
301
+ 'border:1px solid rgba(31,30,29,.08);',
302
+ 'box-shadow:0 1px 2px rgba(31,30,29,.04),0 8px 24px rgba(31,30,29,.10);',
303
+ 'transition:max-width .36s cubic-bezier(.65,0,.35,1),',
304
+ 'padding .36s cubic-bezier(.65,0,.35,1),',
305
+ 'border-radius .36s cubic-bezier(.65,0,.35,1),',
306
+ 'box-shadow .25s ease;',
307
+ 'user-select:none;cursor:grab}',
308
+ '#ann-toolbar.ann-dragging,#ann-toolbar.ann-dragging *{cursor:grabbing !important}',
309
+
310
+ // Collapsed (round button) state — same element morphs
311
+ '#ann-toolbar.collapsed{max-width:44px;padding:0;border-radius:50%;',
312
+ 'overflow:visible;justify-content:center;cursor:grab;',
313
+ 'box-shadow:0 1px 2px rgba(31,30,29,.04),0 6px 18px rgba(31,30,29,.10)}',
314
+ '@media (hover:hover){#ann-toolbar.collapsed:hover{',
315
+ 'box-shadow:0 2px 4px rgba(31,30,29,.06),0 10px 28px rgba(31,30,29,.14)}}',
316
+
317
+ // Children fade — fast out on collapse, delayed in on expand
318
+ '#ann-toolbar > *:not(.ann-collapsed-view){',
319
+ 'opacity:1;transition:opacity .15s ease .22s}',
320
+ '#ann-toolbar.collapsed > *:not(.ann-collapsed-view){',
321
+ 'opacity:0;pointer-events:none;transition:opacity .12s ease 0s}',
322
+
323
+ // The collapsed face — pencil icon + badge, sits absolute over the row
324
+ '.ann-collapsed-view{position:absolute;inset:0;display:flex;align-items:center;',
325
+ 'justify-content:center;color:#C96442;pointer-events:none;opacity:0;',
326
+ 'transition:opacity .14s ease 0s}',
327
+ '#ann-toolbar.collapsed .ann-collapsed-view{opacity:1;pointer-events:auto;',
328
+ 'transition:opacity .14s ease .22s}',
329
+ '.ann-collapsed-view svg{width:18px;height:18px;display:block}',
330
+ '.ann-collapsed-view .ann-collapsed-badge{position:absolute;top:-4px;right:-4px;',
331
+ 'background:#C96442;color:#FAF9F5;border:1.5px solid #FAF9F5;',
332
+ 'border-radius:100px;min-width:17px;height:17px;padding:0 4px;',
333
+ 'font-size:10px;font-weight:600;font-variant-numeric:tabular-nums;',
334
+ 'display:flex;align-items:center;justify-content:center;line-height:1;',
335
+ 'pointer-events:none;transform-origin:center;',
336
+ 'transition:transform .2s cubic-bezier(.2,.8,.2,1)}',
337
+ '.ann-collapsed-view .ann-collapsed-badge.empty{display:none}',
338
+ '.ann-collapsed-view .ann-collapsed-badge.bump{animation:ann-badge-pop .35s cubic-bezier(.2,.8,.2,1)}',
339
+
340
+
341
+ '#ann-toolbar button{background:none;border:none;color:#1F1E1D;cursor:pointer;',
342
+ 'padding:7px 10px;border-radius:9px;font-size:13px;font-weight:500;font-family:inherit;',
343
+ 'transition:background .15s ease,color .15s ease,transform .1s ease;',
344
+ 'display:inline-flex;align-items:center;gap:6px;white-space:nowrap;line-height:1}',
345
+ '#ann-toolbar button svg{width:18px;height:18px;flex-shrink:0;display:block}',
346
+ '@media (hover:hover){#ann-toolbar button:hover{background:#F0EEE6;color:#1F1E1D}}',
347
+ '#ann-toolbar button:active{transform:scale(.97)}',
348
+ '#ann-toolbar button.active{background:rgba(201,100,66,.10);color:#C96442}',
349
+ '#ann-toolbar button.icon-only{padding:7px}',
350
+
351
+ // Eye-toggle button with corner badge
352
+ '#ann-toolbar .ann-eye-btn{position:relative}',
353
+ '#ann-toolbar .ann-eye-badge{position:absolute;top:-3px;right:-3px;',
354
+ 'background:#C96442;color:#FAF9F5;border:1.5px solid #FAF9F5;',
355
+ 'border-radius:100px;min-width:17px;height:17px;padding:0 4px;',
356
+ 'font-size:10px;font-weight:600;font-variant-numeric:tabular-nums;',
357
+ 'display:flex;align-items:center;justify-content:center;line-height:1;',
358
+ 'pointer-events:none;transform-origin:center;',
359
+ 'transition:transform .2s cubic-bezier(.2,.8,.2,1)}',
360
+ '#ann-toolbar .ann-eye-badge.empty{display:none}',
361
+ '#ann-toolbar .ann-eye-btn.hidden-state{color:#75726B}',
362
+ // pop on increment
363
+ '@keyframes ann-badge-pop{0%{transform:scale(1)}40%{transform:scale(1.25)}100%{transform:scale(1)}}',
364
+ '#ann-toolbar .ann-eye-badge.bump{animation:ann-badge-pop .35s cubic-bezier(.2,.8,.2,1)}',
365
+
366
+ // Copy success state
367
+ '#ann-toolbar button.success{background:rgba(34,165,90,.10)}',
368
+ '#ann-toolbar button.success svg{color:inherit}',
369
+
370
+ // Color swatch (toolbar circle)
371
+ '#ann-toolbar .ann-color-swatch{width:22px;height:22px;border-radius:50%;cursor:pointer;',
372
+ 'border:2px solid rgba(31,30,29,.10);flex-shrink:0;',
373
+ 'transition:transform .15s ease,border-color .15s ease}',
374
+ '@media (hover:hover){#ann-toolbar .ann-color-swatch:hover{transform:scale(1.1);',
375
+ 'border-color:rgba(31,30,29,.25)}}',
376
+ '#ann-toolbar .ann-color-swatch:active{transform:scale(.95)}',
377
+
378
+ // Color picker popover
379
+ '#ann-color-picker{position:fixed;z-index:99998;',
380
+ 'background:#FAF9F5;border:1px solid rgba(31,30,29,.08);',
381
+ 'border-radius:12px;padding:10px 14px;',
382
+ 'box-shadow:0 1px 2px rgba(31,30,29,.04),0 12px 32px rgba(31,30,29,.14);',
383
+ 'display:flex;gap:8px;align-items:center;',
384
+ 'animation:ann-popup-in .2s cubic-bezier(.2,.8,.2,1) both}',
385
+ '.ann-color-opt{width:28px;height:28px;border-radius:50%;cursor:pointer;',
386
+ 'flex-shrink:0;border:none;padding:0;',
387
+ 'transition:transform .12s ease}',
388
+ '@media (hover:hover){.ann-color-opt:hover{transform:scale(1.15)}}',
389
+ '.ann-color-opt:active{transform:scale(.92)}',
390
+ '.ann-color-opt.active{box-shadow:0 0 0 2px #FAF9F5,0 0 0 3.5px #1F1E1D}',
391
+
392
+ // Custom toolbar tooltip (pops above)
393
+ '#ann-tip{position:fixed;z-index:100000;',
394
+ 'background:#FAF9F5;border:1px solid rgba(31,30,29,.08);',
395
+ 'border-radius:8px;padding:5px 10px;',
396
+ 'box-shadow:0 2px 12px rgba(31,30,29,.12);',
397
+ 'font-family:ui-sans-serif,-apple-system,system-ui,sans-serif;',
398
+ '-webkit-font-smoothing:antialiased;',
399
+ 'font-size:12px;font-weight:500;color:#1F1E1D;',
400
+ 'white-space:nowrap;pointer-events:none;',
401
+ 'opacity:0;transform:translateX(-50%) translateY(4px);',
402
+ 'transition:opacity .1s ease,transform .1s ease}',
403
+ '#ann-tip.show{opacity:1;transform:translateX(-50%) translateY(0)}',
404
+ '#ann-tip .ann-tip-key{color:#9A968E;margin-left:6px;font-size:11px;font-weight:400}',
405
+ "#ann-tip::after{content:'';position:absolute;bottom:-5px;left:50%;",
406
+ 'transform:translateX(-50%);',
407
+ 'border-left:5px solid transparent;border-right:5px solid transparent;',
408
+ 'border-top:5px solid #FAF9F5}',
409
+
410
+ '#ann-toolbar .ann-divider{width:1px;height:18px;background:rgba(31,30,29,.10);margin:0 2px}',
411
+
412
+ '#ann-toolbar select{appearance:none;-webkit-appearance:none;-moz-appearance:none;',
413
+ 'background-color:#F5F4ED;',
414
+ 'background-image:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%2375726B\' stroke-width=\'2.5\' stroke-linecap=\'round\' stroke-linejoin=\'round\'><path d=\'m6 9 6 6 6-6\'/></svg>");',
415
+ 'background-repeat:no-repeat;background-position:right 9px center;',
416
+ 'border:1px solid rgba(31,30,29,.08);',
417
+ 'color:#1F1E1D;border-radius:8px;padding:5px 26px 5px 10px;',
418
+ 'font-size:12px;font-weight:500;line-height:1.4;',
419
+ 'font-family:inherit;cursor:pointer;transition:border-color .15s ease}',
420
+ '#ann-toolbar select:hover{border-color:rgba(31,30,29,.18)}',
421
+ '#ann-toolbar select:focus{outline:none;border-color:#C96442}',
422
+ '#ann-toolbar select::-ms-expand{display:none}',
423
+
424
+ // Hover highlight
425
+ '#ann-hover-highlight{position:fixed;z-index:99990;pointer-events:none;',
426
+ 'border:1.5px solid #C96442;border-radius:4px;',
427
+ 'background:rgba(201,100,66,.04);',
428
+ 'transition:all .12s cubic-bezier(.2,.8,.2,1)}',
429
+
430
+ // Hover label
431
+ '#ann-hover-label{position:fixed;z-index:99991;pointer-events:none;',
432
+ 'background:#1F1E1D;color:#FAF9F5;',
433
+ 'padding:5px 10px;border-radius:7px;font-size:11px;font-weight:500;',
434
+ 'font-family:ui-monospace,SFMono-Regular,Menlo,monospace;',
435
+ 'white-space:nowrap;box-shadow:0 4px 16px rgba(31,30,29,.18)}',
436
+
437
+ // Markers — outer wrapper handles position + entry pop only (no visual)
438
+ '.ann-marker{position:absolute;z-index:99992;width:24px;height:24px;',
439
+ 'cursor:pointer;overflow:visible;',
440
+ 'transform:translate(-50%,-50%);',
441
+ 'animation:ann-pop .3s cubic-bezier(.2,.8,.2,1) backwards}',
442
+ // Inner circle — the actual visible pin, scales on hover
443
+ '.ann-marker-circle{cursor:pointer;width:24px;height:24px;border-radius:50%;',
444
+ 'background:#C96442;color:#FAF9F5;',
445
+ 'font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;',
446
+ 'display:flex;align-items:center;justify-content:center;',
447
+ 'border:2px solid #FAF9F5;box-sizing:border-box;',
448
+ 'box-shadow:0 1px 2px rgba(31,30,29,.10),0 4px 12px rgba(201,100,66,.30);',
449
+ 'font-family:ui-sans-serif,-apple-system,system-ui,sans-serif;',
450
+ 'transform-origin:center;',
451
+ 'transition:transform .2s cubic-bezier(.2,.8,.2,1),box-shadow .2s ease}',
452
+ '.ann-marker .ann-marker-num,.ann-marker .ann-marker-edit{',
453
+ 'display:flex;align-items:center;justify-content:center;line-height:1}',
454
+ '.ann-marker .ann-marker-edit{display:none}',
455
+ '.ann-marker .ann-marker-edit svg{width:13px;height:13px;display:block}',
456
+ '@media (hover:hover){.ann-marker:hover .ann-marker-circle{',
457
+ 'transform:scale(1.3);',
458
+ 'box-shadow:0 2px 4px rgba(31,30,29,.12),0 8px 22px rgba(201,100,66,.42)}',
459
+ '.ann-marker:hover .ann-marker-num{display:none}',
460
+ '.ann-marker:hover .ann-marker-edit{display:flex}}',
461
+ '@keyframes ann-pop{from{transform:translate(-50%,-50%) scale(.8);opacity:0}',
462
+ 'to{transform:translate(-50%,-50%) scale(1);opacity:1}}',
463
+
464
+ // Marker tooltip
465
+ '.ann-marker-tip{position:absolute;left:30px;top:50%;transform:translateY(-50%);',
466
+ 'background:#FAF9F5;color:#1F1E1D;',
467
+ 'padding:10px 12px;border-radius:10px;font-size:12px;min-width:200px;max-width:320px;',
468
+ 'border:1px solid rgba(31,30,29,.08);',
469
+ 'box-shadow:0 1px 2px rgba(31,30,29,.04),0 12px 32px rgba(31,30,29,.14);',
470
+ 'pointer-events:none;white-space:normal;line-height:1.5;text-wrap:pretty;',
471
+ 'opacity:0;transition:opacity .15s}',
472
+ '.ann-marker:hover .ann-marker-tip{opacity:1}',
473
+ '.ann-marker-tip .ann-tip-element{color:#9A968E;font-size:11px;',
474
+ 'font-family:ui-monospace,SFMono-Regular,Menlo,monospace;',
475
+ 'display:block;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
476
+ '.ann-marker-tip .ann-tip-comment{color:#1F1E1D;font-size:12px;line-height:1.5}',
477
+
478
+ // Popup (comment input)
479
+ '#ann-popup{position:fixed;z-index:99998;background:#FAF9F5;',
480
+ 'border:1px solid rgba(31,30,29,.08);',
481
+ 'border-radius:14px;padding:18px;min-width:320px;max-width:400px;',
482
+ 'box-shadow:0 1px 2px rgba(31,30,29,.04),0 16px 48px rgba(31,30,29,.16);',
483
+ 'font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;',
484
+ '-webkit-font-smoothing:antialiased;',
485
+ 'animation:ann-popup-in .2s cubic-bezier(.2,.8,.2,1) both}',
486
+ '@keyframes ann-popup-in{from{transform:translateY(4px) scale(.98);opacity:0}',
487
+ 'to{transform:translateY(0) scale(1);opacity:1}}',
488
+
489
+ // Header — inline chevron + element name
490
+ '#ann-popup .ann-popup-header{display:flex;align-items:center;gap:5px;',
491
+ 'margin-bottom:8px;cursor:pointer;user-select:none}',
492
+ '#ann-popup .ann-popup-chevron{color:#9A968E;flex-shrink:0;',
493
+ 'width:14px;height:14px;display:block;',
494
+ 'transition:transform .15s ease;transform:rotate(-90deg)}',
495
+ '#ann-popup .ann-popup-header.expanded .ann-popup-chevron{transform:rotate(0deg)}',
496
+ '#ann-popup .ann-popup-label{color:#1F1E1D;font-size:13px;font-weight:400;',
497
+ 'font-family:ui-sans-serif,-apple-system,system-ui,sans-serif;',
498
+ 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}',
499
+
500
+ // Computed styles block (shown when header expanded)
501
+ '#ann-popup .ann-popup-styles{display:none;',
502
+ 'background:#F5F4ED;border:1px solid rgba(31,30,29,.06);',
503
+ 'border-radius:8px;padding:10px 12px;margin-bottom:12px;',
504
+ 'font-family:ui-monospace,SFMono-Regular,Menlo,monospace;',
505
+ 'font-size:12px;line-height:1.7;word-break:break-word}',
506
+ '#ann-popup .ann-popup-header.expanded ~ .ann-popup-styles{display:block}',
507
+ '#ann-popup .ann-popup-styles .ann-prop{color:#C96442}',
508
+ '#ann-popup .ann-popup-styles .ann-val{color:#1F1E1D}',
509
+
510
+ '#ann-popup textarea{width:100%;background:#F5F4ED;border:1px solid rgba(31,30,29,.08);',
511
+ 'color:#1F1E1D;border-radius:9px;padding:10px 12px;font-size:13px;',
512
+ 'font-family:inherit;-webkit-font-smoothing:antialiased;',
513
+ 'resize:vertical;min-height:64px;outline:none;line-height:1.55;',
514
+ 'transition:border-color .15s ease,background .15s ease}',
515
+ '#ann-popup textarea::placeholder{color:#A8A49B}',
516
+ '#ann-popup textarea:focus{border-color:#C96442;background:#FAF9F5}',
517
+
518
+ '#ann-popup .ann-popup-actions{display:flex;gap:8px;margin-top:12px;align-items:center;',
519
+ 'justify-content:flex-end}',
520
+ '#ann-popup .ann-popup-actions .ann-spacer{flex:1}',
521
+
522
+ '#ann-popup .ann-popup-actions button{background:transparent;border:none;',
523
+ 'color:#75726B;border-radius:9px;padding:8px 16px;font-size:13px;font-weight:500;',
524
+ 'cursor:pointer;font-family:inherit;',
525
+ 'transition:background .15s,color .15s,transform .1s}',
526
+ '@media (hover:hover){#ann-popup .ann-popup-actions button:hover{color:#1F1E1D}}',
527
+ '#ann-popup .ann-popup-actions button:active{transform:scale(.97)}',
528
+ '#ann-popup .ann-popup-actions button.primary{background:#C96442;',
529
+ 'color:#FAF9F5;border-radius:100px;padding:8px 20px}',
530
+ '@media (hover:hover){#ann-popup .ann-popup-actions button.primary:hover{background:#B5573A}}',
531
+ '#ann-popup .ann-popup-actions button.icon-only{padding:7px;color:#9A968E}',
532
+ '#ann-popup .ann-popup-actions button.icon-only svg{width:15px;height:15px;display:block}',
533
+ '@media (hover:hover){#ann-popup .ann-popup-actions button.icon-only:hover{',
534
+ 'background:rgba(201,100,66,.10);color:#C96442}}',
535
+
536
+ // Active cursor — asterisk SVG
537
+ "body.ann-active{cursor:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 155 157' fill='none'%3E%3Cpath d='M58.8282 156.513L62.2248 103.934L18.2055 133.416L0 101.353L47.4158 78.2565L0 55.1599L18.2055 23.0965L62.2248 52.5786L58.8282 0H95.3751L91.8427 52.5786L135.862 23.0965L154.067 55.1599L106.787 78.2565L154.067 101.353L135.862 133.416L91.8427 103.934L95.3751 156.513H58.8282Z' fill='%23C96442'/%3E%3C/svg%3E\") 14 14,crosshair !important}",
538
+ 'body.ann-active *{cursor:inherit !important}',
539
+ // Override crosshair on the tool's own UI
540
+ 'body.ann-active #ann-toolbar{cursor:grab !important}',
541
+ 'body.ann-active #ann-toolbar *,',
542
+ 'body.ann-active #ann-color-picker,body.ann-active #ann-color-picker *,',
543
+ 'body.ann-active #ann-popup,body.ann-active #ann-popup *,',
544
+ 'body.ann-active .ann-marker,body.ann-active .ann-marker *{cursor:auto !important}',
545
+ 'body.ann-active #ann-ruler-h,body.ann-active #ann-ruler-v{cursor:crosshair !important}',
546
+ 'body.ann-active #ann-ruler-corner{cursor:default !important}',
547
+ 'body.ann-active #ann-toolbar button,body.ann-active #ann-toolbar .ann-color-swatch,',
548
+ 'body.ann-active #ann-color-picker .ann-color-opt,',
549
+ 'body.ann-active .ann-collapsed-view,',
550
+ 'body.ann-active #ann-popup button,body.ann-active #ann-popup .ann-popup-header,',
551
+ 'body.ann-active .ann-marker{cursor:pointer !important}',
552
+ 'body.ann-active #ann-popup textarea{cursor:text !important}',
553
+ 'body.ann-active .ann-guide-handle{cursor:grab !important}',
554
+ 'body.ann-active .ann-guide-handle:active{cursor:grabbing !important}',
555
+ 'body.ann-active .ann-guide-close{cursor:pointer !important}',
556
+
557
+ // Copied toast
558
+ '#ann-toast{position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-12px);',
559
+ 'z-index:99999;background:#1F1E1D;color:#FAF9F5;padding:10px 18px;',
560
+ 'border-radius:100px;font-size:13px;font-weight:500;',
561
+ 'font-family:ui-sans-serif,-apple-system,system-ui,sans-serif;',
562
+ '-webkit-font-smoothing:antialiased;',
563
+ 'box-shadow:0 1px 2px rgba(31,30,29,.10),0 12px 32px rgba(31,30,29,.24);',
564
+ 'opacity:0;transition:all .25s cubic-bezier(.2,.8,.2,1);pointer-events:none;',
565
+ 'display:inline-flex;align-items:center;gap:8px}',
566
+ '#ann-toast svg{width:14px;height:14px;color:#C96442}',
567
+ '#ann-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}',
568
+
569
+ // Rulers — hidden by default, slide in on edge hover
570
+ // Base state defines EXIT transition (short, ease-in — subtle retreat)
571
+ '#ann-ruler-h,#ann-ruler-v{position:fixed;z-index:99988;',
572
+ 'background:rgba(250,249,245,.92);backdrop-filter:blur(6px);',
573
+ 'cursor:crosshair;pointer-events:none;',
574
+ 'opacity:0;transition:opacity .15s ease-in,transform .15s ease-in}',
575
+ '#ann-ruler-h{top:0;left:19px;right:0;height:20px;',
576
+ 'border-bottom:1px solid rgba(31,30,29,.08);',
577
+ 'transform:translateY(-100%)}',
578
+ '#ann-ruler-v{top:19px;left:0;bottom:0;width:20px;',
579
+ 'border-right:1px solid rgba(31,30,29,.08);',
580
+ 'transform:translateX(-100%)}',
581
+ '#ann-ruler-corner{position:fixed;z-index:99989;top:0;left:0;width:20px;height:20px;',
582
+ 'background:rgba(250,249,245,.92);backdrop-filter:blur(6px);',
583
+ 'border-right:1px solid rgba(31,30,29,.08);',
584
+ 'border-bottom:1px solid rgba(31,30,29,.08);',
585
+ 'pointer-events:none;opacity:0;transform:scale(.9);',
586
+ 'transition:opacity .15s ease-in,transform .15s ease-in}',
587
+ // Show state defines ENTER transition (longer, bouncy easing — confident slide-in)
588
+ '#ann-ruler-h.ann-ruler-show{opacity:1;transform:translateY(0);pointer-events:auto;',
589
+ 'transition:opacity .3s cubic-bezier(.2,.8,.2,1),transform .3s cubic-bezier(.2,.8,.2,1)}',
590
+ '#ann-ruler-v.ann-ruler-show{opacity:1;transform:translateX(0);pointer-events:auto;',
591
+ 'transition:opacity .3s cubic-bezier(.2,.8,.2,1),transform .3s cubic-bezier(.2,.8,.2,1)}',
592
+ '#ann-ruler-corner.ann-ruler-show{opacity:1;transform:scale(1);pointer-events:auto;',
593
+ 'transition:opacity .3s cubic-bezier(.2,.8,.2,1),transform .3s cubic-bezier(.2,.8,.2,1)}',
594
+ '#ann-ruler-h canvas,#ann-ruler-v canvas{display:block}',
595
+
596
+ // Guide lines (dropped from rulers)
597
+ '.ann-guide{position:fixed;z-index:99987;pointer-events:auto}',
598
+ '.ann-guide-h{left:0;right:0;height:0;padding:4px 0;border-top:1px solid;',
599
+ 'cursor:ns-resize;background:none !important;box-sizing:content-box}',
600
+ '.ann-guide-v{top:0;bottom:0;width:0;padding:0 4px;border-left:1px solid;',
601
+ 'cursor:ew-resize;background:none !important;box-sizing:content-box}',
602
+ // Guide handle (drag affordance + label + close)
603
+ '.ann-guide-handle{position:absolute;display:flex;align-items:center;gap:2px;',
604
+ 'background:#FAF9F5;border:1px solid rgba(31,30,29,.10);',
605
+ 'border-radius:6px;padding:3px 3px 3px 8px;',
606
+ 'box-shadow:0 1px 6px rgba(31,30,29,.10);',
607
+ 'cursor:grab;user-select:none;',
608
+ 'font-family:ui-monospace,SFMono-Regular,Menlo,monospace;',
609
+ 'font-size:10px;font-weight:500;color:#1F1E1D;white-space:nowrap;',
610
+ 'font-variant-numeric:tabular-nums;',
611
+ 'opacity:0;transition:opacity .15s ease}',
612
+ '.ann-guide:hover .ann-guide-handle,.ann-guide-handle.dragging{opacity:1}',
613
+ '.ann-guide-handle:active{cursor:grabbing}',
614
+ '.ann-guide-h .ann-guide-handle{left:24px;top:50%;transform:translateY(-50%)}',
615
+ '.ann-guide-v .ann-guide-handle{top:24px;left:50%;transform:translateX(-50%)}',
616
+ '.ann-guide-close{background:none;border:none;color:#9A968E;cursor:pointer;',
617
+ 'padding:2px;border-radius:4px;display:flex;align-items:center;justify-content:center;',
618
+ 'transition:color .12s,background .12s}',
619
+ '@media (hover:hover){.ann-guide-close:hover{background:rgba(31,30,29,.08);color:#1F1E1D}}',
620
+ '.ann-guide-close svg{width:12px;height:12px;display:block}',
621
+ // Wider invisible hit area on the line itself
622
+ '.ann-guide::before{content:"";position:absolute}',
623
+ '.ann-guide-h::before{left:0;right:0;top:-4px;bottom:-4px}',
624
+ '.ann-guide-v::before{top:0;bottom:0;left:-4px;right:-4px}',
625
+
626
+ // Reduced motion
627
+ '@media (prefers-reduced-motion:reduce){',
628
+ '#ann-toolbar,#ann-popup,#ann-toast,.ann-marker,#ann-hover-highlight,',
629
+ '#ann-ruler-h,#ann-ruler-v,#ann-ruler-corner{',
630
+ 'transition:none !important;animation:none !important}}',
631
+ ].join("\n");
632
+ document.head.appendChild(style);
633
+ }
634
+
635
+ // ═══════════════════════════════════════════
636
+ // UI — DOM helpers
637
+ // ═══════════════════════════════════════════
638
+
639
+ function el(tag, attrs, children) {
640
+ var e = document.createElement(tag);
641
+ if (attrs) Object.keys(attrs).forEach(function (k) {
642
+ if (k === "className") e.className = attrs[k];
643
+ else if (k.startsWith("on")) e.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
644
+ else e.setAttribute(k, attrs[k]);
645
+ });
646
+ if (children) {
647
+ if (typeof children === "string") e.textContent = children;
648
+ else if (Array.isArray(children)) children.forEach(function (c) { if (c) e.appendChild(c); });
649
+ else e.appendChild(children);
650
+ }
651
+ return e;
652
+ }
653
+
654
+ // ═══════════════════════════════════════════
655
+ // Icons (Lucide — ISC license, inlined for zero-dep)
656
+ // ═══════════════════════════════════════════
657
+
658
+ var ICON_ATTRS = 'xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ' +
659
+ 'fill="none" stroke="currentColor" stroke-width="2" ' +
660
+ 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"';
661
+
662
+ var ICONS = {
663
+ // square-pen (toolbar Annotate button + collapsed face)
664
+ pencil: '<svg ' + ICON_ATTRS + '>' +
665
+ '<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>' +
666
+ '<path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/>' +
667
+ '</svg>',
668
+ // plain pencil (marker hover edit)
669
+ pencilPlain: '<svg ' + ICON_ATTRS + '>' +
670
+ '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/>' +
671
+ '<path d="m15 5 4 4"/>' +
672
+ '</svg>',
673
+ // copy
674
+ copy: '<svg ' + ICON_ATTRS + '>' +
675
+ '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>' +
676
+ '<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>' +
677
+ '</svg>',
678
+ // trash-2
679
+ trash: '<svg ' + ICON_ATTRS + '>' +
680
+ '<path d="M3 6h18"/>' +
681
+ '<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>' +
682
+ '<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>' +
683
+ '<line x1="10" x2="10" y1="11" y2="17"/>' +
684
+ '<line x1="14" x2="14" y1="11" y2="17"/>' +
685
+ '</svg>',
686
+ // x
687
+ x: '<svg ' + ICON_ATTRS + '>' +
688
+ '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>' +
689
+ '</svg>',
690
+ // check
691
+ check: '<svg ' + ICON_ATTRS + '>' +
692
+ '<path d="M20 6 9 17l-5-5"/>' +
693
+ '</svg>',
694
+ // eye
695
+ eye: '<svg ' + ICON_ATTRS + '>' +
696
+ '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/>' +
697
+ '<circle cx="12" cy="12" r="3"/>' +
698
+ '</svg>',
699
+ // eye-off
700
+ eyeOff: '<svg ' + ICON_ATTRS + '>' +
701
+ '<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/>' +
702
+ '<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/>' +
703
+ '<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/>' +
704
+ '<path d="m2 2 20 20"/>' +
705
+ '</svg>',
706
+ // chevron-right
707
+ chevronRight: '<svg ' + ICON_ATTRS + '>' +
708
+ '<path d="m9 18 6-6-6-6"/>' +
709
+ '</svg>',
710
+ // chevron-down
711
+ chevronDown: '<svg ' + ICON_ATTRS + '>' +
712
+ '<path d="m6 9 6 6 6-6"/>' +
713
+ '</svg>',
714
+ // asterisk logo mark
715
+ asterisk: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 155 157" fill="currentColor" aria-hidden="true">' +
716
+ '<path d="M58.8282 156.513L62.2248 103.934L18.2055 133.416L0 101.353L47.4158 78.2565L0 55.1599L18.2055 23.0965L62.2248 52.5786L58.8282 0H95.3751L91.8427 52.5786L135.862 23.0965L154.067 55.1599L106.787 78.2565L154.067 101.353L135.862 133.416L91.8427 103.934L95.3751 156.513H58.8282Z"/>' +
717
+ '</svg>',
718
+ // check-circle filled (for copy success)
719
+ checkCircleFilled: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true">' +
720
+ '<circle cx="12" cy="12" r="10" fill="#22A55A"/>' +
721
+ '<path d="m8.5 12.5 2.5 2.5 4.5-5" fill="none" stroke="#FAF9F5" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>' +
722
+ '</svg>',
723
+ };
724
+
725
+ function iconEl(name) {
726
+ var span = document.createElement("span");
727
+ span.innerHTML = ICONS[name];
728
+ return span.firstChild;
729
+ }
730
+
731
+ // ═══════════════════════════════════════════
732
+ // UI — Toolbar
733
+ // ═══════════════════════════════════════════
734
+
735
+ var toolbar, hoverHighlight, hoverLabel, popup, toast;
736
+ var markersContainer;
737
+ var eyeBtn, eyeIconWrap, eyeBadge, copyBtn;
738
+ var collapsedView, collapsedBadge;
739
+ var colorSwatch, colorPicker;
740
+ var accentSheet;
741
+ var tipEl;
742
+
743
+ function cursorSvg(hex) {
744
+ var encoded = hex.replace("#", "%23");
745
+ return "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 155 157' fill='none'%3E%3Cpath d='M58.8282 156.513L62.2248 103.934L18.2055 133.416L0 101.353L47.4158 78.2565L0 55.1599L18.2055 23.0965L62.2248 52.5786L58.8282 0H95.3751L91.8427 52.5786L135.862 23.0965L154.067 55.1599L106.787 78.2565L154.067 101.353L135.862 133.416L91.8427 103.934L95.3751 156.513H58.8282Z' fill='" + encoded + "'/%3E%3C/svg%3E\") 14 14,crosshair";
746
+ }
747
+
748
+ function darkenHex(hex, amt) {
749
+ var r = Math.max(0, parseInt(hex.slice(1, 3), 16) - amt);
750
+ var g = Math.max(0, parseInt(hex.slice(3, 5), 16) - amt);
751
+ var b = Math.max(0, parseInt(hex.slice(5, 7), 16) - amt);
752
+ return "#" + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1);
753
+ }
754
+
755
+ function updateAccentColor(hex) {
756
+ state.markerColor = hex;
757
+ if (colorSwatch) colorSwatch.style.background = hex;
758
+
759
+ if (!accentSheet) {
760
+ accentSheet = document.createElement("style");
761
+ accentSheet.id = "ann-accent-styles";
762
+ document.head.appendChild(accentSheet);
763
+ }
764
+
765
+ var bg10 = hex + "1A"; // 10%
766
+ var bg42 = hex + "6B"; // 42%
767
+ var dark = darkenHex(hex, 20);
768
+
769
+ accentSheet.textContent = [
770
+ "#ann-toolbar button.active{background:" + bg10 + ";color:" + hex + "}",
771
+ "#ann-toolbar .ann-eye-badge{background:" + hex + "}",
772
+ ".ann-collapsed-view{color:" + hex + "}",
773
+ ".ann-collapsed-view .ann-collapsed-badge{background:" + hex + "}",
774
+ "#ann-popup .ann-popup-label{color:" + hex + "}",
775
+ "#ann-popup .ann-popup-header.expanded .ann-popup-chevron{color:" + hex + "}",
776
+ "#ann-popup textarea:focus{border-color:" + hex + "}",
777
+ "#ann-popup .ann-popup-actions button.primary{background:" + hex + ";border-color:" + hex + "}",
778
+ "#ann-toast svg{color:" + hex + "}",
779
+ "body.ann-active{cursor:" + cursorSvg(hex) + " !important}",
780
+ "@media (hover:hover){",
781
+ "#ann-popup .ann-popup-actions button.primary:hover{background:" + dark + ";border-color:" + dark + "}",
782
+ "#ann-popup .ann-popup-actions button.icon-only:hover{background:" + bg10 + ";color:" + hex + "}",
783
+ ".ann-marker:hover .ann-marker-circle{box-shadow:0 2px 4px rgba(31,30,29,.12),0 8px 22px " + bg42 + "}}",
784
+ ].join("\n");
785
+
786
+ renderMarkers();
787
+ }
788
+
789
+ function addTip(target, text, key) {
790
+ target.addEventListener("mouseenter", function () {
791
+ if (!tipEl) return;
792
+ tipEl.innerHTML = escHtml(text) + (key ? '<span class="ann-tip-key">' + escHtml(key) + '</span>' : "");
793
+ var r = target.getBoundingClientRect();
794
+ tipEl.style.left = (r.left + r.width / 2) + "px";
795
+ tipEl.style.top = (r.top - 8) + "px";
796
+ tipEl.style.bottom = "auto";
797
+ // Anchor bottom of tooltip to that top value
798
+ requestAnimationFrame(function () {
799
+ var th = tipEl.offsetHeight;
800
+ tipEl.style.top = (r.top - 8 - th) + "px";
801
+ tipEl.classList.add("show");
802
+ });
803
+ });
804
+ target.addEventListener("mouseleave", function () {
805
+ if (tipEl) tipEl.classList.remove("show");
806
+ });
807
+ }
808
+
809
+ function createToolbar() {
810
+ injectStyles();
811
+
812
+ // Hover highlight box
813
+ hoverHighlight = el("div", { id: "ann-hover-highlight" });
814
+ hoverHighlight.style.display = "none";
815
+ document.body.appendChild(hoverHighlight);
816
+
817
+ // Hover label
818
+ hoverLabel = el("div", { id: "ann-hover-label" });
819
+ hoverLabel.style.display = "none";
820
+ document.body.appendChild(hoverLabel);
821
+
822
+ // Markers container
823
+ markersContainer = el("div", { id: "ann-markers", style: "position:absolute;top:0;left:0;width:0;height:0;overflow:visible;z-index:99992" });
824
+ document.body.appendChild(markersContainer);
825
+
826
+ // Toast
827
+ toast = el("div", { id: "ann-toast" });
828
+ toast.appendChild(iconEl("check"));
829
+ toast.appendChild(document.createTextNode("Copied to clipboard"));
830
+ document.body.appendChild(toast);
831
+
832
+ // Build toolbar
833
+ toolbar = el("div", { id: "ann-toolbar" });
834
+
835
+ var isMac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
836
+ var altKey = isMac ? "\u2325" : "Alt";
837
+
838
+ // Shared tooltip element
839
+ tipEl = el("div", { id: "ann-tip" });
840
+ document.body.appendChild(tipEl);
841
+
842
+ // Toggle annotate mode
843
+ var toggleBtn = el("button", {
844
+ onClick: toggleActive
845
+ });
846
+ toggleBtn.id = "ann-toggle-btn";
847
+ toggleBtn.appendChild(iconEl("asterisk"));
848
+ toggleBtn.appendChild(document.createTextNode("Annotate"));
849
+
850
+ // Color swatch
851
+ colorSwatch = el("div", {
852
+ className: "ann-color-swatch",
853
+ role: "button",
854
+ tabindex: "0",
855
+ "aria-label": "Change marker color"
856
+ });
857
+ colorSwatch.style.background = state.markerColor;
858
+ colorSwatch.addEventListener("click", function (e) { e.stopPropagation(); showColorPicker(); });
859
+ colorSwatch.addEventListener("keydown", function (e) {
860
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); showColorPicker(); }
861
+ });
862
+
863
+ var divider1 = el("span", { className: "ann-divider" });
864
+
865
+ // Eye toggle (markers visibility) with corner count badge
866
+ eyeBtn = el("button", {
867
+ className: "icon-only ann-eye-btn",
868
+ onClick: toggleMarkersVisibility,
869
+ "aria-label": "Show or hide markers"
870
+ });
871
+ eyeIconWrap = el("span", { className: "ann-eye-icon" });
872
+ eyeIconWrap.appendChild(iconEl("eye"));
873
+ eyeBadge = el("span", { className: "ann-eye-badge empty" }, "0");
874
+ eyeBtn.appendChild(eyeIconWrap);
875
+ eyeBtn.appendChild(eyeBadge);
876
+
877
+ // Copy
878
+ copyBtn = el("button", {
879
+ className: "icon-only",
880
+ onClick: copyOutput,
881
+ "aria-label": "Copy feedback"
882
+ });
883
+ copyBtn.appendChild(iconEl("copy"));
884
+
885
+ // Clear all
886
+ var clearBtn = el("button", {
887
+ className: "icon-only",
888
+ onClick: clearAll,
889
+ "aria-label": "Clear all"
890
+ });
891
+ clearBtn.appendChild(iconEl("trash"));
892
+
893
+ var divider3 = el("span", { className: "ann-divider" });
894
+
895
+ // Close (collapse toolbar)
896
+ var closeBtn = el("button", {
897
+ className: "icon-only",
898
+ onClick: hideToolbar,
899
+ "aria-label": "Close"
900
+ });
901
+ closeBtn.appendChild(iconEl("x"));
902
+
903
+ var toggleKey = (isMac ? "\u2325" : "Alt") + "A";
904
+ var extKey = isMac ? "\u2325\u21E7A" : "Alt+Shift+A";
905
+ addTip(toggleBtn, "Toggle mode", toggleKey);
906
+ addTip(colorSwatch, "Marker color", "M");
907
+ addTip(eyeBtn, "Show/hide markers", "H");
908
+ addTip(copyBtn, "Copy markdown", "C");
909
+ addTip(clearBtn, "Clear all");
910
+ addTip(closeBtn, "Close", extKey);
911
+
912
+ toolbar.appendChild(toggleBtn);
913
+ toolbar.appendChild(colorSwatch);
914
+ toolbar.appendChild(divider1);
915
+ toolbar.appendChild(eyeBtn);
916
+ toolbar.appendChild(copyBtn);
917
+ toolbar.appendChild(clearBtn);
918
+ toolbar.appendChild(divider3);
919
+ toolbar.appendChild(closeBtn);
920
+
921
+ // Collapsed face — overlay sibling that's revealed when toolbar morphs to round
922
+ collapsedView = el("div", {
923
+ className: "ann-collapsed-view",
924
+ role: "button",
925
+ tabindex: "0",
926
+ title: "Open annotate-html (" + altKey + "+A)",
927
+ "aria-label": "Open annotate toolbar"
928
+ });
929
+ collapsedView.appendChild(iconEl("asterisk"));
930
+ collapsedBadge = el("span", { className: "ann-collapsed-badge empty" }, "0");
931
+ collapsedView.appendChild(collapsedBadge);
932
+ collapsedView.addEventListener("click", showToolbar);
933
+ collapsedView.addEventListener("keydown", function (e) {
934
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); showToolbar(); }
935
+ });
936
+ toolbar.appendChild(collapsedView);
937
+
938
+ document.body.appendChild(toolbar);
939
+
940
+ // Make toolbar draggable
941
+ initToolbarDrag();
942
+
943
+ // Create rulers (visible when annotation mode is active)
944
+ createRulers();
945
+ }
946
+
947
+ // ═══════════════════════════════════════════
948
+ // Toolbar drag
949
+ // ═══════════════════════════════════════════
950
+
951
+ function initToolbarDrag() {
952
+ var dragging = false;
953
+ var didDrag = false;
954
+ var startX, startY, origLeft, origTop;
955
+
956
+ toolbar.addEventListener("mousedown", function (e) {
957
+ // Only drag from non-interactive surfaces
958
+ var node = e.target;
959
+ while (node && node !== toolbar) {
960
+ var tag = node.tagName;
961
+ if (tag === "BUTTON" || tag === "SELECT" || tag === "TEXTAREA" || tag === "INPUT") return;
962
+ if (node.className && typeof node.className === "string" &&
963
+ node.className.indexOf("ann-color-swatch") !== -1) return;
964
+ node = node.parentElement;
965
+ }
966
+
967
+ e.preventDefault();
968
+ dragging = false;
969
+ didDrag = false;
970
+ startX = e.clientX;
971
+ startY = e.clientY;
972
+ var rect = toolbar.getBoundingClientRect();
973
+ origLeft = rect.left;
974
+ origTop = rect.top;
975
+
976
+ function onMove(ev) {
977
+ var dx = ev.clientX - startX;
978
+ var dy = ev.clientY - startY;
979
+ if (!dragging && Math.abs(dx) + Math.abs(dy) > 3) {
980
+ dragging = true;
981
+ didDrag = true;
982
+ toolbar.classList.add("ann-dragging");
983
+ toolbar.style.bottom = "auto";
984
+ toolbar.style.right = "auto";
985
+ }
986
+ if (dragging) {
987
+ var w = toolbar.offsetWidth;
988
+ var h = toolbar.offsetHeight;
989
+ var l = Math.max(0, Math.min(origLeft + dx, window.innerWidth - w));
990
+ var t = Math.max(0, Math.min(origTop + dy, window.innerHeight - h));
991
+ toolbar.style.left = l + "px";
992
+ toolbar.style.top = t + "px";
993
+ }
994
+ }
995
+
996
+ function onUp() {
997
+ document.removeEventListener("mousemove", onMove);
998
+ document.removeEventListener("mouseup", onUp);
999
+ toolbar.classList.remove("ann-dragging");
1000
+ dragging = false;
1001
+ if (didDrag) {
1002
+ // Anchor to whichever side the toolbar landed on
1003
+ var rect = toolbar.getBoundingClientRect();
1004
+ var center = rect.left + rect.width / 2;
1005
+ if (center >= window.innerWidth / 2) {
1006
+ // Right half — anchor right, expands leftward
1007
+ toolbar.style.right = (window.innerWidth - rect.right) + "px";
1008
+ toolbar.style.left = "auto";
1009
+ }
1010
+ // Left half — keep left anchoring, expands rightward
1011
+
1012
+ // Suppress the click that fires after drag
1013
+ var suppress = function (ev) { ev.stopPropagation(); ev.preventDefault(); };
1014
+ toolbar.addEventListener("click", suppress, true);
1015
+ setTimeout(function () { toolbar.removeEventListener("click", suppress, true); }, 0);
1016
+ }
1017
+ }
1018
+
1019
+ document.addEventListener("mousemove", onMove);
1020
+ document.addEventListener("mouseup", onUp);
1021
+ });
1022
+
1023
+ // Re-clamp after expand/collapse transition or window resize
1024
+ toolbar.addEventListener("transitionend", function (e) {
1025
+ if (e.propertyName === "max-width") clampToolbar();
1026
+ });
1027
+ }
1028
+
1029
+ function clampToolbar() {
1030
+ if (!toolbar || toolbar.style.top === "") return;
1031
+ var rect = toolbar.getBoundingClientRect();
1032
+ var anchorsLeft = toolbar.style.left !== "" && toolbar.style.left !== "auto";
1033
+
1034
+ if (anchorsLeft) {
1035
+ // Left-anchored — check right overflow
1036
+ if (rect.right > window.innerWidth) {
1037
+ toolbar.style.left = Math.max(0, window.innerWidth - rect.width) + "px";
1038
+ }
1039
+ } else {
1040
+ // Right-anchored — check left overflow
1041
+ if (rect.left < 0) {
1042
+ toolbar.style.right = Math.max(0, window.innerWidth - rect.width) + "px";
1043
+ }
1044
+ }
1045
+ if (rect.bottom > window.innerHeight) {
1046
+ toolbar.style.top = Math.max(0, window.innerHeight - rect.height) + "px";
1047
+ }
1048
+ if (rect.top < 0) {
1049
+ toolbar.style.top = "0px";
1050
+ }
1051
+ }
1052
+
1053
+ // ═══════════════════════════════════════════
1054
+ // Actions
1055
+ // ═══════════════════════════════════════════
1056
+
1057
+ // ═══════════════════════════════════════════
1058
+ // Rulers & Guides
1059
+ // ═══════════════════════════════════════════
1060
+
1061
+ var rulerH, rulerV, rulerCorner, rulerHCanvas, rulerVCanvas;
1062
+ var guides = [];
1063
+
1064
+ function createRulers() {
1065
+ rulerCorner = el("div", { id: "ann-ruler-corner" });
1066
+ document.body.appendChild(rulerCorner);
1067
+
1068
+ rulerH = el("div", { id: "ann-ruler-h" });
1069
+ rulerHCanvas = document.createElement("canvas");
1070
+ rulerHCanvas.height = 20;
1071
+ rulerH.appendChild(rulerHCanvas);
1072
+ initRulerDrag(rulerH, "v");
1073
+ document.body.appendChild(rulerH);
1074
+
1075
+ rulerV = el("div", { id: "ann-ruler-v" });
1076
+ rulerVCanvas = document.createElement("canvas");
1077
+ rulerVCanvas.width = 20;
1078
+ rulerV.appendChild(rulerVCanvas);
1079
+ initRulerDrag(rulerV, "h");
1080
+ document.body.appendChild(rulerV);
1081
+
1082
+ drawRulers();
1083
+ }
1084
+
1085
+ function initRulerDrag(rulerEl, guideDir) {
1086
+ var preview = null;
1087
+
1088
+ rulerEl.addEventListener("mousedown", function (e) {
1089
+ e.preventDefault();
1090
+ state.rulerDragging = true;
1091
+ preview = el("div", { className: "ann-guide ann-guide-" + guideDir });
1092
+ preview.style.borderColor = state.markerColor;
1093
+ preview.style.opacity = "0.5";
1094
+ var lbl = el("span", { className: "ann-guide-label" }, "0px");
1095
+ preview.appendChild(lbl);
1096
+ document.body.appendChild(preview);
1097
+
1098
+ function onMove(ev) {
1099
+ if (!preview) return;
1100
+ var pos, pagePos;
1101
+ if (guideDir === "h") {
1102
+ pos = ev.clientY;
1103
+ pagePos = pos + window.scrollY;
1104
+ preview.style.top = pos + "px";
1105
+ } else {
1106
+ pos = ev.clientX;
1107
+ pagePos = pos + window.scrollX;
1108
+ preview.style.left = pos + "px";
1109
+ }
1110
+ preview.querySelector(".ann-guide-label").textContent = Math.round(pagePos) + "px";
1111
+ // If dragged back onto the ruler, dim it
1112
+ var onRuler = guideDir === "h" ? ev.clientX < 20 : ev.clientY < 20;
1113
+ preview.style.opacity = onRuler ? "0.2" : "0.5";
1114
+ }
1115
+
1116
+ function onUp(ev) {
1117
+ document.removeEventListener("mousemove", onMove);
1118
+ document.removeEventListener("mouseup", onUp);
1119
+ state.rulerDragging = false;
1120
+ if (!preview) return;
1121
+ // Check if dropped back on the ruler — cancel
1122
+ var onRuler = guideDir === "h" ? ev.clientX < 20 : ev.clientY < 20;
1123
+ if (preview.parentNode) preview.parentNode.removeChild(preview);
1124
+ preview = null;
1125
+ if (onRuler) return;
1126
+
1127
+ var viewPos, pagePos;
1128
+ if (guideDir === "h") {
1129
+ viewPos = ev.clientY;
1130
+ pagePos = viewPos + window.scrollY;
1131
+ } else {
1132
+ viewPos = ev.clientX;
1133
+ pagePos = viewPos + window.scrollX;
1134
+ }
1135
+ addGuide(guideDir, viewPos, pagePos);
1136
+ }
1137
+
1138
+ document.addEventListener("mousemove", onMove);
1139
+ document.addEventListener("mouseup", onUp);
1140
+ });
1141
+ }
1142
+
1143
+ function drawRulers() {
1144
+ if (!rulerHCanvas || !rulerVCanvas) return;
1145
+ var dpr = window.devicePixelRatio || 1;
1146
+ var w = window.innerWidth - 20;
1147
+ var h = window.innerHeight - 20;
1148
+ var sx = window.scrollX;
1149
+ var sy = window.scrollY;
1150
+ var tickColor = "rgba(31,30,29,.25)";
1151
+ var textColor = "rgba(31,30,29,.5)";
1152
+
1153
+ // Horizontal ruler
1154
+ rulerHCanvas.width = w * dpr;
1155
+ rulerHCanvas.height = 20 * dpr;
1156
+ rulerHCanvas.style.width = w + "px";
1157
+ rulerHCanvas.style.height = "20px";
1158
+ var ctxH = rulerHCanvas.getContext("2d");
1159
+ ctxH.scale(dpr, dpr);
1160
+ ctxH.font = "9px ui-monospace,SFMono-Regular,Menlo,monospace";
1161
+ ctxH.fillStyle = textColor;
1162
+ ctxH.strokeStyle = tickColor;
1163
+ ctxH.lineWidth = 0.5;
1164
+ var startX = Math.floor(sx / 10) * 10;
1165
+ for (var px = startX; px < sx + w; px += 10) {
1166
+ var x = px - sx;
1167
+ var isMajor = px % 100 === 0;
1168
+ var isMid = px % 50 === 0;
1169
+ ctxH.beginPath();
1170
+ ctxH.moveTo(x + 0.5, 20);
1171
+ ctxH.lineTo(x + 0.5, isMajor ? 4 : isMid ? 10 : 14);
1172
+ ctxH.stroke();
1173
+ if (isMajor) {
1174
+ ctxH.fillText(String(px), x + 3, 12);
1175
+ }
1176
+ }
1177
+
1178
+ // Vertical ruler
1179
+ rulerVCanvas.width = 20 * dpr;
1180
+ rulerVCanvas.height = h * dpr;
1181
+ rulerVCanvas.style.width = "20px";
1182
+ rulerVCanvas.style.height = h + "px";
1183
+ var ctxV = rulerVCanvas.getContext("2d");
1184
+ ctxV.scale(dpr, dpr);
1185
+ ctxV.font = "9px ui-monospace,SFMono-Regular,Menlo,monospace";
1186
+ ctxV.fillStyle = textColor;
1187
+ ctxV.strokeStyle = tickColor;
1188
+ ctxV.lineWidth = 0.5;
1189
+ var startY = Math.floor(sy / 10) * 10;
1190
+ for (var py = startY; py < sy + h; py += 10) {
1191
+ var y = py - sy;
1192
+ var isMajorY = py % 100 === 0;
1193
+ var isMidY = py % 50 === 0;
1194
+ ctxV.beginPath();
1195
+ ctxV.moveTo(20, y + 0.5);
1196
+ ctxV.lineTo(isMajorY ? 4 : isMidY ? 10 : 14, y + 0.5);
1197
+ ctxV.stroke();
1198
+ if (isMajorY && y >= 10) {
1199
+ ctxV.save();
1200
+ ctxV.translate(12, y + 3);
1201
+ ctxV.rotate(-Math.PI / 2);
1202
+ ctxV.fillText(String(py), 0, 0);
1203
+ ctxV.restore();
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ function addGuide(dir, viewPos, pagePos) {
1209
+ var guide = el("div", { className: "ann-guide ann-guide-" + dir });
1210
+ guide.style[dir === "h" ? "top" : "left"] = viewPos + "px";
1211
+ guide.style.borderColor = state.markerColor;
1212
+ guide._pagePos = pagePos;
1213
+ guide._dir = dir;
1214
+
1215
+ // Handle: drag affordance + px label + close button
1216
+ var handle = el("div", { className: "ann-guide-handle" });
1217
+ var label = el("span", {}, String(Math.round(pagePos)) + "px");
1218
+
1219
+ var closeBtn = el("button", { className: "ann-guide-close", "aria-label": "Remove guide" });
1220
+ closeBtn.appendChild(iconEl("x"));
1221
+ closeBtn.addEventListener("click", function (e) {
1222
+ e.stopPropagation();
1223
+ removeGuide(guide);
1224
+ });
1225
+
1226
+ handle.appendChild(label);
1227
+ handle.appendChild(closeBtn);
1228
+ guide.appendChild(handle);
1229
+
1230
+ // Drag from handle or line to reposition; drag to ruler edge to remove
1231
+ function startDrag(e) {
1232
+ e.preventDefault();
1233
+ e.stopPropagation();
1234
+ handle.classList.add("dragging");
1235
+ function onMove(ev) {
1236
+ var pos, pp;
1237
+ if (dir === "h") {
1238
+ pos = ev.clientY;
1239
+ pp = pos + window.scrollY;
1240
+ guide.style.top = pos + "px";
1241
+ } else {
1242
+ pos = ev.clientX;
1243
+ pp = pos + window.scrollX;
1244
+ guide.style.left = pos + "px";
1245
+ }
1246
+ guide._pagePos = pp;
1247
+ label.textContent = Math.round(pp) + "px";
1248
+ var onRuler = dir === "h" ? ev.clientX < 20 : ev.clientY < 20;
1249
+ guide.style.opacity = onRuler ? "0.3" : "1";
1250
+ }
1251
+ function onUp(ev) {
1252
+ document.removeEventListener("mousemove", onMove);
1253
+ document.removeEventListener("mouseup", onUp);
1254
+ handle.classList.remove("dragging");
1255
+ var onRuler = dir === "h" ? ev.clientX < 20 : ev.clientY < 20;
1256
+ if (onRuler) { removeGuide(guide); return; }
1257
+ guide.style.opacity = "1";
1258
+ }
1259
+ document.addEventListener("mousemove", onMove);
1260
+ document.addEventListener("mouseup", onUp);
1261
+ }
1262
+
1263
+ handle.addEventListener("mousedown", startDrag);
1264
+ guide.addEventListener("mousedown", function (e) {
1265
+ if (e.target === guide || e.target === guide.querySelector("::before")) startDrag(e);
1266
+ });
1267
+
1268
+ document.body.appendChild(guide);
1269
+ guides.push(guide);
1270
+ }
1271
+
1272
+ function removeGuide(guide) {
1273
+ if (guide.parentNode) guide.parentNode.removeChild(guide);
1274
+ guides = guides.filter(function (g) { return g !== guide; });
1275
+ }
1276
+
1277
+ function updateGuidePositions() {
1278
+ guides.forEach(function (g) {
1279
+ if (g._dir === "h") {
1280
+ g.style.top = (g._pagePos - window.scrollY) + "px";
1281
+ } else {
1282
+ g.style.left = (g._pagePos - window.scrollX) + "px";
1283
+ }
1284
+ });
1285
+ }
1286
+
1287
+ function clearGuides() {
1288
+ guides.forEach(function (g) { if (g.parentNode) g.parentNode.removeChild(g); });
1289
+ guides = [];
1290
+ }
1291
+
1292
+ // ═══════════════════════════════════════════
1293
+ // Ruler edge-hover reveal
1294
+ // ═══════════════════════════════════════════
1295
+
1296
+ var RULER_EDGE_PX = 40;
1297
+ var rulerTimerH = null, rulerTimerV = null;
1298
+
1299
+ function showRulerAxis(axis) {
1300
+ if (state.markersHidden) return;
1301
+ if (axis === "h") {
1302
+ if (rulerTimerH) { clearTimeout(rulerTimerH); rulerTimerH = null; }
1303
+ if (rulerH) rulerH.classList.add("ann-ruler-show");
1304
+ } else {
1305
+ if (rulerTimerV) { clearTimeout(rulerTimerV); rulerTimerV = null; }
1306
+ if (rulerV) rulerV.classList.add("ann-ruler-show");
1307
+ }
1308
+ if (rulerCorner) rulerCorner.classList.add("ann-ruler-show");
1309
+ }
1310
+
1311
+ function hideRulerAxis(axis) {
1312
+ if (state.rulerDragging) return;
1313
+ if (axis === "h") {
1314
+ if (rulerTimerH) return;
1315
+ rulerTimerH = setTimeout(function () {
1316
+ rulerTimerH = null;
1317
+ if (rulerH) rulerH.classList.remove("ann-ruler-show");
1318
+ if (rulerCorner && rulerV && !rulerV.classList.contains("ann-ruler-show")) {
1319
+ rulerCorner.classList.remove("ann-ruler-show");
1320
+ }
1321
+ }, 300);
1322
+ } else {
1323
+ if (rulerTimerV) return;
1324
+ rulerTimerV = setTimeout(function () {
1325
+ rulerTimerV = null;
1326
+ if (rulerV) rulerV.classList.remove("ann-ruler-show");
1327
+ if (rulerCorner && rulerH && !rulerH.classList.contains("ann-ruler-show")) {
1328
+ rulerCorner.classList.remove("ann-ruler-show");
1329
+ }
1330
+ }, 300);
1331
+ }
1332
+ }
1333
+
1334
+ function handleRulerProximity(e) {
1335
+ if (!rulerH) return;
1336
+ var x = e.clientX, y = e.clientY;
1337
+ if (y <= RULER_EDGE_PX) showRulerAxis("h");
1338
+ else hideRulerAxis("h");
1339
+ if (x <= RULER_EDGE_PX) showRulerAxis("v");
1340
+ else hideRulerAxis("v");
1341
+ }
1342
+
1343
+ function toggleActive() {
1344
+ state.active = !state.active;
1345
+ var btn = document.getElementById("ann-toggle-btn");
1346
+ if (state.active) {
1347
+ btn.classList.add("active");
1348
+ document.body.classList.add("ann-active");
1349
+ } else {
1350
+ btn.classList.remove("active");
1351
+ document.body.classList.remove("ann-active");
1352
+ hideHover();
1353
+ closePopup();
1354
+ }
1355
+ }
1356
+
1357
+ function hideHover() {
1358
+ hoverHighlight.style.display = "none";
1359
+ hoverLabel.style.display = "none";
1360
+ state.hoveredEl = null;
1361
+ }
1362
+
1363
+ function hideToolbar() {
1364
+ if (!toolbar) return;
1365
+ if (state.active) toggleActive();
1366
+ closePopup();
1367
+ hideHover();
1368
+ toolbar.classList.add("collapsed");
1369
+ state.toolbarHidden = true;
1370
+ }
1371
+
1372
+ function showToolbar() {
1373
+ if (!toolbar) return;
1374
+ toolbar.classList.remove("collapsed");
1375
+ state.toolbarHidden = false;
1376
+ }
1377
+
1378
+ function toggleMarkersVisibility() {
1379
+ state.markersHidden = !state.markersHidden;
1380
+ markersContainer.style.display = state.markersHidden ? "none" : "";
1381
+ eyeIconWrap.innerHTML = "";
1382
+ eyeIconWrap.appendChild(iconEl(state.markersHidden ? "eyeOff" : "eye"));
1383
+ if (state.markersHidden) eyeBtn.classList.add("hidden-state");
1384
+ else eyeBtn.classList.remove("hidden-state");
1385
+ // Toggle rulers and guides visibility
1386
+ if (state.markersHidden) {
1387
+ if (rulerH) rulerH.classList.remove("ann-ruler-show");
1388
+ if (rulerV) rulerV.classList.remove("ann-ruler-show");
1389
+ if (rulerCorner) rulerCorner.classList.remove("ann-ruler-show");
1390
+ }
1391
+ guides.forEach(function (g) {
1392
+ g.style.display = state.markersHidden ? "none" : "";
1393
+ });
1394
+ }
1395
+
1396
+ function updateCount() {
1397
+ if (!eyeBadge) return;
1398
+ var n = state.annotations.length;
1399
+ var prev = parseInt(eyeBadge.textContent, 10) || 0;
1400
+
1401
+ eyeBadge.textContent = n;
1402
+ if (n === 0) eyeBadge.classList.add("empty");
1403
+ else eyeBadge.classList.remove("empty");
1404
+
1405
+ if (collapsedBadge) {
1406
+ collapsedBadge.textContent = n;
1407
+ if (n === 0) collapsedBadge.classList.add("empty");
1408
+ else collapsedBadge.classList.remove("empty");
1409
+ }
1410
+
1411
+ // Pop on increment
1412
+ if (n > prev) {
1413
+ eyeBadge.classList.remove("bump");
1414
+ void eyeBadge.offsetWidth;
1415
+ eyeBadge.classList.add("bump");
1416
+ if (collapsedBadge) {
1417
+ collapsedBadge.classList.remove("bump");
1418
+ void collapsedBadge.offsetWidth;
1419
+ collapsedBadge.classList.add("bump");
1420
+ }
1421
+ }
1422
+ }
1423
+
1424
+ function showToast(msg) {
1425
+ // Replace text node (preserve the icon SVG child)
1426
+ var tn = null;
1427
+ for (var i = 0; i < toast.childNodes.length; i++) {
1428
+ if (toast.childNodes[i].nodeType === 3) { tn = toast.childNodes[i]; break; }
1429
+ }
1430
+ if (tn) tn.nodeValue = msg || "Copied to clipboard";
1431
+ else toast.appendChild(document.createTextNode(msg || "Copied to clipboard"));
1432
+ toast.classList.add("show");
1433
+ setTimeout(function () { toast.classList.remove("show"); }, 1800);
1434
+ }
1435
+
1436
+ function flashCopySuccess() {
1437
+ if (!copyBtn) return;
1438
+ copyBtn.innerHTML = ICONS.checkCircleFilled;
1439
+ copyBtn.classList.add("success");
1440
+ setTimeout(function () {
1441
+ copyBtn.innerHTML = "";
1442
+ copyBtn.appendChild(iconEl("copy"));
1443
+ copyBtn.classList.remove("success");
1444
+ }, 1500);
1445
+ }
1446
+
1447
+ function copyOutput() {
1448
+ var md = generateOutput();
1449
+ if (!md) { showToast("No annotations"); return; }
1450
+ navigator.clipboard.writeText(md).then(function () {
1451
+ flashCopySuccess();
1452
+ showToast("Copied " + state.annotations.length + " annotation(s)");
1453
+ });
1454
+ }
1455
+
1456
+ function clearAll() {
1457
+ state.annotations = [];
1458
+ updateCount();
1459
+ renderMarkers();
1460
+ }
1461
+
1462
+ // ═══════════════════════════════════════════
1463
+ // Color picker
1464
+ // ═══════════════════════════════════════════
1465
+
1466
+ function closeColorPicker() {
1467
+ if (colorPicker && colorPicker.parentNode) colorPicker.parentNode.removeChild(colorPicker);
1468
+ colorPicker = null;
1469
+ }
1470
+
1471
+ function showColorPicker() {
1472
+ if (colorPicker) { closeColorPicker(); return; }
1473
+ colorPicker = el("div", { id: "ann-color-picker" });
1474
+
1475
+ MARKER_COLORS.forEach(function (hex) {
1476
+ var opt = el("div", { className: "ann-color-opt" + (hex === state.markerColor ? " active" : "") });
1477
+ opt.style.background = hex;
1478
+ opt.addEventListener("click", function (e) {
1479
+ e.stopPropagation();
1480
+ updateAccentColor(hex);
1481
+ closeColorPicker();
1482
+ });
1483
+ colorPicker.appendChild(opt);
1484
+ });
1485
+
1486
+ // Position above the swatch
1487
+ var swRect = colorSwatch.getBoundingClientRect();
1488
+ colorPicker.style.bottom = (window.innerHeight - swRect.top + 8) + "px";
1489
+ colorPicker.style.right = (window.innerWidth - swRect.right) + "px";
1490
+
1491
+ document.body.appendChild(colorPicker);
1492
+ }
1493
+
1494
+ // ═══════════════════════════════════════════
1495
+ // Popup (comment input)
1496
+ // ═══════════════════════════════════════════
1497
+
1498
+ function closePopup() {
1499
+ if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
1500
+ popup = null;
1501
+ closeColorPicker();
1502
+ }
1503
+
1504
+ function getStyleEntries(target) {
1505
+ if (!target) return [];
1506
+ var s = window.getComputedStyle(target);
1507
+ var entries = [];
1508
+ var color = s.color;
1509
+ if (color && color !== "rgb(0, 0, 0)") entries.push(["color", color]);
1510
+ var bg = s.backgroundColor;
1511
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") entries.push(["background", bg]);
1512
+ if (s.fontSize) entries.push(["font-size", s.fontSize]);
1513
+ if (s.fontWeight && s.fontWeight !== "400" && s.fontWeight !== "normal") entries.push(["font-weight", s.fontWeight]);
1514
+ if (s.fontFamily) entries.push(["font-family", s.fontFamily]);
1515
+ var d = s.display;
1516
+ if (d && d !== "block" && d !== "inline") entries.push(["display", d]);
1517
+ if (s.borderRadius && s.borderRadius !== "0px") entries.push(["border-radius", s.borderRadius]);
1518
+ if (s.padding && s.padding !== "0px") entries.push(["padding", s.padding]);
1519
+ return entries;
1520
+ }
1521
+
1522
+ function buildPopupHeader(elementName, target) {
1523
+ var header = el("div", { className: "ann-popup-header" });
1524
+ var chevWrap = el("span", { className: "ann-popup-chevron" });
1525
+ chevWrap.appendChild(iconEl("chevronDown"));
1526
+ var label = el("span", { className: "ann-popup-label" }, elementName);
1527
+ header.appendChild(chevWrap);
1528
+ header.appendChild(label);
1529
+ header.addEventListener("click", function () {
1530
+ header.classList.toggle("expanded");
1531
+ });
1532
+
1533
+ var stylesBlock = el("div", { className: "ann-popup-styles" });
1534
+ var entries = target ? (target.nodeType ? getStyleEntries(target) : target) : [];
1535
+ entries.forEach(function (pair) {
1536
+ var line = document.createElement("div");
1537
+ line.innerHTML = '<span class="ann-prop">' + escHtml(pair[0]) + ':</span> ' +
1538
+ '<span class="ann-val">' + escHtml(pair[1]) + ';</span>';
1539
+ stylesBlock.appendChild(line);
1540
+ });
1541
+
1542
+ return { header: header, styles: stylesBlock };
1543
+ }
1544
+
1545
+ function positionPopup(x, y) {
1546
+ popup.style.left = Math.min(x + 12, window.innerWidth - 400) + "px";
1547
+ popup.style.top = Math.min(y + 12, window.innerHeight - 220) + "px";
1548
+ }
1549
+
1550
+ function showPopup(clickX, clickY, info, rect, selectedText) {
1551
+ closePopup();
1552
+
1553
+ var bb = rect ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null;
1554
+
1555
+ popup = el("div", { id: "ann-popup" });
1556
+
1557
+ var hp = buildPopupHeader(info.name, info.target);
1558
+ var textarea = el("textarea", { placeholder: "What should change?", rows: "3" });
1559
+
1560
+ var cancelBtn = el("button", { onClick: function () { closePopup(); } }, "Cancel");
1561
+ var addBtn = el("button", { className: "primary", onClick: function () {
1562
+ var comment = textarea.value.trim();
1563
+ if (!comment) { closePopup(); return; }
1564
+
1565
+ var annotation = {
1566
+ id: "ann-" + (++state.counter),
1567
+ x: clickX + window.scrollX,
1568
+ y: clickY + window.scrollY,
1569
+ comment: comment,
1570
+ element: info.name,
1571
+ elementPath: info.path,
1572
+ timestamp: Date.now(),
1573
+ selectedText: selectedText || undefined,
1574
+ boundingBox: bb,
1575
+ nearbyText: getNearbyText(info.target),
1576
+ cssClasses: getClasses(info.target),
1577
+ fullPath: getFullPath(info.target),
1578
+ accessibility: getA11yInfo(info.target),
1579
+ computedStyles: getComputedSnapshot(info.target),
1580
+ };
1581
+
1582
+ state.annotations.push(annotation);
1583
+ updateCount();
1584
+ renderMarkers();
1585
+ closePopup();
1586
+ }}, "Add");
1587
+
1588
+ var actions = el("div", { className: "ann-popup-actions" }, [
1589
+ el("span", { className: "ann-spacer" }),
1590
+ cancelBtn,
1591
+ addBtn,
1592
+ ]);
1593
+
1594
+ popup.appendChild(hp.header);
1595
+ popup.appendChild(hp.styles);
1596
+ popup.appendChild(textarea);
1597
+ popup.appendChild(actions);
1598
+
1599
+ positionPopup(clickX, clickY);
1600
+ document.body.appendChild(popup);
1601
+ textarea.focus();
1602
+
1603
+ textarea.addEventListener("keydown", function (e) {
1604
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) addBtn.click();
1605
+ if (e.key === "Escape") closePopup();
1606
+ });
1607
+ }
1608
+
1609
+ function showEditPopup(x, y, index) {
1610
+ closePopup();
1611
+ var ann = state.annotations[index];
1612
+ if (!ann) return;
1613
+
1614
+ popup = el("div", { id: "ann-popup" });
1615
+
1616
+ // Parse stored styles string back into pairs for display
1617
+ var pairs = [];
1618
+ if (ann.computedStyles) {
1619
+ ann.computedStyles.replace(/([a-z]+): ([^,]+(?:,(?![a-z]+:)[^,]*)*)/g, function (_, k, v) {
1620
+ pairs.push([k, v.trim()]);
1621
+ });
1622
+ }
1623
+ var hp = buildPopupHeader(ann.element, pairs);
1624
+ var textarea = el("textarea", { placeholder: "What should change?", rows: "3" });
1625
+ textarea.value = ann.comment;
1626
+
1627
+ var trashBtn = el("button", {
1628
+ className: "icon-only",
1629
+ title: "Delete annotation",
1630
+ "aria-label": "Delete annotation",
1631
+ onClick: function () {
1632
+ state.annotations.splice(index, 1);
1633
+ updateCount();
1634
+ renderMarkers();
1635
+ closePopup();
1636
+ }
1637
+ });
1638
+ trashBtn.appendChild(iconEl("trash"));
1639
+
1640
+ var cancelBtn = el("button", { onClick: function () { closePopup(); } }, "Cancel");
1641
+ var saveBtn = el("button", { className: "primary", onClick: function () {
1642
+ var comment = textarea.value.trim();
1643
+ if (!comment) {
1644
+ // Empty save = delete
1645
+ state.annotations.splice(index, 1);
1646
+ } else {
1647
+ ann.comment = comment;
1648
+ }
1649
+ updateCount();
1650
+ renderMarkers();
1651
+ closePopup();
1652
+ }}, "Save");
1653
+
1654
+ var actions = el("div", { className: "ann-popup-actions" }, [
1655
+ trashBtn,
1656
+ el("span", { className: "ann-spacer" }),
1657
+ cancelBtn,
1658
+ saveBtn,
1659
+ ]);
1660
+
1661
+ popup.appendChild(hp.header);
1662
+ popup.appendChild(hp.styles);
1663
+ popup.appendChild(textarea);
1664
+ popup.appendChild(actions);
1665
+
1666
+ positionPopup(x, y);
1667
+ document.body.appendChild(popup);
1668
+ textarea.focus();
1669
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
1670
+
1671
+ textarea.addEventListener("keydown", function (e) {
1672
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) saveBtn.click();
1673
+ if (e.key === "Escape") closePopup();
1674
+ });
1675
+ }
1676
+
1677
+ // ═══════════════════════════════════════════
1678
+ // Markers
1679
+ // ═══════════════════════════════════════════
1680
+
1681
+ function renderMarkers() {
1682
+ markersContainer.innerHTML = "";
1683
+ state.annotations.forEach(function (a, i) {
1684
+ var marker = el("div", { className: "ann-marker" });
1685
+ marker.style.left = a.x + "px";
1686
+ marker.style.top = a.y + "px";
1687
+
1688
+ // Inner visible circle — this scales on hover, the wrapper does not
1689
+ var circle = el("div", { className: "ann-marker-circle" });
1690
+ circle.style.background = state.markerColor;
1691
+ circle.style.boxShadow = "0 1px 2px rgba(31,30,29,.10),0 4px 12px " +
1692
+ state.markerColor + "4D"; // 30% opacity hex suffix
1693
+ var numEl = el("span", { className: "ann-marker-num" }, String(i + 1));
1694
+ var editEl = el("span", { className: "ann-marker-edit" });
1695
+ editEl.appendChild(iconEl("pencilPlain"));
1696
+ circle.appendChild(numEl);
1697
+ circle.appendChild(editEl);
1698
+ marker.appendChild(circle);
1699
+
1700
+ // Tooltip — sibling of circle so it doesn't scale with it
1701
+ var tip = el("div", { className: "ann-marker-tip" });
1702
+ tip.innerHTML = '<span class="ann-tip-element">' + escHtml(a.element) + '</span>' +
1703
+ '<span class="ann-tip-comment">' + escHtml(a.comment) + '</span>';
1704
+ marker.appendChild(tip);
1705
+
1706
+ // Click to edit
1707
+ marker.addEventListener("click", function (e) {
1708
+ e.stopPropagation();
1709
+ e.preventDefault();
1710
+ showEditPopup(e.clientX, e.clientY, i);
1711
+ });
1712
+
1713
+ markersContainer.appendChild(marker);
1714
+ });
1715
+ }
1716
+
1717
+ function escHtml(s) {
1718
+ var d = document.createElement("div");
1719
+ d.textContent = s;
1720
+ return d.innerHTML;
1721
+ }
1722
+
1723
+ // ═══════════════════════════════════════════
1724
+ // Event Handlers
1725
+ // ═══════════════════════════════════════════
1726
+
1727
+ function isToolbarElement(el) {
1728
+ if (!el) return false;
1729
+ var node = el;
1730
+ while (node) {
1731
+ if (node.id === "ann-toolbar" || node.id === "ann-popup" ||
1732
+ node.id === "ann-color-picker" || node.id === "ann-tip" ||
1733
+ node.id === "ann-ruler-h" || node.id === "ann-ruler-v" ||
1734
+ node.id === "ann-ruler-corner" ||
1735
+ (node.className && typeof node.className === "string" && node.className.indexOf("ann-guide") !== -1) ||
1736
+ node.id === "ann-hover-highlight" || node.id === "ann-hover-label" ||
1737
+ (node.className && typeof node.className === "string" && node.className.indexOf("ann-marker") !== -1)) {
1738
+ return true;
1739
+ }
1740
+ node = node.parentElement;
1741
+ }
1742
+ return false;
1743
+ }
1744
+
1745
+ function handleMouseMove(e) {
1746
+ if (!state.active) return;
1747
+ if (isToolbarElement(e.target)) { hideHover(); return; }
1748
+
1749
+ var target = document.elementFromPoint(e.clientX, e.clientY);
1750
+ if (!target || isToolbarElement(target)) { hideHover(); return; }
1751
+
1752
+ state.hoveredEl = target;
1753
+ var rect = target.getBoundingClientRect();
1754
+
1755
+ // Highlight box
1756
+ hoverHighlight.style.display = "block";
1757
+ hoverHighlight.style.left = rect.left + "px";
1758
+ hoverHighlight.style.top = rect.top + "px";
1759
+ hoverHighlight.style.width = rect.width + "px";
1760
+ hoverHighlight.style.height = rect.height + "px";
1761
+ hoverHighlight.style.borderColor = state.markerColor;
1762
+ hoverHighlight.style.background = state.markerColor + "0A";
1763
+
1764
+ // Label
1765
+ var info = identifyElement(target);
1766
+ hoverLabel.style.display = "block";
1767
+ hoverLabel.textContent = info.name + " " + info.path;
1768
+
1769
+ // Position label above element or below if too high
1770
+ var labelTop = rect.top - 28;
1771
+ if (labelTop < 4) labelTop = rect.bottom + 6;
1772
+ hoverLabel.style.left = Math.max(4, rect.left) + "px";
1773
+ hoverLabel.style.top = labelTop + "px";
1774
+ }
1775
+
1776
+ function handleClick(e) {
1777
+ if (!state.active) return;
1778
+ if (isToolbarElement(e.target)) return;
1779
+
1780
+ e.preventDefault();
1781
+ e.stopPropagation();
1782
+
1783
+ var target = document.elementFromPoint(e.clientX, e.clientY);
1784
+ if (!target || isToolbarElement(target)) return;
1785
+
1786
+ var info = identifyElement(target);
1787
+ info.target = target;
1788
+ var rect = target.getBoundingClientRect();
1789
+
1790
+ // Check for text selection
1791
+ var sel = window.getSelection();
1792
+ var selectedText = sel && sel.toString().trim() ? sel.toString().trim() : null;
1793
+
1794
+ showPopup(e.clientX, e.clientY, info, rect, selectedText);
1795
+ }
1796
+
1797
+ function handleKeydown(e) {
1798
+ // Alt+A: bring back hidden toolbar OR toggle annotate mode
1799
+ if (e.altKey && (e.code === "KeyA" || (e.key && e.key.toLowerCase() === "a"))) {
1800
+ e.preventDefault();
1801
+ if (state.toolbarHidden) {
1802
+ showToolbar();
1803
+ if (!state.active) toggleActive();
1804
+ } else {
1805
+ toggleActive();
1806
+ }
1807
+ }
1808
+ // Single-key shortcuts (only when no popup/input is focused)
1809
+ if (!e.metaKey && !e.ctrlKey && !e.altKey && !popup && !colorPicker &&
1810
+ document.activeElement && document.activeElement.tagName !== "TEXTAREA" &&
1811
+ document.activeElement.tagName !== "INPUT") {
1812
+ var k = e.key.toLowerCase();
1813
+ if (k === "c" && state.annotations.length > 0) { e.preventDefault(); copyOutput(); }
1814
+ if (k === "m") { e.preventDefault(); showColorPicker(); }
1815
+ if (k === "h") { e.preventDefault(); toggleMarkersVisibility(); }
1816
+ }
1817
+ // Escape to deactivate or close popup
1818
+ if (e.key === "Escape") {
1819
+ if (colorPicker) { closeColorPicker(); return; }
1820
+ if (popup) { closePopup(); return; }
1821
+ if (state.active) toggleActive();
1822
+ }
1823
+ }
1824
+
1825
+ // ═══════════════════════════════════════════
1826
+ // Init
1827
+ // ═══════════════════════════════════════════
1828
+
1829
+ function init() {
1830
+ createToolbar();
1831
+ document.addEventListener("mousemove", handleMouseMove, true);
1832
+ document.addEventListener("click", handleClick, true);
1833
+ document.addEventListener("keydown", handleKeydown);
1834
+ window.addEventListener("scroll", function () {
1835
+ if (state.active) hideHover();
1836
+ drawRulers();
1837
+ updateGuidePositions();
1838
+ }, { passive: true });
1839
+ window.addEventListener("resize", function () {
1840
+ drawRulers();
1841
+ clampToolbar();
1842
+ }, { passive: true });
1843
+
1844
+ // Edge-hover reveal for rulers
1845
+ document.addEventListener("mousemove", handleRulerProximity, { passive: true });
1846
+ document.documentElement.addEventListener("mouseleave", function () {
1847
+ hideRulerAxis("h");
1848
+ hideRulerAxis("v");
1849
+ });
1850
+
1851
+ // Publish a tiny control hook so re-injection (Chrome extension click,
1852
+ // second <script> tag, etc.) toggles this instance instead of duplicating.
1853
+ window.__annotateHtml = {
1854
+ version: 1,
1855
+ toggle: function () {
1856
+ if (state.toolbarHidden) showToolbar();
1857
+ else hideToolbar();
1858
+ },
1859
+ show: showToolbar,
1860
+ hide: hideToolbar,
1861
+ // Programmatic API for seeding annotations and guides onto the page.
1862
+ seed: function (annotations, seedGuides) {
1863
+ if (annotations) {
1864
+ annotations.forEach(function (a) {
1865
+ var target = typeof a.target === "string"
1866
+ ? document.querySelector(a.target) : a.target;
1867
+ if (!target) return;
1868
+ var rect = target.getBoundingClientRect();
1869
+ var info = identifyElement(target, getElementPath(target));
1870
+ var bb = "x:" + Math.round(rect.x) + ", y:" + Math.round(rect.y) +
1871
+ " (" + Math.round(rect.width) + "x" + Math.round(rect.height) + "px)";
1872
+ state.annotations.push({
1873
+ id: "ann-" + (++state.counter),
1874
+ x: rect.x + rect.width / 2 + window.scrollX,
1875
+ y: rect.y + window.scrollY,
1876
+ comment: a.comment || "",
1877
+ element: info.name,
1878
+ elementPath: info.path,
1879
+ timestamp: Date.now(),
1880
+ boundingBox: bb,
1881
+ nearbyText: getNearbyText(target),
1882
+ cssClasses: getClasses(target),
1883
+ fullPath: getFullPath(target),
1884
+ accessibility: getA11yInfo(target),
1885
+ computedStyles: getComputedSnapshot(target),
1886
+ });
1887
+ });
1888
+ updateCount();
1889
+ renderMarkers();
1890
+ }
1891
+ if (seedGuides) {
1892
+ seedGuides.forEach(function (g) {
1893
+ var viewPos = g.dir === "h"
1894
+ ? g.pos - window.scrollY
1895
+ : g.pos - window.scrollX;
1896
+ addGuide(g.dir, viewPos, g.pos);
1897
+ });
1898
+ }
1899
+ }
1900
+ };
1901
+ }
1902
+
1903
+ // Boot when DOM ready
1904
+ if (document.readyState === "loading") {
1905
+ document.addEventListener("DOMContentLoaded", init);
1906
+ } else {
1907
+ init();
1908
+ }
1909
+
1910
+ })();