opencode-plannotator-tui 0.3.0 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +83 -11
- package/bridge/native-host.mjs +29 -0
- package/bridge/receiver.mjs +77 -0
- package/bridge/wire.mjs +38 -0
- package/browser/UPSTREAM.md +9 -0
- package/browser/capture.js +96 -0
- package/browser/icons/icon-128.png +0 -0
- package/browser/icons/icon-16.png +0 -0
- package/browser/icons/icon-32.png +0 -0
- package/browser/icons/icon-48.png +0 -0
- package/browser/icons/icon.svg +3 -0
- package/browser/inspector.js +125 -0
- package/browser/manifest.json +13 -0
- package/browser/panel.css +3 -0
- package/browser/panel.html +23 -0
- package/browser/panel.mjs +155 -0
- package/browser/protocol.mjs +45 -0
- package/browser/upstream/LICENSE +21 -0
- package/browser/upstream/annotate-1.0.0.js +1910 -0
- package/browser/worker.mjs +105 -0
- package/package.json +15 -3
- package/review.mjs +11 -4
- package/scripts/install-bridge.mjs +27 -0
- package/scripts/install-plugin.mjs +34 -0
- package/scripts/migrate-page-context.mjs +31 -0
- package/targets.mjs +32 -0
- package/tui.ts +7 -7
- package/web-review.mjs +36 -0
|
@@ -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
|
+
})();
|