@wasao/kagemusha 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +497 -58
  2. package/dist/commands/add.d.ts +6 -0
  3. package/dist/commands/add.d.ts.map +1 -0
  4. package/dist/commands/add.js +26 -0
  5. package/dist/commands/add.js.map +1 -0
  6. package/dist/commands/capture.d.ts +4 -2
  7. package/dist/commands/capture.d.ts.map +1 -1
  8. package/dist/commands/capture.js +236 -11
  9. package/dist/commands/capture.js.map +1 -1
  10. package/dist/commands/discover.d.ts +2 -0
  11. package/dist/commands/discover.d.ts.map +1 -0
  12. package/dist/commands/discover.js +62 -0
  13. package/dist/commands/discover.js.map +1 -0
  14. package/dist/commands/edit.d.ts +6 -0
  15. package/dist/commands/edit.d.ts.map +1 -0
  16. package/dist/commands/edit.js +120 -0
  17. package/dist/commands/edit.js.map +1 -0
  18. package/dist/commands/init.d.ts +1 -1
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js +220 -118
  21. package/dist/commands/init.js.map +1 -1
  22. package/dist/commands/list.d.ts +2 -0
  23. package/dist/commands/list.d.ts.map +1 -0
  24. package/dist/commands/list.js +33 -0
  25. package/dist/commands/list.js.map +1 -0
  26. package/dist/commands/login.d.ts +6 -0
  27. package/dist/commands/login.d.ts.map +1 -0
  28. package/dist/commands/login.js +142 -0
  29. package/dist/commands/login.js.map +1 -0
  30. package/dist/commands/validate.js +1 -1
  31. package/dist/commands/validate.js.map +1 -1
  32. package/dist/editor/inject-script.d.ts +2 -0
  33. package/dist/editor/inject-script.d.ts.map +1 -0
  34. package/dist/editor/inject-script.js +768 -0
  35. package/dist/editor/inject-script.js.map +1 -0
  36. package/dist/index.js +32 -20
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/annotate.d.ts +2 -2
  39. package/dist/lib/annotate.d.ts.map +1 -1
  40. package/dist/lib/annotate.js +36 -44
  41. package/dist/lib/annotate.js.map +1 -1
  42. package/dist/lib/auth.d.ts +18 -0
  43. package/dist/lib/auth.d.ts.map +1 -0
  44. package/dist/lib/auth.js +45 -0
  45. package/dist/lib/auth.js.map +1 -0
  46. package/dist/lib/aws-error.d.ts +7 -0
  47. package/dist/lib/aws-error.d.ts.map +1 -0
  48. package/dist/lib/aws-error.js +74 -0
  49. package/dist/lib/aws-error.js.map +1 -0
  50. package/dist/lib/canonical.d.ts +22 -0
  51. package/dist/lib/canonical.d.ts.map +1 -0
  52. package/dist/lib/canonical.js +92 -0
  53. package/dist/lib/canonical.js.map +1 -0
  54. package/dist/lib/config.d.ts +2 -0
  55. package/dist/lib/config.d.ts.map +1 -1
  56. package/dist/lib/config.js +23 -13
  57. package/dist/lib/config.js.map +1 -1
  58. package/dist/lib/crawl.d.ts +1 -1
  59. package/dist/lib/crawl.d.ts.map +1 -1
  60. package/dist/lib/crawl.js +213 -20
  61. package/dist/lib/crawl.js.map +1 -1
  62. package/dist/lib/definition.d.ts +2 -0
  63. package/dist/lib/definition.d.ts.map +1 -0
  64. package/dist/lib/definition.js +6 -0
  65. package/dist/lib/definition.js.map +1 -0
  66. package/dist/lib/diff.d.ts +52 -0
  67. package/dist/lib/diff.d.ts.map +1 -0
  68. package/dist/lib/diff.js +41 -0
  69. package/dist/lib/diff.js.map +1 -0
  70. package/dist/lib/login-error.d.ts +10 -0
  71. package/dist/lib/login-error.d.ts.map +1 -0
  72. package/dist/lib/login-error.js +13 -0
  73. package/dist/lib/login-error.js.map +1 -0
  74. package/dist/lib/screenshot.d.ts +8 -2
  75. package/dist/lib/screenshot.d.ts.map +1 -1
  76. package/dist/lib/screenshot.js +44 -61
  77. package/dist/lib/screenshot.js.map +1 -1
  78. package/dist/lib/staging.d.ts +8 -0
  79. package/dist/lib/staging.d.ts.map +1 -0
  80. package/dist/lib/staging.js +24 -0
  81. package/dist/lib/staging.js.map +1 -0
  82. package/dist/types.d.ts +5 -23
  83. package/dist/types.d.ts.map +1 -1
  84. package/package.json +18 -11
  85. package/dist/commands/preview.d.ts +0 -6
  86. package/dist/commands/preview.d.ts.map +0 -1
  87. package/dist/commands/preview.js +0 -33
  88. package/dist/commands/preview.js.map +0 -1
  89. package/dist/commands/run.d.ts +0 -6
  90. package/dist/commands/run.d.ts.map +0 -1
  91. package/dist/commands/run.js +0 -39
  92. package/dist/commands/run.js.map +0 -1
  93. package/dist/lib/upload.d.ts +0 -9
  94. package/dist/lib/upload.d.ts.map +0 -1
  95. package/dist/lib/upload.js +0 -43
  96. package/dist/lib/upload.js.map +0 -1
@@ -0,0 +1,768 @@
1
+ // This script is injected into the target page to provide annotation editing.
2
+ // It adds a toolbar and SVG overlay layer on top of the actual page.
3
+ // This is a plain script (not a module) — no imports/exports.
4
+ // Type helpers for window properties set by edit.ts
5
+ const _win = window;
6
+ const TOOLBAR_HEIGHT_FALLBACK = 48;
7
+ let toolbarHeight = TOOLBAR_HEIGHT_FALLBACK;
8
+ const svgNS = "http://www.w3.org/2000/svg";
9
+ let tool = "rect";
10
+ let annotations = [];
11
+ let selectedId = null;
12
+ let dragState = null;
13
+ let nextId = 1;
14
+ // Capture state — persisted on save (in page CSS pixels, NOT DPR-scaled)
15
+ let captureMode = "fullPage";
16
+ let captureCrop = null;
17
+ let cropDragState = null;
18
+ const HANDLE_SIZE = 10;
19
+ const MIN_CROP = 10;
20
+ // --- TOOLBAR ---
21
+ const toolbar = document.createElement("div");
22
+ toolbar.id = "kagemusha-toolbar";
23
+ toolbar.innerHTML = `
24
+ <style>
25
+ #kagemusha-toolbar {
26
+ position: fixed; top: 0; left: 0; right: 0; z-index: 999999;
27
+ background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 10px;
28
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3); font-family: -apple-system, sans-serif;
29
+ flex-wrap: nowrap; overflow-x: auto;
30
+ }
31
+ #kagemusha-toolbar button {
32
+ padding: 6px 12px; border: 1px solid #444; border-radius: 6px;
33
+ background: #1a1a2e; color: #fff; font-size: 13px; cursor: pointer;
34
+ }
35
+ #kagemusha-toolbar button:hover { background: #2a2a4e; }
36
+ #kagemusha-toolbar button.active { background: #6366f1; border-color: #6366f1; }
37
+ #kagemusha-toolbar button.cap-btn.active { background: #0ea5e9; border-color: #0ea5e9; }
38
+ #kagemusha-toolbar .sep { width: 1px; height: 24px; background: #444; }
39
+ #kagemusha-toolbar .title { color: #888; font-size: 13px; }
40
+ #kagemusha-toolbar .group-label { color: #7a89b0; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; margin-right: -4px; }
41
+ #kagemusha-toolbar .save-btn { background: #22c55e; border-color: #22c55e; font-weight: 600; margin-left: auto; }
42
+ #kagemusha-toolbar .save-btn:hover { background: #16a34a; }
43
+ #kagemusha-svg-layer {
44
+ position: absolute; top: 0; left: 0; width: 100%;
45
+ z-index: 999998; pointer-events: none;
46
+ }
47
+ #kagemusha-svg-layer.drawing { pointer-events: auto; cursor: crosshair; }
48
+ #kagemusha-svg-layer.cropping { pointer-events: auto; cursor: crosshair; }
49
+ #kagemusha-svg-layer .annotation { pointer-events: auto; cursor: move; }
50
+ #kagemusha-svg-layer.cropping .annotation { pointer-events: none; }
51
+ #kagemusha-svg-layer .annotation.selected { filter: drop-shadow(0 0 3px #6366f1); }
52
+ #kagemusha-svg-layer .capture-crop-box { fill: rgba(14,165,233,0.10); stroke: #0ea5e9; stroke-width: 2; stroke-dasharray: 8 4; pointer-events: none; }
53
+ #kagemusha-svg-layer.cropping .capture-crop-box { pointer-events: auto; cursor: move; }
54
+ #kagemusha-svg-layer .crop-handle { fill: #fff; stroke: #0ea5e9; stroke-width: 2; pointer-events: none; }
55
+ #kagemusha-svg-layer.cropping .crop-handle { pointer-events: auto; }
56
+ #kagemusha-svg-layer .crop-handle.nw, #kagemusha-svg-layer .crop-handle.se { cursor: nwse-resize; }
57
+ #kagemusha-svg-layer .crop-handle.ne, #kagemusha-svg-layer .crop-handle.sw { cursor: nesw-resize; }
58
+ #kagemusha-svg-layer .crop-handle.n, #kagemusha-svg-layer .crop-handle.s { cursor: ns-resize; }
59
+ #kagemusha-svg-layer .crop-handle.e, #kagemusha-svg-layer .crop-handle.w { cursor: ew-resize; }
60
+ .kagemusha-hint {
61
+ position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
62
+ color: #fff; background: rgba(0,0,0,0.7); padding: 6px 16px; border-radius: 8px;
63
+ font-size: 12px; z-index: 999999; font-family: -apple-system, sans-serif;
64
+ }
65
+ </style>
66
+ <span class="title">🥷</span>
67
+ <span class="group-label">Capture</span>
68
+ <button id="kg-cap-full" class="cap-btn active">📷 Full</button>
69
+ <button id="kg-cap-crop" class="cap-btn">✂️ Crop</button>
70
+ <div class="sep"></div>
71
+ <span class="group-label">Annotate</span>
72
+ <button id="kg-tool-rect" class="active">▭ Rect</button>
73
+ <button id="kg-tool-arrow">→ Arrow</button>
74
+ <button id="kg-tool-label">T Label</button>
75
+ <div class="sep"></div>
76
+ <button id="kg-delete">🗑 Delete</button>
77
+ <button class="save-btn" id="kg-save">💾 Save</button>
78
+ `;
79
+ document.body.appendChild(toolbar);
80
+ toolbarHeight =
81
+ Math.ceil(toolbar.getBoundingClientRect().height) || TOOLBAR_HEIGHT_FALLBACK;
82
+ document.body.style.paddingTop = `${toolbarHeight}px`;
83
+ const hint = document.createElement("div");
84
+ hint.className = "kagemusha-hint";
85
+ hint.textContent =
86
+ "Click and drag to add annotations. Click to select. Press Delete to remove.";
87
+ document.body.appendChild(hint);
88
+ // --- SVG LAYER ---
89
+ const svg = document.createElementNS(svgNS, "svg");
90
+ svg.id = "kagemusha-svg-layer";
91
+ svg.classList.add("drawing");
92
+ document.body.appendChild(svg);
93
+ const updateSvgSize = () => {
94
+ svg.setAttribute("width", String(window.innerWidth));
95
+ svg.setAttribute("height", String(document.documentElement.scrollHeight));
96
+ };
97
+ updateSvgSize();
98
+ window.addEventListener("resize", updateSvgSize);
99
+ const defs = document.createElementNS(svgNS, "defs");
100
+ defs.innerHTML =
101
+ '<marker id="kg-arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#FF0000"><polygon points="0 0, 10 3.5, 0 7"/></marker>';
102
+ svg.appendChild(defs);
103
+ // Capture layer goes BEFORE annotations so it renders behind them
104
+ const captureGroup = document.createElementNS(svgNS, "g");
105
+ captureGroup.id = "kagemusha-capture-group";
106
+ svg.appendChild(captureGroup);
107
+ // --- HELPERS ---
108
+ const getPos = (e) => ({ x: e.pageX, y: e.pageY });
109
+ const deselectAll = () => {
110
+ selectedId = null;
111
+ svg.querySelectorAll(".annotation").forEach((el) => {
112
+ el.classList.remove("selected");
113
+ });
114
+ };
115
+ const selectEl = (el) => {
116
+ deselectAll();
117
+ selectedId = el.dataset.id ?? null;
118
+ el.classList.add("selected");
119
+ };
120
+ const deleteSelected = () => {
121
+ if (!selectedId)
122
+ return;
123
+ const el = svg.querySelector(`[data-id="${selectedId}"]`);
124
+ if (el)
125
+ el.remove();
126
+ annotations = annotations.filter((a) => a.id !== selectedId);
127
+ selectedId = null;
128
+ };
129
+ const startMove = (e, id) => {
130
+ e.stopPropagation();
131
+ const el = svg.querySelector(`[data-id="${id}"]`);
132
+ if (!el)
133
+ return;
134
+ selectEl(el);
135
+ const p = getPos(e);
136
+ dragState = {
137
+ type: "move",
138
+ id,
139
+ el: el,
140
+ lastX: p.x,
141
+ lastY: p.y,
142
+ };
143
+ };
144
+ const measureTextWidth = (text, fontSize) => {
145
+ const tmp = document.createElementNS(svgNS, "text");
146
+ tmp.setAttribute("font-size", String(fontSize));
147
+ tmp.setAttribute("font-family", "-apple-system, sans-serif");
148
+ tmp.textContent = text;
149
+ svg.appendChild(tmp);
150
+ const width = tmp.getBBox().width;
151
+ tmp.remove();
152
+ return width;
153
+ };
154
+ const createLabelGroup = (id, x, y, text, fontSize = 14) => {
155
+ const g = document.createElementNS(svgNS, "g");
156
+ g.classList.add("annotation");
157
+ g.dataset.id = id;
158
+ const bg = document.createElementNS(svgNS, "rect");
159
+ const txt = document.createElementNS(svgNS, "text");
160
+ txt.textContent = text;
161
+ txt.setAttribute("x", String(x + 6));
162
+ txt.setAttribute("y", String(y + 16));
163
+ txt.setAttribute("fill", "#FF0000");
164
+ txt.setAttribute("font-size", String(fontSize));
165
+ txt.setAttribute("font-family", "-apple-system, sans-serif");
166
+ const tw = measureTextWidth(text, fontSize) + 12;
167
+ bg.setAttribute("x", String(x));
168
+ bg.setAttribute("y", String(y));
169
+ bg.setAttribute("width", String(tw));
170
+ bg.setAttribute("height", "24");
171
+ bg.setAttribute("fill", "#FFFFFF");
172
+ bg.setAttribute("rx", "4");
173
+ g.appendChild(bg);
174
+ g.appendChild(txt);
175
+ return g;
176
+ };
177
+ // --- CAPTURE HELPERS ---
178
+ const clearCaptureVisual = () => {
179
+ captureGroup.replaceChildren();
180
+ };
181
+ const HANDLE_POSITIONS = [
182
+ { handle: "nw", pos: (c) => ({ cx: c.x, cy: c.y }) },
183
+ { handle: "n", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y }) },
184
+ { handle: "ne", pos: (c) => ({ cx: c.x + c.w, cy: c.y }) },
185
+ { handle: "e", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h / 2 }) },
186
+ { handle: "se", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h }) },
187
+ { handle: "s", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y + c.h }) },
188
+ { handle: "sw", pos: (c) => ({ cx: c.x, cy: c.y + c.h }) },
189
+ { handle: "w", pos: (c) => ({ cx: c.x, cy: c.y + c.h / 2 }) },
190
+ ];
191
+ const redrawCropVisual = () => {
192
+ if (captureMode !== "crop" || !captureCrop) {
193
+ clearCaptureVisual();
194
+ return;
195
+ }
196
+ clearCaptureVisual();
197
+ const r = document.createElementNS(svgNS, "rect");
198
+ r.setAttribute("x", String(captureCrop.x));
199
+ r.setAttribute("y", String(captureCrop.y));
200
+ r.setAttribute("width", String(captureCrop.w));
201
+ r.setAttribute("height", String(captureCrop.h));
202
+ r.setAttribute("class", "capture-crop-box");
203
+ captureGroup.appendChild(r);
204
+ for (const { handle, pos } of HANDLE_POSITIONS) {
205
+ const { cx, cy } = pos(captureCrop);
206
+ const h = document.createElementNS(svgNS, "rect");
207
+ h.setAttribute("x", String(cx - HANDLE_SIZE / 2));
208
+ h.setAttribute("y", String(cy - HANDLE_SIZE / 2));
209
+ h.setAttribute("width", String(HANDLE_SIZE));
210
+ h.setAttribute("height", String(HANDLE_SIZE));
211
+ h.setAttribute("class", `crop-handle ${handle}`);
212
+ h.dataset.handle = handle;
213
+ captureGroup.appendChild(h);
214
+ }
215
+ };
216
+ const updateCaptureUi = () => {
217
+ document
218
+ .querySelectorAll("#kagemusha-toolbar .cap-btn")
219
+ .forEach((b) => {
220
+ b.classList.remove("active");
221
+ });
222
+ const activeId = captureMode === "fullPage" ? "kg-cap-full" : "kg-cap-crop";
223
+ document.getElementById(activeId)?.classList.add("active");
224
+ svg.classList.toggle("cropping", tool === "crop");
225
+ svg.classList.toggle("drawing", tool === "rect" || tool === "arrow" || tool === "label");
226
+ };
227
+ const setCaptureMode = (mode, opts = {}) => {
228
+ captureMode = mode;
229
+ if (mode === "fullPage") {
230
+ captureCrop = null;
231
+ tool = "rect";
232
+ clearCaptureVisual();
233
+ }
234
+ else {
235
+ if (opts.resetSelection)
236
+ captureCrop = null;
237
+ tool = "crop";
238
+ redrawCropVisual();
239
+ }
240
+ // Mark the matching annotation tool button too
241
+ document
242
+ .querySelectorAll("#kg-tool-rect, #kg-tool-arrow, #kg-tool-label")
243
+ .forEach((b) => {
244
+ b.classList.remove("active");
245
+ });
246
+ if (tool === "rect" || tool === "arrow" || tool === "label") {
247
+ document.getElementById(`kg-tool-${tool}`)?.classList.add("active");
248
+ }
249
+ updateCaptureUi();
250
+ deselectAll();
251
+ };
252
+ // --- TOOLS ---
253
+ const setTool = (t) => {
254
+ tool = t;
255
+ document
256
+ .querySelectorAll("#kg-tool-rect, #kg-tool-arrow, #kg-tool-label")
257
+ .forEach((b) => {
258
+ b.classList.remove("active");
259
+ });
260
+ document.getElementById(`kg-tool-${t}`)?.classList.add("active");
261
+ // Switching to an annotation tool exits capture-edit interaction
262
+ // (capture settings themselves are preserved).
263
+ updateCaptureUi();
264
+ deselectAll();
265
+ };
266
+ document
267
+ .getElementById("kg-tool-rect")
268
+ ?.addEventListener("click", () => setTool("rect"));
269
+ document
270
+ .getElementById("kg-tool-arrow")
271
+ ?.addEventListener("click", () => setTool("arrow"));
272
+ document
273
+ .getElementById("kg-tool-label")
274
+ ?.addEventListener("click", () => setTool("label"));
275
+ document
276
+ .getElementById("kg-cap-full")
277
+ ?.addEventListener("click", () => setCaptureMode("fullPage"));
278
+ document
279
+ .getElementById("kg-cap-crop")
280
+ ?.addEventListener("click", () => setCaptureMode("crop", { resetSelection: !captureCrop }));
281
+ document.getElementById("kg-delete")?.addEventListener("click", deleteSelected);
282
+ document.addEventListener("keydown", (e) => {
283
+ if (e.key === "Delete" || e.key === "Backspace") {
284
+ if (document.activeElement?.tagName === "INPUT")
285
+ return;
286
+ deleteSelected();
287
+ }
288
+ });
289
+ // --- MOUSE: CREATE ---
290
+ svg.addEventListener("mousedown", (e) => {
291
+ if (e.target?.closest(".annotation"))
292
+ return;
293
+ const p = getPos(e);
294
+ if (tool === "crop") {
295
+ e.preventDefault();
296
+ deselectAll();
297
+ const target = e.target;
298
+ const handleAttr = target.dataset?.handle;
299
+ // Resize from a handle
300
+ if (handleAttr && captureCrop) {
301
+ cropDragState = {
302
+ kind: "resize",
303
+ handle: handleAttr,
304
+ sx: p.x,
305
+ sy: p.y,
306
+ orig: { ...captureCrop },
307
+ };
308
+ return;
309
+ }
310
+ // Move the existing crop body
311
+ if (target?.classList?.contains("capture-crop-box") && captureCrop) {
312
+ cropDragState = {
313
+ kind: "move",
314
+ sx: p.x,
315
+ sy: p.y,
316
+ orig: { ...captureCrop },
317
+ };
318
+ return;
319
+ }
320
+ // Otherwise: drag-to-create new crop (replaces existing)
321
+ cropDragState = { kind: "create", sx: p.x, sy: p.y };
322
+ captureCrop = null;
323
+ clearCaptureVisual();
324
+ const r = document.createElementNS(svgNS, "rect");
325
+ r.setAttribute("x", String(p.x));
326
+ r.setAttribute("y", String(p.y));
327
+ r.setAttribute("width", "0");
328
+ r.setAttribute("height", "0");
329
+ r.setAttribute("class", "capture-crop-box");
330
+ captureGroup.appendChild(r);
331
+ return;
332
+ }
333
+ deselectAll();
334
+ if (tool === "rect") {
335
+ const id = `a${nextId++}`;
336
+ const rect = document.createElementNS(svgNS, "rect");
337
+ rect.setAttribute("x", String(p.x));
338
+ rect.setAttribute("y", String(p.y));
339
+ rect.setAttribute("width", "0");
340
+ rect.setAttribute("height", "0");
341
+ rect.setAttribute("fill", "none");
342
+ rect.setAttribute("stroke", "#FF0000");
343
+ rect.setAttribute("stroke-width", "3");
344
+ rect.setAttribute("rx", "4");
345
+ rect.classList.add("annotation");
346
+ rect.dataset.id = id;
347
+ svg.appendChild(rect);
348
+ dragState = { type: "create-rect", id, el: rect, sx: p.x, sy: p.y };
349
+ }
350
+ else if (tool === "arrow") {
351
+ const id = `a${nextId++}`;
352
+ const line = document.createElementNS(svgNS, "line");
353
+ line.setAttribute("x1", String(p.x));
354
+ line.setAttribute("y1", String(p.y));
355
+ line.setAttribute("x2", String(p.x));
356
+ line.setAttribute("y2", String(p.y));
357
+ line.setAttribute("stroke", "#FF0000");
358
+ line.setAttribute("stroke-width", "3");
359
+ line.setAttribute("marker-end", "url(#kg-arrowhead)");
360
+ line.classList.add("annotation");
361
+ line.dataset.id = id;
362
+ svg.appendChild(line);
363
+ dragState = { type: "create-arrow", id, el: line, sx: p.x, sy: p.y };
364
+ }
365
+ else if (tool === "label") {
366
+ const id = `a${nextId++}`;
367
+ const input = document.createElement("input");
368
+ input.type = "text";
369
+ input.value = "";
370
+ input.placeholder = "Type label...";
371
+ input.style.cssText =
372
+ "position:fixed;z-index:9999999;padding:4px 8px;background:#fff;border:none;border-radius:4px;color:#FF0000;font-size:14px;font-family:-apple-system,sans-serif;outline:2px solid #6366f1;min-width:80px;box-shadow:0 2px 8px rgba(0,0,0,0.2);";
373
+ input.style.left = `${e.clientX}px`;
374
+ input.style.top = `${e.clientY}px`;
375
+ document.body.appendChild(input);
376
+ svg.classList.remove("drawing");
377
+ setTimeout(() => input.focus(), 50);
378
+ let labelFinished = false;
379
+ const finishLabel = () => {
380
+ if (labelFinished)
381
+ return;
382
+ labelFinished = true;
383
+ const text = input.value.trim();
384
+ input.remove();
385
+ svg.classList.add("drawing");
386
+ if (!text)
387
+ return;
388
+ const g = createLabelGroup(id, p.x, p.y, text);
389
+ svg.appendChild(g);
390
+ annotations.push({ id, type: "label", x: p.x, y: p.y, text });
391
+ selectEl(g);
392
+ g.addEventListener("mousedown", (ev) => startMove(ev, id));
393
+ };
394
+ input.addEventListener("keydown", (ev) => {
395
+ if (ev.key === "Enter")
396
+ finishLabel();
397
+ if (ev.key === "Escape") {
398
+ labelFinished = true;
399
+ input.remove();
400
+ svg.classList.add("drawing");
401
+ }
402
+ });
403
+ input.addEventListener("blur", finishLabel);
404
+ }
405
+ });
406
+ // --- MOUSE: DRAG ---
407
+ document.addEventListener("mousemove", (e) => {
408
+ if (cropDragState) {
409
+ const p = getPos(e);
410
+ if (cropDragState.kind === "create") {
411
+ const x = Math.min(cropDragState.sx, p.x);
412
+ const y = Math.min(cropDragState.sy, p.y);
413
+ const w = Math.abs(p.x - cropDragState.sx);
414
+ const h = Math.abs(p.y - cropDragState.sy);
415
+ const r = captureGroup.firstChild;
416
+ if (r) {
417
+ r.setAttribute("x", String(x));
418
+ r.setAttribute("y", String(y));
419
+ r.setAttribute("width", String(w));
420
+ r.setAttribute("height", String(h));
421
+ }
422
+ return;
423
+ }
424
+ if (cropDragState.kind === "move") {
425
+ const dx = p.x - cropDragState.sx;
426
+ const dy = p.y - cropDragState.sy;
427
+ captureCrop = {
428
+ x: cropDragState.orig.x + dx,
429
+ y: cropDragState.orig.y + dy,
430
+ w: cropDragState.orig.w,
431
+ h: cropDragState.orig.h,
432
+ };
433
+ redrawCropVisual();
434
+ return;
435
+ }
436
+ // resize
437
+ const dx = p.x - cropDragState.sx;
438
+ const dy = p.y - cropDragState.sy;
439
+ const o = cropDragState.orig;
440
+ let nx = o.x;
441
+ let ny = o.y;
442
+ let nw = o.w;
443
+ let nh = o.h;
444
+ const h = cropDragState.handle;
445
+ if (h.includes("w")) {
446
+ nx = o.x + dx;
447
+ nw = o.w - dx;
448
+ }
449
+ if (h.includes("e")) {
450
+ nw = o.w + dx;
451
+ }
452
+ if (h.includes("n")) {
453
+ ny = o.y + dy;
454
+ nh = o.h - dy;
455
+ }
456
+ if (h.includes("s")) {
457
+ nh = o.h + dy;
458
+ }
459
+ // Keep min size and prevent flip-through
460
+ if (nw < MIN_CROP) {
461
+ if (h.includes("w"))
462
+ nx = o.x + (o.w - MIN_CROP);
463
+ nw = MIN_CROP;
464
+ }
465
+ if (nh < MIN_CROP) {
466
+ if (h.includes("n"))
467
+ ny = o.y + (o.h - MIN_CROP);
468
+ nh = MIN_CROP;
469
+ }
470
+ captureCrop = { x: nx, y: ny, w: nw, h: nh };
471
+ redrawCropVisual();
472
+ return;
473
+ }
474
+ if (!dragState)
475
+ return;
476
+ const p = getPos(e);
477
+ if (dragState.type === "create-rect") {
478
+ dragState.el.setAttribute("x", String(Math.min(dragState.sx ?? 0, p.x)));
479
+ dragState.el.setAttribute("y", String(Math.min(dragState.sy ?? 0, p.y)));
480
+ dragState.el.setAttribute("width", String(Math.abs(p.x - (dragState.sx ?? 0))));
481
+ dragState.el.setAttribute("height", String(Math.abs(p.y - (dragState.sy ?? 0))));
482
+ }
483
+ else if (dragState.type === "create-arrow") {
484
+ dragState.el.setAttribute("x2", String(p.x));
485
+ dragState.el.setAttribute("y2", String(p.y));
486
+ }
487
+ else if (dragState.type === "move") {
488
+ const a = annotations.find((ann) => ann.id === dragState?.id);
489
+ if (!a)
490
+ return;
491
+ const dx = p.x - (dragState.lastX ?? 0);
492
+ const dy = p.y - (dragState.lastY ?? 0);
493
+ dragState.lastX = p.x;
494
+ dragState.lastY = p.y;
495
+ if (a.type === "rect") {
496
+ a.x = (a.x ?? 0) + dx;
497
+ a.y = (a.y ?? 0) + dy;
498
+ dragState.el.setAttribute("x", String(a.x));
499
+ dragState.el.setAttribute("y", String(a.y));
500
+ }
501
+ else if (a.type === "arrow") {
502
+ a.fromX = (a.fromX ?? 0) + dx;
503
+ a.fromY = (a.fromY ?? 0) + dy;
504
+ a.toX = (a.toX ?? 0) + dx;
505
+ a.toY = (a.toY ?? 0) + dy;
506
+ dragState.el.setAttribute("x1", String(a.fromX));
507
+ dragState.el.setAttribute("y1", String(a.fromY));
508
+ dragState.el.setAttribute("x2", String(a.toX));
509
+ dragState.el.setAttribute("y2", String(a.toY));
510
+ }
511
+ else if (a.type === "label") {
512
+ a.x = (a.x ?? 0) + dx;
513
+ a.y = (a.y ?? 0) + dy;
514
+ const bg = dragState.el.querySelector("rect");
515
+ const txt = dragState.el.querySelector("text");
516
+ bg?.setAttribute("x", String(a.x));
517
+ bg?.setAttribute("y", String(a.y));
518
+ txt?.setAttribute("x", String(a.x + 6));
519
+ txt?.setAttribute("y", String(a.y + 16));
520
+ }
521
+ }
522
+ });
523
+ // --- MOUSE: UP ---
524
+ document.addEventListener("mouseup", (e) => {
525
+ if (cropDragState) {
526
+ if (cropDragState.kind === "create") {
527
+ const p = getPos(e);
528
+ const w = Math.abs(p.x - cropDragState.sx);
529
+ const h = Math.abs(p.y - cropDragState.sy);
530
+ if (w < 5 || h < 5) {
531
+ cropDragState = null;
532
+ redrawCropVisual();
533
+ return;
534
+ }
535
+ captureCrop = {
536
+ x: Math.min(cropDragState.sx, p.x),
537
+ y: Math.min(cropDragState.sy, p.y),
538
+ w,
539
+ h,
540
+ };
541
+ }
542
+ // move / resize: state is already applied via redrawCropVisual on mousemove
543
+ cropDragState = null;
544
+ redrawCropVisual();
545
+ return;
546
+ }
547
+ if (!dragState)
548
+ return;
549
+ const p = getPos(e);
550
+ if (dragState.type === "create-rect") {
551
+ const w = Math.abs(p.x - (dragState.sx ?? 0));
552
+ const h = Math.abs(p.y - (dragState.sy ?? 0));
553
+ if (w < 5 && h < 5) {
554
+ dragState.el.remove();
555
+ }
556
+ else {
557
+ const a = {
558
+ id: dragState.id,
559
+ type: "rect",
560
+ x: Math.min(dragState.sx ?? 0, p.x),
561
+ y: Math.min(dragState.sy ?? 0, p.y),
562
+ width: w,
563
+ height: h,
564
+ };
565
+ annotations.push(a);
566
+ selectEl(dragState.el);
567
+ const capturedId = dragState.id;
568
+ dragState.el.addEventListener("mousedown", (ev) => startMove(ev, capturedId));
569
+ }
570
+ }
571
+ else if (dragState.type === "create-arrow") {
572
+ const dist = Math.hypot(p.x - (dragState.sx ?? 0), p.y - (dragState.sy ?? 0));
573
+ if (dist < 5) {
574
+ dragState.el.remove();
575
+ }
576
+ else {
577
+ const a = {
578
+ id: dragState.id,
579
+ type: "arrow",
580
+ fromX: dragState.sx,
581
+ fromY: dragState.sy,
582
+ toX: p.x,
583
+ toY: p.y,
584
+ };
585
+ annotations.push(a);
586
+ selectEl(dragState.el);
587
+ const capturedId = dragState.id;
588
+ dragState.el.addEventListener("mousedown", (ev) => startMove(ev, capturedId));
589
+ }
590
+ }
591
+ dragState = null;
592
+ });
593
+ // --- LOAD EXISTING ---
594
+ _win.__kagemusha_loadAnnotations = (decorations) => {
595
+ const dpr = window.devicePixelRatio || 1;
596
+ for (const d of decorations) {
597
+ const id = `a${nextId++}`;
598
+ if (d.type === "rect" && d.target) {
599
+ const rx = d.target.x / dpr;
600
+ const ry = d.target.y / dpr + toolbarHeight;
601
+ const rw = d.target.width / dpr;
602
+ const rh = d.target.height / dpr;
603
+ const rect = document.createElementNS(svgNS, "rect");
604
+ rect.setAttribute("x", String(rx));
605
+ rect.setAttribute("y", String(ry));
606
+ rect.setAttribute("width", String(rw));
607
+ rect.setAttribute("height", String(rh));
608
+ rect.setAttribute("fill", "none");
609
+ rect.setAttribute("stroke", d.style?.color ?? "#FF0000");
610
+ rect.setAttribute("stroke-width", "3");
611
+ rect.setAttribute("rx", "4");
612
+ rect.classList.add("annotation");
613
+ rect.dataset.id = id;
614
+ svg.appendChild(rect);
615
+ annotations.push({
616
+ id,
617
+ type: "rect",
618
+ x: rx,
619
+ y: ry,
620
+ width: rw,
621
+ height: rh,
622
+ });
623
+ rect.addEventListener("mousedown", (ev) => startMove(ev, id));
624
+ }
625
+ else if (d.type === "arrow" && d.from && d.to) {
626
+ const ax1 = d.from.x / dpr;
627
+ const ay1 = d.from.y / dpr + toolbarHeight;
628
+ const ax2 = d.to.x / dpr;
629
+ const ay2 = d.to.y / dpr + toolbarHeight;
630
+ const line = document.createElementNS(svgNS, "line");
631
+ line.setAttribute("x1", String(ax1));
632
+ line.setAttribute("y1", String(ay1));
633
+ line.setAttribute("x2", String(ax2));
634
+ line.setAttribute("y2", String(ay2));
635
+ line.setAttribute("stroke", d.style?.color ?? "#FF0000");
636
+ line.setAttribute("stroke-width", "3");
637
+ line.setAttribute("marker-end", "url(#kg-arrowhead)");
638
+ line.classList.add("annotation");
639
+ line.dataset.id = id;
640
+ svg.appendChild(line);
641
+ annotations.push({
642
+ id,
643
+ type: "arrow",
644
+ fromX: ax1,
645
+ fromY: ay1,
646
+ toX: ax2,
647
+ toY: ay2,
648
+ });
649
+ line.addEventListener("mousedown", (ev) => startMove(ev, id));
650
+ }
651
+ else if (d.type === "label" && d.position) {
652
+ const lx = d.position.x / dpr;
653
+ const ly = d.position.y / dpr + toolbarHeight;
654
+ const fontSize = (d.style?.fontSize ?? 14) / dpr;
655
+ const g = createLabelGroup(id, lx, ly, d.text ?? "", fontSize);
656
+ svg.appendChild(g);
657
+ annotations.push({ id, type: "label", x: lx, y: ly, text: d.text });
658
+ g.addEventListener("mousedown", (ev) => startMove(ev, id));
659
+ }
660
+ }
661
+ };
662
+ // --- LOAD EXISTING CAPTURE CONFIG ---
663
+ _win.__kagemusha_loadCapture = (capture) => {
664
+ if (!capture ||
665
+ !["fullPage", "crop"].includes(capture.mode ?? "")) {
666
+ // Unknown / legacy mode (e.g. removed "selector") — fall back to fullPage
667
+ setCaptureMode("fullPage");
668
+ setTool("rect");
669
+ return;
670
+ }
671
+ if (capture.mode === "fullPage") {
672
+ setCaptureMode("fullPage");
673
+ // Leave annotation tool active after load
674
+ setTool("rect");
675
+ return;
676
+ }
677
+ if (capture.mode === "crop") {
678
+ // crop is stored in page CSS pixels (not DPR-scaled); shift by toolbar for display
679
+ const sx = capture.crop.start.x;
680
+ const sy = capture.crop.start.y + toolbarHeight;
681
+ const ex = capture.crop.end.x;
682
+ const ey = capture.crop.end.y + toolbarHeight;
683
+ captureCrop = { x: sx, y: sy, w: ex - sx, h: ey - sy };
684
+ captureMode = "crop";
685
+ tool = "rect";
686
+ redrawCropVisual();
687
+ updateCaptureUi();
688
+ }
689
+ };
690
+ // --- SAVE ---
691
+ const save = () => {
692
+ if (captureMode === "crop" && !captureCrop) {
693
+ alert("Crop mode is active but no area is drawn.\nDrag to define an area, or switch to Full Page.");
694
+ return;
695
+ }
696
+ const dpr = window.devicePixelRatio || 1;
697
+ const s = Math.round;
698
+ const decorations = annotations
699
+ .map((a) => {
700
+ if (a.type === "rect") {
701
+ return {
702
+ type: "rect",
703
+ target: {
704
+ x: s((a.x ?? 0) * dpr),
705
+ y: s(((a.y ?? 0) - toolbarHeight) * dpr),
706
+ width: s((a.width ?? 0) * dpr),
707
+ height: s((a.height ?? 0) * dpr),
708
+ },
709
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) },
710
+ };
711
+ }
712
+ if (a.type === "arrow") {
713
+ return {
714
+ type: "arrow",
715
+ from: {
716
+ x: s((a.fromX ?? 0) * dpr),
717
+ y: s(((a.fromY ?? 0) - toolbarHeight) * dpr),
718
+ },
719
+ to: {
720
+ x: s((a.toX ?? 0) * dpr),
721
+ y: s(((a.toY ?? 0) - toolbarHeight) * dpr),
722
+ },
723
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) },
724
+ };
725
+ }
726
+ if (a.type === "label") {
727
+ return {
728
+ type: "label",
729
+ text: a.text,
730
+ position: {
731
+ x: s((a.x ?? 0) * dpr),
732
+ y: s(((a.y ?? 0) - toolbarHeight) * dpr),
733
+ },
734
+ style: {
735
+ fontSize: s(14 * dpr),
736
+ color: "#FF0000",
737
+ background: "#FFFFFF",
738
+ },
739
+ };
740
+ }
741
+ return null;
742
+ })
743
+ .filter((d) => d !== null);
744
+ let capture;
745
+ if (captureMode === "crop" && captureCrop) {
746
+ // Store in page CSS pixels (Playwright's clip is in CSS pixels, not DPR)
747
+ capture = {
748
+ mode: "crop",
749
+ crop: {
750
+ start: {
751
+ x: Math.round(captureCrop.x),
752
+ y: Math.round(captureCrop.y - toolbarHeight),
753
+ },
754
+ end: {
755
+ x: Math.round(captureCrop.x + captureCrop.w),
756
+ y: Math.round(captureCrop.y + captureCrop.h - toolbarHeight),
757
+ },
758
+ },
759
+ };
760
+ }
761
+ else {
762
+ capture = { mode: "fullPage" };
763
+ }
764
+ _win.__kagemusha_save(JSON.stringify({ decorations, capture }));
765
+ };
766
+ document.getElementById("kg-save")?.addEventListener("click", save);
767
+ export {};
768
+ //# sourceMappingURL=inject-script.js.map