@wasao/kagemusha 0.1.1 → 0.3.4

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 (155) hide show
  1. package/README.md +168 -79
  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 +3 -2
  7. package/dist/commands/capture.d.ts.map +1 -1
  8. package/dist/commands/capture.js +256 -20
  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 +1 -1
  15. package/dist/commands/edit.d.ts.map +1 -1
  16. package/dist/commands/edit.js +82 -26
  17. package/dist/commands/edit.js.map +1 -1
  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 +240 -105
  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 +131 -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/annotations.d.ts +11 -0
  33. package/dist/editor/inject-script/annotations.d.ts.map +1 -0
  34. package/dist/editor/inject-script/annotations.js +409 -0
  35. package/dist/editor/inject-script/annotations.js.map +1 -0
  36. package/dist/editor/inject-script/bridge.d.ts +13 -0
  37. package/dist/editor/inject-script/bridge.d.ts.map +1 -0
  38. package/dist/editor/inject-script/bridge.js +33 -0
  39. package/dist/editor/inject-script/bridge.js.map +1 -0
  40. package/dist/editor/inject-script/crop.d.ts +9 -0
  41. package/dist/editor/inject-script/crop.d.ts.map +1 -0
  42. package/dist/editor/inject-script/crop.js +236 -0
  43. package/dist/editor/inject-script/crop.js.map +1 -0
  44. package/dist/editor/inject-script/dom.d.ts +7 -0
  45. package/dist/editor/inject-script/dom.d.ts.map +1 -0
  46. package/dist/editor/inject-script/dom.js +32 -0
  47. package/dist/editor/inject-script/dom.js.map +1 -0
  48. package/dist/editor/inject-script/index.d.ts +2 -0
  49. package/dist/editor/inject-script/index.d.ts.map +1 -0
  50. package/dist/editor/inject-script/index.js +56 -0
  51. package/dist/editor/inject-script/index.js.map +1 -0
  52. package/dist/editor/inject-script/record.d.ts +5 -0
  53. package/dist/editor/inject-script/record.d.ts.map +1 -0
  54. package/dist/editor/inject-script/record.js +398 -0
  55. package/dist/editor/inject-script/record.js.map +1 -0
  56. package/dist/editor/inject-script/selector.d.ts +6 -0
  57. package/dist/editor/inject-script/selector.d.ts.map +1 -0
  58. package/dist/editor/inject-script/selector.js +112 -0
  59. package/dist/editor/inject-script/selector.js.map +1 -0
  60. package/dist/editor/inject-script/state.d.ts +27 -0
  61. package/dist/editor/inject-script/state.d.ts.map +1 -0
  62. package/dist/editor/inject-script/state.js +26 -0
  63. package/dist/editor/inject-script/state.js.map +1 -0
  64. package/dist/editor/inject-script/svg.d.ts +7 -0
  65. package/dist/editor/inject-script/svg.d.ts.map +1 -0
  66. package/dist/editor/inject-script/svg.js +39 -0
  67. package/dist/editor/inject-script/svg.js.map +1 -0
  68. package/dist/editor/inject-script/toolbar.d.ts +14 -0
  69. package/dist/editor/inject-script/toolbar.d.ts.map +1 -0
  70. package/dist/editor/inject-script/toolbar.js +240 -0
  71. package/dist/editor/inject-script/toolbar.js.map +1 -0
  72. package/dist/editor/inject-script/types.d.ts +102 -0
  73. package/dist/editor/inject-script/types.d.ts.map +1 -0
  74. package/dist/editor/inject-script/types.js +5 -0
  75. package/dist/editor/inject-script/types.js.map +1 -0
  76. package/dist/editor/inject-script.js +1276 -353
  77. package/dist/index.js +34 -16
  78. package/dist/index.js.map +1 -1
  79. package/dist/lib/annotate.d.ts +2 -2
  80. package/dist/lib/annotate.d.ts.map +1 -1
  81. package/dist/lib/annotate.js +35 -43
  82. package/dist/lib/annotate.js.map +1 -1
  83. package/dist/lib/auth.d.ts +18 -0
  84. package/dist/lib/auth.d.ts.map +1 -0
  85. package/dist/lib/auth.js +45 -0
  86. package/dist/lib/auth.js.map +1 -0
  87. package/dist/lib/aws-error.d.ts +7 -0
  88. package/dist/lib/aws-error.d.ts.map +1 -0
  89. package/dist/lib/aws-error.js +74 -0
  90. package/dist/lib/aws-error.js.map +1 -0
  91. package/dist/lib/canonical.d.ts +54 -0
  92. package/dist/lib/canonical.d.ts.map +1 -0
  93. package/dist/lib/canonical.js +152 -0
  94. package/dist/lib/canonical.js.map +1 -0
  95. package/dist/lib/config.d.ts +2 -0
  96. package/dist/lib/config.d.ts.map +1 -1
  97. package/dist/lib/config.js +23 -13
  98. package/dist/lib/config.js.map +1 -1
  99. package/dist/lib/crawl.d.ts +1 -1
  100. package/dist/lib/crawl.d.ts.map +1 -1
  101. package/dist/lib/crawl.js +213 -20
  102. package/dist/lib/crawl.js.map +1 -1
  103. package/dist/lib/definition.d.ts +2 -0
  104. package/dist/lib/definition.d.ts.map +1 -0
  105. package/dist/lib/definition.js +6 -0
  106. package/dist/lib/definition.js.map +1 -0
  107. package/dist/lib/diff.d.ts +71 -0
  108. package/dist/lib/diff.d.ts.map +1 -0
  109. package/dist/lib/diff.js +40 -0
  110. package/dist/lib/diff.js.map +1 -0
  111. package/dist/lib/login-error.d.ts +10 -0
  112. package/dist/lib/login-error.d.ts.map +1 -0
  113. package/dist/lib/login-error.js +13 -0
  114. package/dist/lib/login-error.js.map +1 -0
  115. package/dist/lib/page-ready.d.ts +18 -0
  116. package/dist/lib/page-ready.d.ts.map +1 -0
  117. package/dist/lib/page-ready.js +19 -0
  118. package/dist/lib/page-ready.js.map +1 -0
  119. package/dist/lib/screenshot.d.ts +11 -2
  120. package/dist/lib/screenshot.d.ts.map +1 -1
  121. package/dist/lib/screenshot.js +73 -64
  122. package/dist/lib/screenshot.js.map +1 -1
  123. package/dist/lib/staging.d.ts +7 -0
  124. package/dist/lib/staging.d.ts.map +1 -0
  125. package/dist/lib/staging.js +21 -0
  126. package/dist/lib/staging.js.map +1 -0
  127. package/dist/types.d.ts +10 -23
  128. package/dist/types.d.ts.map +1 -1
  129. package/package.json +20 -3
  130. package/dist/commands/preview.d.ts +0 -6
  131. package/dist/commands/preview.d.ts.map +0 -1
  132. package/dist/commands/preview.js +0 -33
  133. package/dist/commands/preview.js.map +0 -1
  134. package/dist/commands/run.d.ts +0 -6
  135. package/dist/commands/run.d.ts.map +0 -1
  136. package/dist/commands/run.js +0 -39
  137. package/dist/commands/run.js.map +0 -1
  138. package/dist/editor/editor/editor.html +0 -313
  139. package/dist/editor/editor/inject.ts +0 -385
  140. package/dist/editor/editor.html +0 -338
  141. package/dist/editor/inject-script.cjs +0 -398
  142. package/dist/editor/inject-script.cjs.map +0 -1
  143. package/dist/editor/inject-script.d.cts +0 -2
  144. package/dist/editor/inject-script.d.cts.map +0 -1
  145. package/dist/editor/inject-script.d.ts +0 -2
  146. package/dist/editor/inject-script.d.ts.map +0 -1
  147. package/dist/editor/inject-script.js.map +0 -1
  148. package/dist/editor/inject.d.ts +0 -2
  149. package/dist/editor/inject.d.ts.map +0 -1
  150. package/dist/editor/inject.js +0 -385
  151. package/dist/editor/inject.js.map +0 -1
  152. package/dist/lib/upload.d.ts +0 -9
  153. package/dist/lib/upload.d.ts.map +0 -1
  154. package/dist/lib/upload.js +0 -43
  155. package/dist/lib/upload.js.map +0 -1
@@ -1,128 +1,132 @@
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 = 48;
7
- const svgNS = "http://www.w3.org/2000/svg";
8
- let tool = "rect";
9
- let annotations = [];
10
- let selectedId = null;
11
- let dragState = null;
12
- let nextId = 1;
13
- // --- TOOLBAR ---
14
- const toolbar = document.createElement("div");
15
- toolbar.id = "kagemusha-toolbar";
16
- toolbar.innerHTML = `
17
- <style>
18
- #kagemusha-toolbar {
19
- position: fixed; top: 0; left: 0; right: 0; z-index: 999999;
20
- background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 12px;
21
- box-shadow: 0 2px 8px rgba(0,0,0,0.3); font-family: -apple-system, sans-serif;
22
- }
23
- #kagemusha-toolbar button {
24
- padding: 6px 14px; border: 1px solid #444; border-radius: 6px;
25
- background: #1a1a2e; color: #fff; font-size: 13px; cursor: pointer;
26
- }
27
- #kagemusha-toolbar button:hover { background: #2a2a4e; }
28
- #kagemusha-toolbar button.active { background: #6366f1; border-color: #6366f1; }
29
- #kagemusha-toolbar .sep { width: 1px; height: 24px; background: #444; }
30
- #kagemusha-toolbar .title { color: #888; font-size: 13px; }
31
- #kagemusha-toolbar .save-btn { background: #22c55e; border-color: #22c55e; font-weight: 600; margin-left: auto; }
32
- #kagemusha-toolbar .save-btn:hover { background: #16a34a; }
33
- #kagemusha-svg-layer {
34
- position: absolute; top: 0; left: 0; width: 100%;
35
- z-index: 999998; pointer-events: none;
36
- }
37
- #kagemusha-svg-layer.drawing { pointer-events: auto; cursor: crosshair; }
38
- #kagemusha-svg-layer .annotation { pointer-events: auto; cursor: move; }
39
- #kagemusha-svg-layer .annotation.selected { filter: drop-shadow(0 0 3px #6366f1); }
40
- .kagemusha-hint {
41
- position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
42
- color: #fff; background: rgba(0,0,0,0.7); padding: 6px 16px; border-radius: 8px;
43
- font-size: 12px; z-index: 999999; font-family: -apple-system, sans-serif;
44
- }
45
- </style>
46
- <span class="title">🥷 Annotation Editor</span>
47
- <button id="kg-tool-rect" class="active">▭ Rectangle</button>
48
- <button id="kg-tool-arrow">→ Arrow</button>
49
- <button id="kg-tool-label">T Label</button>
50
- <div class="sep"></div>
51
- <button id="kg-delete">🗑 Delete</button>
52
- <button class="save-btn" id="kg-save">💾 Save</button>
53
- `;
54
- document.body.appendChild(toolbar);
55
- document.body.style.paddingTop = `${TOOLBAR_HEIGHT}px`;
56
- const hint = document.createElement("div");
57
- hint.className = "kagemusha-hint";
58
- hint.textContent = "Click and drag to add annotations. Click to select. Press Delete to remove.";
59
- document.body.appendChild(hint);
60
- // --- SVG LAYER ---
61
- const svg = document.createElementNS(svgNS, "svg");
62
- svg.id = "kagemusha-svg-layer";
63
- svg.classList.add("drawing");
64
- document.body.appendChild(svg);
65
- const updateSvgSize = () => {
66
- svg.setAttribute("width", String(window.innerWidth));
67
- svg.setAttribute("height", String(document.documentElement.scrollHeight));
68
- };
69
- updateSvgSize();
70
- window.addEventListener("resize", updateSvgSize);
71
- const defs = document.createElementNS(svgNS, "defs");
72
- defs.innerHTML = '<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>';
73
- svg.appendChild(defs);
74
- // --- HELPERS ---
75
- const getPos = (e) => ({ x: e.pageX, y: e.pageY });
76
- const deselectAll = () => {
77
- selectedId = null;
78
- svg.querySelectorAll(".annotation").forEach((el) => { el.classList.remove("selected"); });
79
- };
80
- const selectEl = (el) => {
81
- deselectAll();
82
- selectedId = el.dataset.id ?? null;
83
- el.classList.add("selected");
84
- };
85
- const deleteSelected = () => {
86
- if (!selectedId)
87
- return;
88
- const el = svg.querySelector(`[data-id="${selectedId}"]`);
89
- if (el)
90
- el.remove();
91
- annotations = annotations.filter((a) => a.id !== selectedId);
92
- selectedId = null;
93
- };
94
- const startMove = (e, id) => {
95
- e.stopPropagation();
96
- const el = svg.querySelector(`[data-id="${id}"]`);
97
- if (!el)
98
- return;
99
- selectEl(el);
100
- const p = getPos(e);
101
- dragState = { type: "move", id, el: el, lastX: p.x, lastY: p.y };
102
- };
103
- const measureTextWidth = (text, fontSize) => {
104
- const tmp = document.createElementNS(svgNS, "text");
1
+ "use strict";
2
+ (() => {
3
+ // src/editor/inject-script/dom.ts
4
+ var getMousePos = (e) => ({
5
+ x: e.pageX,
6
+ y: e.pageY
7
+ });
8
+ var measureSvgTextWidth = (svg2, text, fontSize, svgNs) => {
9
+ const tmp = document.createElementNS(svgNs, "text");
105
10
  tmp.setAttribute("font-size", String(fontSize));
106
11
  tmp.setAttribute("font-family", "-apple-system, sans-serif");
107
12
  tmp.textContent = text;
108
- svg.appendChild(tmp);
13
+ svg2.appendChild(tmp);
109
14
  const width = tmp.getBBox().width;
110
15
  tmp.remove();
111
16
  return width;
112
- };
113
- const createLabelGroup = (id, x, y, text, fontSize = 14) => {
114
- const g = document.createElementNS(svgNS, "g");
17
+ };
18
+ var showErrorToast = (message, ms = 5e3) => {
19
+ const toast = document.createElement("div");
20
+ toast.setAttribute(
21
+ "style",
22
+ "position:fixed;top:72px;left:50%;transform:translateX(-50%);background:#dc2626;color:#fff;padding:12px 20px;border-radius:8px;font-family:-apple-system,sans-serif;font-size:14px;line-height:1.4;z-index:var(--kg-z-top);box-shadow:0 4px 12px rgba(0,0,0,0.3);max-width:480px;white-space:pre-wrap;text-align:center;"
23
+ );
24
+ toast.textContent = message;
25
+ document.documentElement.appendChild(toast);
26
+ setTimeout(() => toast.remove(), ms);
27
+ };
28
+
29
+ // src/editor/inject-script/state.ts
30
+ var SVG_NS = "http://www.w3.org/2000/svg";
31
+ var HANDLE_SIZE = 10;
32
+ var MIN_CROP = 10;
33
+ var state = {
34
+ tool: "rect",
35
+ annotations: [],
36
+ selectedId: null,
37
+ dragState: null,
38
+ nextId: 1,
39
+ captureMode: "fullPage",
40
+ captureCrop: null,
41
+ cropDragState: null,
42
+ recordedSteps: [],
43
+ recording: false,
44
+ pickerKind: null
45
+ };
46
+ var allocateAnnotationId = () => `a${state.nextId++}`;
47
+
48
+ // src/editor/inject-script/svg.ts
49
+ var svgEl = null;
50
+ var captureGroupEl = null;
51
+ var initSvgLayer = () => {
52
+ const svg2 = document.createElementNS(SVG_NS, "svg");
53
+ svg2.id = "kagemusha-svg-layer";
54
+ svg2.classList.add("drawing");
55
+ document.documentElement.appendChild(svg2);
56
+ const updateSvgSize = () => {
57
+ svg2.setAttribute("width", String(window.innerWidth));
58
+ svg2.setAttribute("height", String(document.documentElement.scrollHeight));
59
+ };
60
+ updateSvgSize();
61
+ window.addEventListener("resize", updateSvgSize);
62
+ const defs = document.createElementNS(SVG_NS, "defs");
63
+ defs.innerHTML = '<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>';
64
+ svg2.appendChild(defs);
65
+ const captureGroup = document.createElementNS(SVG_NS, "g");
66
+ captureGroup.id = "kagemusha-capture-group";
67
+ svg2.appendChild(captureGroup);
68
+ svgEl = svg2;
69
+ captureGroupEl = captureGroup;
70
+ return { svg: svg2, captureGroup };
71
+ };
72
+ var getSvg = () => {
73
+ if (!svgEl) throw new Error("SVG layer not initialized");
74
+ return svgEl;
75
+ };
76
+ var getCaptureGroup = () => {
77
+ if (!captureGroupEl) throw new Error("Capture group not initialized");
78
+ return captureGroupEl;
79
+ };
80
+
81
+ // src/editor/inject-script/annotations.ts
82
+ var isLabelInput = (el) => el?.tagName === "INPUT";
83
+ var deselectAll = () => {
84
+ state.selectedId = null;
85
+ getSvg().querySelectorAll(".annotation").forEach((el) => {
86
+ el.classList.remove("selected");
87
+ });
88
+ };
89
+ var selectEl = (el) => {
90
+ deselectAll();
91
+ state.selectedId = el.dataset.id ?? null;
92
+ el.classList.add("selected");
93
+ };
94
+ var deleteSelected = () => {
95
+ if (!state.selectedId) return;
96
+ const el = getSvg().querySelector(`[data-id="${state.selectedId}"]`);
97
+ if (el) el.remove();
98
+ state.annotations = state.annotations.filter(
99
+ (a) => a.id !== state.selectedId
100
+ );
101
+ state.selectedId = null;
102
+ };
103
+ var startMove = (e, id) => {
104
+ e.stopPropagation();
105
+ const el = getSvg().querySelector(`[data-id="${id}"]`);
106
+ if (!el) return;
107
+ selectEl(el);
108
+ const p = getMousePos(e);
109
+ state.dragState = {
110
+ type: "move",
111
+ id,
112
+ el,
113
+ lastX: p.x,
114
+ lastY: p.y
115
+ };
116
+ };
117
+ var createLabelGroup = (id, x, y, text, fontSize = 14) => {
118
+ const g = document.createElementNS(SVG_NS, "g");
115
119
  g.classList.add("annotation");
116
120
  g.dataset.id = id;
117
- const bg = document.createElementNS(svgNS, "rect");
118
- const txt = document.createElementNS(svgNS, "text");
121
+ const bg = document.createElementNS(SVG_NS, "rect");
122
+ const txt = document.createElementNS(SVG_NS, "text");
119
123
  txt.textContent = text;
120
124
  txt.setAttribute("x", String(x + 6));
121
125
  txt.setAttribute("y", String(y + 16));
122
126
  txt.setAttribute("fill", "#FF0000");
123
127
  txt.setAttribute("font-size", String(fontSize));
124
128
  txt.setAttribute("font-family", "-apple-system, sans-serif");
125
- const tw = measureTextWidth(text, fontSize) + 12;
129
+ const tw = measureSvgTextWidth(getSvg(), text, fontSize, SVG_NS) + 12;
126
130
  bg.setAttribute("x", String(x));
127
131
  bg.setAttribute("y", String(y));
128
132
  bg.setAttribute("width", String(tw));
@@ -132,263 +136,1182 @@ const createLabelGroup = (id, x, y, text, fontSize = 14) => {
132
136
  g.appendChild(bg);
133
137
  g.appendChild(txt);
134
138
  return g;
135
- };
136
- // --- TOOLS ---
137
- const setTool = (t) => {
138
- tool = t;
139
- document.querySelectorAll("#kagemusha-toolbar button").forEach((b) => { b.classList.remove("active"); });
140
- document.getElementById(`kg-tool-${t}`)?.classList.add("active");
141
- svg.classList.toggle("drawing", true);
139
+ };
140
+ var handleMouseDown = (e) => {
141
+ if (e.target?.closest(".annotation")) return false;
142
142
  deselectAll();
143
- };
144
- document.getElementById("kg-tool-rect")?.addEventListener("click", () => setTool("rect"));
145
- document.getElementById("kg-tool-arrow")?.addEventListener("click", () => setTool("arrow"));
146
- document.getElementById("kg-tool-label")?.addEventListener("click", () => setTool("label"));
147
- document.getElementById("kg-delete")?.addEventListener("click", deleteSelected);
148
- document.getElementById("kg-save")?.addEventListener("click", save);
149
- document.addEventListener("keydown", (e) => {
143
+ const p = getMousePos(e);
144
+ const svg2 = getSvg();
145
+ if (state.tool === "rect") {
146
+ const id = allocateAnnotationId();
147
+ const rect = document.createElementNS(SVG_NS, "rect");
148
+ rect.setAttribute("x", String(p.x));
149
+ rect.setAttribute("y", String(p.y));
150
+ rect.setAttribute("width", "0");
151
+ rect.setAttribute("height", "0");
152
+ rect.setAttribute("fill", "none");
153
+ rect.setAttribute("stroke", "#FF0000");
154
+ rect.setAttribute("stroke-width", "3");
155
+ rect.setAttribute("rx", "4");
156
+ rect.classList.add("annotation");
157
+ rect.dataset.id = id;
158
+ svg2.appendChild(rect);
159
+ state.dragState = {
160
+ type: "create-rect",
161
+ id,
162
+ el: rect,
163
+ sx: p.x,
164
+ sy: p.y
165
+ };
166
+ return true;
167
+ }
168
+ if (state.tool === "arrow") {
169
+ const id = allocateAnnotationId();
170
+ const line = document.createElementNS(SVG_NS, "line");
171
+ line.setAttribute("x1", String(p.x));
172
+ line.setAttribute("y1", String(p.y));
173
+ line.setAttribute("x2", String(p.x));
174
+ line.setAttribute("y2", String(p.y));
175
+ line.setAttribute("stroke", "#FF0000");
176
+ line.setAttribute("stroke-width", "3");
177
+ line.setAttribute("marker-end", "url(#kg-arrowhead)");
178
+ line.classList.add("annotation");
179
+ line.dataset.id = id;
180
+ svg2.appendChild(line);
181
+ state.dragState = {
182
+ type: "create-arrow",
183
+ id,
184
+ el: line,
185
+ sx: p.x,
186
+ sy: p.y
187
+ };
188
+ return true;
189
+ }
190
+ if (state.tool === "label") {
191
+ const id = allocateAnnotationId();
192
+ const input = document.createElement("input");
193
+ input.type = "text";
194
+ input.value = "";
195
+ input.placeholder = "Type label...";
196
+ input.style.cssText = "position:fixed;z-index:var(--kg-z-top);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);";
197
+ input.style.left = `${e.clientX}px`;
198
+ input.style.top = `${e.clientY}px`;
199
+ document.documentElement.appendChild(input);
200
+ svg2.classList.remove("drawing");
201
+ setTimeout(() => input.focus(), 50);
202
+ let labelFinished = false;
203
+ const finishLabel = () => {
204
+ if (labelFinished) return;
205
+ labelFinished = true;
206
+ const text = input.value.trim();
207
+ input.remove();
208
+ svg2.classList.add("drawing");
209
+ if (!text) return;
210
+ const g = createLabelGroup(id, p.x, p.y, text);
211
+ svg2.appendChild(g);
212
+ state.annotations.push({ id, type: "label", x: p.x, y: p.y, text });
213
+ selectEl(g);
214
+ g.addEventListener("mousedown", (ev) => startMove(ev, id));
215
+ };
216
+ input.addEventListener("keydown", (ev) => {
217
+ if (ev.key === "Enter") finishLabel();
218
+ if (ev.key === "Escape") {
219
+ labelFinished = true;
220
+ input.remove();
221
+ svg2.classList.add("drawing");
222
+ }
223
+ });
224
+ input.addEventListener("blur", finishLabel);
225
+ return true;
226
+ }
227
+ return false;
228
+ };
229
+ var handleMouseMove = (e) => {
230
+ const drag = state.dragState;
231
+ if (!drag) return false;
232
+ const p = getMousePos(e);
233
+ if (drag.type === "create-rect") {
234
+ drag.el.setAttribute("x", String(Math.min(drag.sx ?? 0, p.x)));
235
+ drag.el.setAttribute("y", String(Math.min(drag.sy ?? 0, p.y)));
236
+ drag.el.setAttribute("width", String(Math.abs(p.x - (drag.sx ?? 0))));
237
+ drag.el.setAttribute("height", String(Math.abs(p.y - (drag.sy ?? 0))));
238
+ } else if (drag.type === "create-arrow") {
239
+ drag.el.setAttribute("x2", String(p.x));
240
+ drag.el.setAttribute("y2", String(p.y));
241
+ } else if (drag.type === "move") {
242
+ const a = state.annotations.find((ann) => ann.id === drag.id);
243
+ if (!a) return true;
244
+ const dx = p.x - (drag.lastX ?? 0);
245
+ const dy = p.y - (drag.lastY ?? 0);
246
+ drag.lastX = p.x;
247
+ drag.lastY = p.y;
248
+ if (a.type === "rect") {
249
+ a.x = (a.x ?? 0) + dx;
250
+ a.y = (a.y ?? 0) + dy;
251
+ drag.el.setAttribute("x", String(a.x));
252
+ drag.el.setAttribute("y", String(a.y));
253
+ } else if (a.type === "arrow") {
254
+ a.fromX = (a.fromX ?? 0) + dx;
255
+ a.fromY = (a.fromY ?? 0) + dy;
256
+ a.toX = (a.toX ?? 0) + dx;
257
+ a.toY = (a.toY ?? 0) + dy;
258
+ drag.el.setAttribute("x1", String(a.fromX));
259
+ drag.el.setAttribute("y1", String(a.fromY));
260
+ drag.el.setAttribute("x2", String(a.toX));
261
+ drag.el.setAttribute("y2", String(a.toY));
262
+ } else if (a.type === "label") {
263
+ a.x = (a.x ?? 0) + dx;
264
+ a.y = (a.y ?? 0) + dy;
265
+ const bg = drag.el.querySelector("rect");
266
+ const txt = drag.el.querySelector("text");
267
+ bg?.setAttribute("x", String(a.x));
268
+ bg?.setAttribute("y", String(a.y));
269
+ txt?.setAttribute("x", String(a.x + 6));
270
+ txt?.setAttribute("y", String(a.y + 16));
271
+ }
272
+ }
273
+ return true;
274
+ };
275
+ var handleMouseUp = (e) => {
276
+ const drag = state.dragState;
277
+ if (!drag) return false;
278
+ const p = getMousePos(e);
279
+ if (drag.type === "create-rect") {
280
+ const w = Math.abs(p.x - (drag.sx ?? 0));
281
+ const h = Math.abs(p.y - (drag.sy ?? 0));
282
+ if (w < 5 && h < 5) {
283
+ drag.el.remove();
284
+ } else {
285
+ const a = {
286
+ id: drag.id,
287
+ type: "rect",
288
+ x: Math.min(drag.sx ?? 0, p.x),
289
+ y: Math.min(drag.sy ?? 0, p.y),
290
+ width: w,
291
+ height: h
292
+ };
293
+ state.annotations.push(a);
294
+ selectEl(drag.el);
295
+ const capturedId = drag.id;
296
+ drag.el.addEventListener(
297
+ "mousedown",
298
+ (ev) => startMove(ev, capturedId)
299
+ );
300
+ }
301
+ } else if (drag.type === "create-arrow") {
302
+ const dist = Math.hypot(p.x - (drag.sx ?? 0), p.y - (drag.sy ?? 0));
303
+ if (dist < 5) {
304
+ drag.el.remove();
305
+ } else {
306
+ const a = {
307
+ id: drag.id,
308
+ type: "arrow",
309
+ fromX: drag.sx,
310
+ fromY: drag.sy,
311
+ toX: p.x,
312
+ toY: p.y
313
+ };
314
+ state.annotations.push(a);
315
+ selectEl(drag.el);
316
+ const capturedId = drag.id;
317
+ drag.el.addEventListener(
318
+ "mousedown",
319
+ (ev) => startMove(ev, capturedId)
320
+ );
321
+ }
322
+ }
323
+ state.dragState = null;
324
+ return true;
325
+ };
326
+ var handleKeyDown = (e) => {
150
327
  if (e.key === "Delete" || e.key === "Backspace") {
151
- if (document.activeElement?.tagName === "INPUT")
152
- return;
153
- deleteSelected();
154
- }
155
- });
156
- // --- MOUSE: CREATE ---
157
- svg.addEventListener("mousedown", (e) => {
158
- if (e.target?.closest(".annotation"))
159
- return;
160
- const p = getPos(e);
161
- deselectAll();
162
- if (tool === "rect") {
163
- const id = `a${nextId++}`;
164
- const rect = document.createElementNS(svgNS, "rect");
165
- rect.setAttribute("x", String(p.x));
166
- rect.setAttribute("y", String(p.y));
167
- rect.setAttribute("width", "0");
168
- rect.setAttribute("height", "0");
328
+ if (isLabelInput(document.activeElement)) return;
329
+ deleteSelected();
330
+ }
331
+ };
332
+ var loadAnnotations = (decorations) => {
333
+ const dpr = window.devicePixelRatio || 1;
334
+ const svg2 = getSvg();
335
+ for (const d of decorations) {
336
+ const id = allocateAnnotationId();
337
+ if (d.type === "rect" && d.target) {
338
+ const rx = d.target.x / dpr;
339
+ const ry = d.target.y / dpr;
340
+ const rw = d.target.width / dpr;
341
+ const rh = d.target.height / dpr;
342
+ const rect = document.createElementNS(SVG_NS, "rect");
343
+ rect.setAttribute("x", String(rx));
344
+ rect.setAttribute("y", String(ry));
345
+ rect.setAttribute("width", String(rw));
346
+ rect.setAttribute("height", String(rh));
169
347
  rect.setAttribute("fill", "none");
170
- rect.setAttribute("stroke", "#FF0000");
348
+ rect.setAttribute("stroke", d.style?.color ?? "#FF0000");
171
349
  rect.setAttribute("stroke-width", "3");
172
350
  rect.setAttribute("rx", "4");
173
351
  rect.classList.add("annotation");
174
352
  rect.dataset.id = id;
175
- svg.appendChild(rect);
176
- dragState = { type: "create-rect", id, el: rect, sx: p.x, sy: p.y };
177
- }
178
- else if (tool === "arrow") {
179
- const id = `a${nextId++}`;
180
- const line = document.createElementNS(svgNS, "line");
181
- line.setAttribute("x1", String(p.x));
182
- line.setAttribute("y1", String(p.y));
183
- line.setAttribute("x2", String(p.x));
184
- line.setAttribute("y2", String(p.y));
185
- line.setAttribute("stroke", "#FF0000");
353
+ svg2.appendChild(rect);
354
+ state.annotations.push({
355
+ id,
356
+ type: "rect",
357
+ x: rx,
358
+ y: ry,
359
+ width: rw,
360
+ height: rh
361
+ });
362
+ rect.addEventListener("mousedown", (ev) => startMove(ev, id));
363
+ } else if (d.type === "arrow" && d.from && d.to) {
364
+ const ax1 = d.from.x / dpr;
365
+ const ay1 = d.from.y / dpr;
366
+ const ax2 = d.to.x / dpr;
367
+ const ay2 = d.to.y / dpr;
368
+ const line = document.createElementNS(SVG_NS, "line");
369
+ line.setAttribute("x1", String(ax1));
370
+ line.setAttribute("y1", String(ay1));
371
+ line.setAttribute("x2", String(ax2));
372
+ line.setAttribute("y2", String(ay2));
373
+ line.setAttribute("stroke", d.style?.color ?? "#FF0000");
186
374
  line.setAttribute("stroke-width", "3");
187
375
  line.setAttribute("marker-end", "url(#kg-arrowhead)");
188
376
  line.classList.add("annotation");
189
377
  line.dataset.id = id;
190
- svg.appendChild(line);
191
- dragState = { type: "create-arrow", id, el: line, sx: p.x, sy: p.y };
192
- }
193
- else if (tool === "label") {
194
- const id = `a${nextId++}`;
195
- const input = document.createElement("input");
196
- input.type = "text";
197
- input.value = "";
198
- input.placeholder = "Type label...";
199
- input.style.cssText = "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);";
200
- input.style.left = `${e.clientX}px`;
201
- input.style.top = `${e.clientY}px`;
202
- document.body.appendChild(input);
203
- svg.classList.remove("drawing");
204
- setTimeout(() => input.focus(), 50);
205
- let labelFinished = false;
206
- const finishLabel = () => {
207
- if (labelFinished)
208
- return;
209
- labelFinished = true;
210
- const text = input.value.trim();
211
- input.remove();
212
- svg.classList.add("drawing");
213
- if (!text)
214
- return;
215
- const g = createLabelGroup(id, p.x, p.y, text);
216
- svg.appendChild(g);
217
- annotations.push({ id, type: "label", x: p.x, y: p.y, text });
218
- selectEl(g);
219
- g.addEventListener("mousedown", (ev) => startMove(ev, id));
220
- };
221
- input.addEventListener("keydown", (ev) => {
222
- if (ev.key === "Enter")
223
- finishLabel();
224
- if (ev.key === "Escape") {
225
- labelFinished = true;
226
- input.remove();
227
- svg.classList.add("drawing");
228
- }
378
+ svg2.appendChild(line);
379
+ state.annotations.push({
380
+ id,
381
+ type: "arrow",
382
+ fromX: ax1,
383
+ fromY: ay1,
384
+ toX: ax2,
385
+ toY: ay2
229
386
  });
230
- input.addEventListener("blur", finishLabel);
231
- }
232
- });
233
- // --- MOUSE: DRAG ---
234
- document.addEventListener("mousemove", (e) => {
235
- if (!dragState)
236
- return;
237
- const p = getPos(e);
238
- if (dragState.type === "create-rect") {
239
- dragState.el.setAttribute("x", String(Math.min(dragState.sx ?? 0, p.x)));
240
- dragState.el.setAttribute("y", String(Math.min(dragState.sy ?? 0, p.y)));
241
- dragState.el.setAttribute("width", String(Math.abs(p.x - (dragState.sx ?? 0))));
242
- dragState.el.setAttribute("height", String(Math.abs(p.y - (dragState.sy ?? 0))));
243
- }
244
- else if (dragState.type === "create-arrow") {
245
- dragState.el.setAttribute("x2", String(p.x));
246
- dragState.el.setAttribute("y2", String(p.y));
247
- }
248
- else if (dragState.type === "move") {
249
- const a = annotations.find((ann) => ann.id === dragState?.id);
250
- if (!a)
251
- return;
252
- const dx = p.x - (dragState.lastX ?? 0);
253
- const dy = p.y - (dragState.lastY ?? 0);
254
- dragState.lastX = p.x;
255
- dragState.lastY = p.y;
256
- if (a.type === "rect") {
257
- a.x = (a.x ?? 0) + dx;
258
- a.y = (a.y ?? 0) + dy;
259
- dragState.el.setAttribute("x", String(a.x));
260
- dragState.el.setAttribute("y", String(a.y));
261
- }
262
- else if (a.type === "arrow") {
263
- a.fromX = (a.fromX ?? 0) + dx;
264
- a.fromY = (a.fromY ?? 0) + dy;
265
- a.toX = (a.toX ?? 0) + dx;
266
- a.toY = (a.toY ?? 0) + dy;
267
- dragState.el.setAttribute("x1", String(a.fromX));
268
- dragState.el.setAttribute("y1", String(a.fromY));
269
- dragState.el.setAttribute("x2", String(a.toX));
270
- dragState.el.setAttribute("y2", String(a.toY));
271
- }
272
- else if (a.type === "label") {
273
- a.x = (a.x ?? 0) + dx;
274
- a.y = (a.y ?? 0) + dy;
275
- const bg = dragState.el.querySelector("rect");
276
- const txt = dragState.el.querySelector("text");
277
- bg?.setAttribute("x", String(a.x));
278
- bg?.setAttribute("y", String(a.y));
279
- txt?.setAttribute("x", String(a.x + 6));
280
- txt?.setAttribute("y", String(a.y + 16));
281
- }
387
+ line.addEventListener("mousedown", (ev) => startMove(ev, id));
388
+ } else if (d.type === "label" && d.position) {
389
+ const lx = d.position.x / dpr;
390
+ const ly = d.position.y / dpr;
391
+ const fontSize = (d.style?.fontSize ?? 14) / dpr;
392
+ const g = createLabelGroup(id, lx, ly, d.text ?? "", fontSize);
393
+ svg2.appendChild(g);
394
+ state.annotations.push({
395
+ id,
396
+ type: "label",
397
+ x: lx,
398
+ y: ly,
399
+ text: d.text
400
+ });
401
+ g.addEventListener("mousedown", (ev) => startMove(ev, id));
402
+ }
282
403
  }
283
- });
284
- // --- MOUSE: UP ---
285
- document.addEventListener("mouseup", (e) => {
286
- if (!dragState)
287
- return;
288
- const p = getPos(e);
289
- if (dragState.type === "create-rect") {
290
- const w = Math.abs(p.x - (dragState.sx ?? 0));
291
- const h = Math.abs(p.y - (dragState.sy ?? 0));
292
- if (w < 5 && h < 5) {
293
- dragState.el.remove();
294
- }
295
- else {
296
- const a = { id: dragState.id, type: "rect", x: Math.min(dragState.sx ?? 0, p.x), y: Math.min(dragState.sy ?? 0, p.y), width: w, height: h };
297
- annotations.push(a);
298
- selectEl(dragState.el);
299
- const capturedId = dragState.id;
300
- dragState.el.addEventListener("mousedown", (ev) => startMove(ev, capturedId));
301
- }
404
+ };
405
+ var serializeAnnotations = () => {
406
+ const dpr = window.devicePixelRatio || 1;
407
+ const s = Math.round;
408
+ return state.annotations.map((a) => {
409
+ if (a.type === "rect") {
410
+ return {
411
+ type: "rect",
412
+ target: {
413
+ x: s((a.x ?? 0) * dpr),
414
+ y: s((a.y ?? 0) * dpr),
415
+ width: s((a.width ?? 0) * dpr),
416
+ height: s((a.height ?? 0) * dpr)
417
+ },
418
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) }
419
+ };
420
+ }
421
+ if (a.type === "arrow") {
422
+ return {
423
+ type: "arrow",
424
+ from: {
425
+ x: s((a.fromX ?? 0) * dpr),
426
+ y: s((a.fromY ?? 0) * dpr)
427
+ },
428
+ to: {
429
+ x: s((a.toX ?? 0) * dpr),
430
+ y: s((a.toY ?? 0) * dpr)
431
+ },
432
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) }
433
+ };
434
+ }
435
+ if (a.type === "label") {
436
+ return {
437
+ type: "label",
438
+ text: a.text,
439
+ position: {
440
+ x: s((a.x ?? 0) * dpr),
441
+ y: s((a.y ?? 0) * dpr)
442
+ },
443
+ style: {
444
+ fontSize: s(14 * dpr),
445
+ color: "#FF0000",
446
+ background: "#FFFFFF"
447
+ }
448
+ };
449
+ }
450
+ return null;
451
+ }).filter((d) => d !== null);
452
+ };
453
+
454
+ // src/editor/inject-script/toolbar.ts
455
+ var TOOLBAR_HTML = `
456
+ <style>
457
+ /* INT_MAX guarantees kagemusha UI sits above any host SPA dialog / modal. */
458
+ :root {
459
+ --kg-z-top: 2147483647;
460
+ --kg-z-below-top: 2147483646;
302
461
  }
303
- else if (dragState.type === "create-arrow") {
304
- const dist = Math.hypot(p.x - (dragState.sx ?? 0), p.y - (dragState.sy ?? 0));
305
- if (dist < 5) {
306
- dragState.el.remove();
307
- }
308
- else {
309
- const a = { id: dragState.id, type: "arrow", fromX: dragState.sx, fromY: dragState.sy, toX: p.x, toY: p.y };
310
- annotations.push(a);
311
- selectEl(dragState.el);
312
- const capturedId = dragState.id;
313
- dragState.el.addEventListener("mousedown", (ev) => startMove(ev, capturedId));
314
- }
462
+ #kagemusha-toolbar {
463
+ position: fixed; top: 16px; left: 16px; z-index: var(--kg-z-top);
464
+ background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 10px;
465
+ box-shadow: 0 4px 16px rgba(0,0,0,0.35); font-family: -apple-system, sans-serif;
466
+ flex-wrap: nowrap; overflow-x: auto;
467
+ border-radius: 10px;
468
+ max-width: calc(100vw - 32px);
315
469
  }
316
- dragState = null;
317
- });
318
- // --- LOAD EXISTING ---
319
- _win.__kagemusha_loadAnnotations = (decorations) => {
320
- const dpr = _win.__kagemusha_dpr || 1;
321
- for (const d of decorations) {
322
- const id = `a${nextId++}`;
323
- if (d.type === "rect" && d.target) {
324
- const rx = d.target.x / dpr;
325
- const ry = d.target.y / dpr + TOOLBAR_HEIGHT;
326
- const rw = d.target.width / dpr;
327
- const rh = d.target.height / dpr;
328
- const rect = document.createElementNS(svgNS, "rect");
329
- rect.setAttribute("x", String(rx));
330
- rect.setAttribute("y", String(ry));
331
- rect.setAttribute("width", String(rw));
332
- rect.setAttribute("height", String(rh));
333
- rect.setAttribute("fill", "none");
334
- rect.setAttribute("stroke", d.style?.color ?? "#FF0000");
335
- rect.setAttribute("stroke-width", "3");
336
- rect.setAttribute("rx", "4");
337
- rect.classList.add("annotation");
338
- rect.dataset.id = id;
339
- svg.appendChild(rect);
340
- annotations.push({ id, type: "rect", x: rx, y: ry, width: rw, height: rh });
341
- rect.addEventListener("mousedown", (ev) => startMove(ev, id));
342
- }
343
- else if (d.type === "arrow" && d.from && d.to) {
344
- const ax1 = d.from.x / dpr;
345
- const ay1 = d.from.y / dpr + TOOLBAR_HEIGHT;
346
- const ax2 = d.to.x / dpr;
347
- const ay2 = d.to.y / dpr + TOOLBAR_HEIGHT;
348
- const line = document.createElementNS(svgNS, "line");
349
- line.setAttribute("x1", String(ax1));
350
- line.setAttribute("y1", String(ay1));
351
- line.setAttribute("x2", String(ax2));
352
- line.setAttribute("y2", String(ay2));
353
- line.setAttribute("stroke", d.style?.color ?? "#FF0000");
354
- line.setAttribute("stroke-width", "3");
355
- line.setAttribute("marker-end", "url(#kg-arrowhead)");
356
- line.classList.add("annotation");
357
- line.dataset.id = id;
358
- svg.appendChild(line);
359
- annotations.push({ id, type: "arrow", fromX: ax1, fromY: ay1, toX: ax2, toY: ay2 });
360
- line.addEventListener("mousedown", (ev) => startMove(ev, id));
361
- }
362
- else if (d.type === "label" && d.position) {
363
- const lx = d.position.x / dpr;
364
- const ly = d.position.y / dpr + TOOLBAR_HEIGHT;
365
- const fontSize = (d.style?.fontSize ?? 14) / dpr;
366
- const g = createLabelGroup(id, lx, ly, d.text ?? "", fontSize);
367
- svg.appendChild(g);
368
- annotations.push({ id, type: "label", x: lx, y: ly, text: d.text });
369
- g.addEventListener("mousedown", (ev) => startMove(ev, id));
370
- }
470
+ #kagemusha-toolbar .kg-drag-handle {
471
+ cursor: move; padding: 4px 6px; margin: -2px 2px -2px -10px;
472
+ color: #888; font-size: 14px; line-height: 1; user-select: none;
473
+ border-right: 1px solid #2a2a4e;
371
474
  }
372
- };
373
- // --- SAVE ---
374
- function save() {
375
- const dpr = _win.__kagemusha_dpr || 1;
376
- const s = Math.round;
377
- const decorations = annotations
378
- .map((a) => {
379
- if (a.type === "rect") {
380
- return { type: "rect", target: { x: s((a.x ?? 0) * dpr), y: s(((a.y ?? 0) - TOOLBAR_HEIGHT) * dpr), width: s((a.width ?? 0) * dpr), height: s((a.height ?? 0) * dpr) }, style: { color: "#FF0000", strokeWidth: s(3 * dpr) } };
381
- }
382
- if (a.type === "arrow") {
383
- return { type: "arrow", from: { x: s((a.fromX ?? 0) * dpr), y: s(((a.fromY ?? 0) - TOOLBAR_HEIGHT) * dpr) }, to: { x: s((a.toX ?? 0) * dpr), y: s(((a.toY ?? 0) - TOOLBAR_HEIGHT) * dpr) }, style: { color: "#FF0000", strokeWidth: s(3 * dpr) } };
475
+ #kagemusha-toolbar .kg-drag-handle:hover { color: #fff; }
476
+ #kagemusha-toolbar.kg-dragging { opacity: 0.95; cursor: grabbing; }
477
+ #kagemusha-toolbar button {
478
+ padding: 6px 12px; border: 1px solid #444; border-radius: 6px;
479
+ background: #1a1a2e; color: #fff; font-size: 13px; cursor: pointer;
480
+ }
481
+ #kagemusha-toolbar button:hover { background: #2a2a4e; }
482
+ #kagemusha-toolbar button.active { background: #6366f1; border-color: #6366f1; }
483
+ #kagemusha-toolbar button.cap-btn.active { background: #0ea5e9; border-color: #0ea5e9; }
484
+ #kagemusha-toolbar button#kg-record.active { background: #ef4444; border-color: #ef4444; }
485
+ #kagemusha-toolbar button.picking {
486
+ background: #0ea5e9; border-color: #0ea5e9; color: #fff;
487
+ box-shadow: 0 0 0 2px rgba(14,165,233,0.4);
488
+ animation: kg-picking-pulse 1.2s ease-in-out infinite;
489
+ }
490
+ @keyframes kg-picking-pulse {
491
+ 0%, 100% { box-shadow: 0 0 0 2px rgba(14,165,233,0.4); }
492
+ 50% { box-shadow: 0 0 0 6px rgba(14,165,233,0.1); }
493
+ }
494
+ #kagemusha-toolbar button:disabled { opacity: 0.4; cursor: not-allowed; }
495
+ #kagemusha-toolbar #kg-rec-group { display: none; align-items: center; gap: 10px; }
496
+ #kagemusha-toolbar #kg-rec-group.visible { display: flex; }
497
+ #kagemusha-toolbar #kg-steps-toggle.has-steps { background: #6366f1; border-color: #6366f1; }
498
+ #kagemusha-toolbar #kg-steps-toggle.open { background: #6366f1; border-color: #6366f1; }
499
+ .kagemusha-picker-outline {
500
+ position: fixed; border: 2px solid #0ea5e9;
501
+ background: rgba(14,165,233,0.08); pointer-events: none;
502
+ z-index: var(--kg-z-below-top); border-radius: 2px;
503
+ transition: all 60ms ease-out;
504
+ }
505
+ #kagemusha-toolbar .sep { width: 1px; height: 24px; background: #444; }
506
+ #kagemusha-toolbar .title { color: #888; font-size: 13px; }
507
+ #kagemusha-toolbar .group-label { color: #7a89b0; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; margin-right: -4px; }
508
+ #kagemusha-toolbar .save-btn { background: #22c55e; border-color: #22c55e; font-weight: 600; margin-left: auto; }
509
+ #kagemusha-toolbar .save-btn:hover { background: #16a34a; }
510
+ #kagemusha-svg-layer {
511
+ position: absolute; top: 0; left: 0; width: 100%;
512
+ z-index: var(--kg-z-below-top); pointer-events: none;
513
+ }
514
+ #kagemusha-svg-layer.drawing { pointer-events: auto; cursor: crosshair; }
515
+ #kagemusha-svg-layer.cropping { pointer-events: auto; cursor: crosshair; }
516
+ #kagemusha-svg-layer .annotation { pointer-events: auto; cursor: move; }
517
+ #kagemusha-svg-layer.cropping .annotation { pointer-events: none; }
518
+ #kagemusha-svg-layer .annotation.selected { filter: drop-shadow(0 0 3px #6366f1); }
519
+ #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; }
520
+ #kagemusha-svg-layer.cropping .capture-crop-box { pointer-events: auto; cursor: move; }
521
+ #kagemusha-svg-layer .crop-handle { fill: #fff; stroke: #0ea5e9; stroke-width: 2; pointer-events: none; }
522
+ #kagemusha-svg-layer.cropping .crop-handle { pointer-events: auto; }
523
+ #kagemusha-svg-layer .crop-handle.nw, #kagemusha-svg-layer .crop-handle.se { cursor: nwse-resize; }
524
+ #kagemusha-svg-layer .crop-handle.ne, #kagemusha-svg-layer .crop-handle.sw { cursor: nesw-resize; }
525
+ #kagemusha-svg-layer .crop-handle.n, #kagemusha-svg-layer .crop-handle.s { cursor: ns-resize; }
526
+ #kagemusha-svg-layer .crop-handle.e, #kagemusha-svg-layer .crop-handle.w { cursor: ew-resize; }
527
+ .kagemusha-hint {
528
+ position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
529
+ color: #fff; background: rgba(0,0,0,0.7); padding: 6px 16px; border-radius: 8px;
530
+ font-size: 12px; z-index: var(--kg-z-top); font-family: -apple-system, sans-serif;
531
+ }
532
+ </style>
533
+ <span class="kg-drag-handle" title="Drag to move toolbar">\u22EE\u22EE</span>
534
+ <span class="title">\u{1F977}</span>
535
+ <div class="sep"></div>
536
+ <span class="group-label">Capture</span>
537
+ <button id="kg-cap-full" class="cap-btn active">\u{1F4F7} Full</button>
538
+ <button id="kg-cap-crop" class="cap-btn">\u2702\uFE0F Crop</button>
539
+ <div class="sep"></div>
540
+ <span class="group-label">Annotate</span>
541
+ <button id="kg-tool-rect" class="active">\u25AD Rect</button>
542
+ <button id="kg-tool-arrow">\u2192 Arrow</button>
543
+ <button id="kg-tool-label">T Label</button>
544
+ <div class="sep"></div>
545
+ <span class="group-label">Pre-steps</span>
546
+ <button id="kg-record">\u{1F534} Record</button>
547
+ <div id="kg-rec-group">
548
+ <button id="kg-rec-wait">+ Wait</button>
549
+ <button id="kg-rec-wfs">+ WaitForSelector</button>
550
+ <button id="kg-rec-hover">+ Hover</button>
551
+ </div>
552
+ <button id="kg-steps-toggle">\u{1F4CB} Steps (0)</button>
553
+ <div class="sep"></div>
554
+ <button id="kg-delete">\u{1F5D1} Delete</button>
555
+ <button class="save-btn" id="kg-save">\u{1F4BE} Save</button>
556
+ `;
557
+ var svgRef = null;
558
+ var cropOnEnter = () => {
559
+ };
560
+ var cropOnExit = () => {
561
+ };
562
+ var callbacks = null;
563
+ var initToolbar = (cb, svg2) => {
564
+ callbacks = cb;
565
+ svgRef = svg2;
566
+ const toolbar = document.createElement("div");
567
+ toolbar.id = "kagemusha-toolbar";
568
+ toolbar.innerHTML = TOOLBAR_HTML;
569
+ document.documentElement.appendChild(toolbar);
570
+ const hint = document.createElement("div");
571
+ hint.className = "kagemusha-hint";
572
+ hint.textContent = "Click and drag to add annotations. Click to select. Press Delete to remove.";
573
+ document.documentElement.appendChild(hint);
574
+ document.getElementById("kg-tool-rect")?.addEventListener("click", () => setTool("rect"));
575
+ document.getElementById("kg-tool-arrow")?.addEventListener("click", () => setTool("arrow"));
576
+ document.getElementById("kg-tool-label")?.addEventListener("click", () => setTool("label"));
577
+ document.getElementById("kg-cap-full")?.addEventListener("click", () => setCaptureMode("fullPage"));
578
+ document.getElementById("kg-cap-crop")?.addEventListener(
579
+ "click",
580
+ () => setCaptureMode("crop", { resetSelection: !state.captureCrop })
581
+ );
582
+ document.getElementById("kg-delete")?.addEventListener("click", () => cb.onDelete());
583
+ document.getElementById("kg-save")?.addEventListener("click", () => cb.onSave());
584
+ wireDragHandle(toolbar);
585
+ };
586
+ var wireDragHandle = (toolbar) => {
587
+ const handle = toolbar.querySelector(".kg-drag-handle");
588
+ if (!handle) return;
589
+ let dragging = false;
590
+ let offsetX = 0;
591
+ let offsetY = 0;
592
+ handle.addEventListener("mousedown", (e) => {
593
+ const m = e;
594
+ m.preventDefault();
595
+ const rect = toolbar.getBoundingClientRect();
596
+ offsetX = m.clientX - rect.left;
597
+ offsetY = m.clientY - rect.top;
598
+ dragging = true;
599
+ toolbar.classList.add("kg-dragging");
600
+ document.body.style.userSelect = "none";
601
+ });
602
+ document.addEventListener("mousemove", (e) => {
603
+ if (!dragging) return;
604
+ const x = Math.max(
605
+ 0,
606
+ Math.min(window.innerWidth - toolbar.offsetWidth, e.clientX - offsetX)
607
+ );
608
+ const y = Math.max(
609
+ 0,
610
+ Math.min(window.innerHeight - toolbar.offsetHeight, e.clientY - offsetY)
611
+ );
612
+ toolbar.style.left = `${x}px`;
613
+ toolbar.style.top = `${y}px`;
614
+ });
615
+ document.addEventListener("mouseup", () => {
616
+ if (!dragging) return;
617
+ dragging = false;
618
+ toolbar.classList.remove("kg-dragging");
619
+ document.body.style.userSelect = "";
620
+ });
621
+ };
622
+ var updateCaptureUi = () => {
623
+ document.querySelectorAll("#kagemusha-toolbar .cap-btn").forEach((b) => {
624
+ b.classList.remove("active");
625
+ });
626
+ const activeId = state.captureMode === "fullPage" ? "kg-cap-full" : "kg-cap-crop";
627
+ document.getElementById(activeId)?.classList.add("active");
628
+ if (svgRef) {
629
+ svgRef.classList.toggle("cropping", state.tool === "crop");
630
+ svgRef.classList.toggle(
631
+ "drawing",
632
+ state.tool === "rect" || state.tool === "arrow" || state.tool === "label"
633
+ );
634
+ }
635
+ };
636
+ var setTool = (t) => {
637
+ state.tool = t;
638
+ document.querySelectorAll(
639
+ "#kg-tool-rect, #kg-tool-arrow, #kg-tool-label"
640
+ ).forEach((b) => {
641
+ b.classList.remove("active");
642
+ });
643
+ if (t === "rect" || t === "arrow" || t === "label") {
644
+ document.getElementById(`kg-tool-${t}`)?.classList.add("active");
645
+ }
646
+ updateCaptureUi();
647
+ callbacks?.onToolChange(t);
648
+ };
649
+ var setCaptureMode = (mode, opts = {}) => {
650
+ state.captureMode = mode;
651
+ if (mode === "fullPage") {
652
+ state.captureCrop = null;
653
+ cropOnExit();
654
+ setTool("rect");
655
+ } else {
656
+ if (opts.resetSelection) state.captureCrop = null;
657
+ cropOnEnter();
658
+ state.tool = "crop";
659
+ updateCaptureUi();
660
+ }
661
+ callbacks?.onToolChange(state.tool);
662
+ };
663
+ var registerCropHooks = (onEnter, onExit) => {
664
+ cropOnEnter = onEnter;
665
+ cropOnExit = onExit;
666
+ };
667
+
668
+ // src/editor/inject-script/crop.ts
669
+ var HANDLE_POSITIONS = [
670
+ { handle: "nw", pos: (c) => ({ cx: c.x, cy: c.y }) },
671
+ { handle: "n", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y }) },
672
+ { handle: "ne", pos: (c) => ({ cx: c.x + c.w, cy: c.y }) },
673
+ { handle: "e", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h / 2 }) },
674
+ { handle: "se", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h }) },
675
+ { handle: "s", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y + c.h }) },
676
+ { handle: "sw", pos: (c) => ({ cx: c.x, cy: c.y + c.h }) },
677
+ { handle: "w", pos: (c) => ({ cx: c.x, cy: c.y + c.h / 2 }) }
678
+ ];
679
+ var clearVisual = () => {
680
+ getCaptureGroup().replaceChildren();
681
+ };
682
+ var redrawVisual = () => {
683
+ if (state.captureMode !== "crop" || !state.captureCrop) {
684
+ clearVisual();
685
+ return;
686
+ }
687
+ clearVisual();
688
+ const g = getCaptureGroup();
689
+ const c = state.captureCrop;
690
+ const r = document.createElementNS(SVG_NS, "rect");
691
+ r.setAttribute("x", String(c.x));
692
+ r.setAttribute("y", String(c.y));
693
+ r.setAttribute("width", String(c.w));
694
+ r.setAttribute("height", String(c.h));
695
+ r.setAttribute("class", "capture-crop-box");
696
+ g.appendChild(r);
697
+ for (const { handle, pos } of HANDLE_POSITIONS) {
698
+ const { cx, cy } = pos(c);
699
+ const h = document.createElementNS(SVG_NS, "rect");
700
+ h.setAttribute("x", String(cx - HANDLE_SIZE / 2));
701
+ h.setAttribute("y", String(cy - HANDLE_SIZE / 2));
702
+ h.setAttribute("width", String(HANDLE_SIZE));
703
+ h.setAttribute("height", String(HANDLE_SIZE));
704
+ h.setAttribute("class", `crop-handle ${handle}`);
705
+ h.dataset.handle = handle;
706
+ g.appendChild(h);
707
+ }
708
+ };
709
+ var initCrop = () => {
710
+ registerCropHooks(
711
+ // onEnter: redraw existing selection (if any)
712
+ () => redrawVisual(),
713
+ // onExit: clear visualization
714
+ () => clearVisual()
715
+ );
716
+ };
717
+ var handleMouseDown2 = (e) => {
718
+ if (state.tool !== "crop") return false;
719
+ e.preventDefault();
720
+ const p = getMousePos(e);
721
+ const target = e.target;
722
+ const handleAttr = target.dataset?.handle;
723
+ if (handleAttr && state.captureCrop) {
724
+ state.cropDragState = {
725
+ kind: "resize",
726
+ handle: handleAttr,
727
+ sx: p.x,
728
+ sy: p.y,
729
+ orig: { ...state.captureCrop }
730
+ };
731
+ return true;
732
+ }
733
+ if (target?.classList?.contains("capture-crop-box") && state.captureCrop) {
734
+ state.cropDragState = {
735
+ kind: "move",
736
+ sx: p.x,
737
+ sy: p.y,
738
+ orig: { ...state.captureCrop }
739
+ };
740
+ return true;
741
+ }
742
+ state.cropDragState = { kind: "create", sx: p.x, sy: p.y };
743
+ state.captureCrop = null;
744
+ clearVisual();
745
+ const r = document.createElementNS(SVG_NS, "rect");
746
+ r.setAttribute("x", String(p.x));
747
+ r.setAttribute("y", String(p.y));
748
+ r.setAttribute("width", "0");
749
+ r.setAttribute("height", "0");
750
+ r.setAttribute("class", "capture-crop-box");
751
+ getCaptureGroup().appendChild(r);
752
+ return true;
753
+ };
754
+ var handleMouseMove2 = (e) => {
755
+ const cds = state.cropDragState;
756
+ if (!cds) return false;
757
+ const p = getMousePos(e);
758
+ if (cds.kind === "create") {
759
+ const x = Math.min(cds.sx, p.x);
760
+ const y = Math.min(cds.sy, p.y);
761
+ const w = Math.abs(p.x - cds.sx);
762
+ const h2 = Math.abs(p.y - cds.sy);
763
+ const r = getCaptureGroup().firstChild;
764
+ if (r) {
765
+ r.setAttribute("x", String(x));
766
+ r.setAttribute("y", String(y));
767
+ r.setAttribute("width", String(w));
768
+ r.setAttribute("height", String(h2));
769
+ }
770
+ return true;
771
+ }
772
+ if (cds.kind === "move") {
773
+ const dx2 = p.x - cds.sx;
774
+ const dy2 = p.y - cds.sy;
775
+ state.captureCrop = {
776
+ x: cds.orig.x + dx2,
777
+ y: cds.orig.y + dy2,
778
+ w: cds.orig.w,
779
+ h: cds.orig.h
780
+ };
781
+ redrawVisual();
782
+ return true;
783
+ }
784
+ const dx = p.x - cds.sx;
785
+ const dy = p.y - cds.sy;
786
+ const o = cds.orig;
787
+ let nx = o.x;
788
+ let ny = o.y;
789
+ let nw = o.w;
790
+ let nh = o.h;
791
+ const h = cds.handle;
792
+ if (h.includes("w")) {
793
+ nx = o.x + dx;
794
+ nw = o.w - dx;
795
+ }
796
+ if (h.includes("e")) {
797
+ nw = o.w + dx;
798
+ }
799
+ if (h.includes("n")) {
800
+ ny = o.y + dy;
801
+ nh = o.h - dy;
802
+ }
803
+ if (h.includes("s")) {
804
+ nh = o.h + dy;
805
+ }
806
+ if (nw < MIN_CROP) {
807
+ if (h.includes("w")) nx = o.x + (o.w - MIN_CROP);
808
+ nw = MIN_CROP;
809
+ }
810
+ if (nh < MIN_CROP) {
811
+ if (h.includes("n")) ny = o.y + (o.h - MIN_CROP);
812
+ nh = MIN_CROP;
813
+ }
814
+ state.captureCrop = { x: nx, y: ny, w: nw, h: nh };
815
+ redrawVisual();
816
+ return true;
817
+ };
818
+ var handleMouseUp2 = (e) => {
819
+ const cds = state.cropDragState;
820
+ if (!cds) return false;
821
+ if (cds.kind === "create") {
822
+ const p = getMousePos(e);
823
+ const w = Math.abs(p.x - cds.sx);
824
+ const h = Math.abs(p.y - cds.sy);
825
+ if (w < 5 || h < 5) {
826
+ state.cropDragState = null;
827
+ redrawVisual();
828
+ return true;
829
+ }
830
+ state.captureCrop = {
831
+ x: Math.min(cds.sx, p.x),
832
+ y: Math.min(cds.sy, p.y),
833
+ w,
834
+ h
835
+ };
836
+ }
837
+ state.cropDragState = null;
838
+ redrawVisual();
839
+ return true;
840
+ };
841
+ var loadCapture = (capture, setMode) => {
842
+ if (!capture || !["fullPage", "crop"].includes(capture.mode)) {
843
+ setMode("fullPage");
844
+ return;
845
+ }
846
+ if (capture.mode === "fullPage") {
847
+ setMode("fullPage");
848
+ return;
849
+ }
850
+ if (capture.mode === "crop") {
851
+ const sx = capture.crop.start.x;
852
+ const sy = capture.crop.start.y;
853
+ const ex = capture.crop.end.x;
854
+ const ey = capture.crop.end.y;
855
+ state.captureCrop = { x: sx, y: sy, w: ex - sx, h: ey - sy };
856
+ state.captureMode = "crop";
857
+ setMode("crop");
858
+ redrawVisual();
859
+ }
860
+ };
861
+ var serializeCapture = () => {
862
+ if (state.captureMode === "crop" && state.captureCrop) {
863
+ const c = state.captureCrop;
864
+ return {
865
+ mode: "crop",
866
+ crop: {
867
+ start: { x: Math.round(c.x), y: Math.round(c.y) },
868
+ end: {
869
+ x: Math.round(c.x + c.w),
870
+ y: Math.round(c.y + c.h)
871
+ }
384
872
  }
385
- if (a.type === "label") {
386
- return { type: "label", text: a.text, position: { x: s((a.x ?? 0) * dpr), y: s(((a.y ?? 0) - TOOLBAR_HEIGHT) * dpr) }, style: { fontSize: s(14 * dpr), color: "#FF0000", background: "#FFFFFF" } };
873
+ };
874
+ }
875
+ return { mode: "fullPage" };
876
+ };
877
+
878
+ // src/editor/inject-script/selector.ts
879
+ var escapeQuotes = (s) => s.replace(/"/g, '\\"');
880
+ var inferRole = (el) => {
881
+ const tag = el.tagName;
882
+ if (tag === "BUTTON") return "button";
883
+ if (tag === "A" && el.hasAttribute("href")) return "link";
884
+ if (tag === "INPUT") {
885
+ const type = el.type;
886
+ if (type === "button" || type === "submit") return "button";
887
+ if (type === "checkbox") return "checkbox";
888
+ if (type === "radio") return "radio";
889
+ }
890
+ return null;
891
+ };
892
+ var isInteractiveAncestor = (el) => {
893
+ let cur = el;
894
+ while (cur) {
895
+ if (cur.tagName === "BUTTON" || cur.tagName === "A" || cur.getAttribute("role") === "button" || cur.getAttribute("role") === "link" || cur.hasAttribute("data-testid")) {
896
+ return cur;
897
+ }
898
+ cur = cur.parentElement;
899
+ }
900
+ return null;
901
+ };
902
+ var cssPath = (el) => {
903
+ const parts = [];
904
+ let cur = el;
905
+ let depth = 0;
906
+ while (cur && depth < 4 && cur !== document.body) {
907
+ const node = cur;
908
+ const tag = node.tagName;
909
+ let segment = tag.toLowerCase();
910
+ const id = node.id;
911
+ if (id && /^[a-zA-Z][\w-]*$/.test(id)) {
912
+ parts.unshift(`#${id}`);
913
+ break;
914
+ }
915
+ const parent = node.parentElement;
916
+ if (parent) {
917
+ const siblings = Array.from(parent.children).filter(
918
+ (c) => c.tagName === tag
919
+ );
920
+ if (siblings.length > 1) {
921
+ const idx = siblings.indexOf(node) + 1;
922
+ segment += `:nth-of-type(${idx})`;
387
923
  }
388
- return null;
389
- })
390
- .filter((d) => d !== null);
391
- _win.__kagemusha_save(JSON.stringify(decorations));
392
- }
393
- export {};
394
- //# sourceMappingURL=inject-script.js.map
924
+ }
925
+ parts.unshift(segment);
926
+ cur = parent;
927
+ depth++;
928
+ }
929
+ return parts.join(" > ");
930
+ };
931
+ var computeSelector = (raw) => {
932
+ const el = isInteractiveAncestor(raw) ?? raw;
933
+ const testId = el.getAttribute("data-testid");
934
+ if (testId) {
935
+ return {
936
+ selector: `[data-testid="${escapeQuotes(testId)}"]`,
937
+ quality: "good"
938
+ };
939
+ }
940
+ const ariaLabel = el.getAttribute("aria-label");
941
+ if (ariaLabel) {
942
+ return {
943
+ selector: `[aria-label="${escapeQuotes(ariaLabel)}"]`,
944
+ quality: "good"
945
+ };
946
+ }
947
+ const role = el.getAttribute("role") ?? inferRole(el);
948
+ const text = el.textContent?.trim();
949
+ if (role && text && text.length > 0 && text.length < 50) {
950
+ return { selector: `text="${escapeQuotes(text)}"`, quality: "good" };
951
+ }
952
+ if (text && text.length > 0 && text.length < 50 && !/\n/.test(text)) {
953
+ return { selector: `text="${escapeQuotes(text)}"`, quality: "good" };
954
+ }
955
+ return { selector: cssPath(el), quality: "fallback" };
956
+ };
957
+
958
+ // src/editor/inject-script/record.ts
959
+ var KAGEMUSHA_UI_SELECTORS = [
960
+ "#kagemusha-toolbar",
961
+ "#kagemusha-svg-layer",
962
+ ".kagemusha-hint",
963
+ ".kagemusha-steps-panel",
964
+ ".kagemusha-prompt"
965
+ ];
966
+ var isOwnUi = (el) => {
967
+ if (!(el instanceof Element)) return false;
968
+ return KAGEMUSHA_UI_SELECTORS.some((s) => el.closest(s) !== null);
969
+ };
970
+ var svgRef2 = null;
971
+ var panelEl = null;
972
+ var pickerOutlineEl = null;
973
+ var panelOpen = false;
974
+ var ensurePanel = () => {
975
+ if (panelEl) return panelEl;
976
+ const div = document.createElement("div");
977
+ div.className = "kagemusha-steps-panel";
978
+ div.setAttribute(
979
+ "style",
980
+ // Anchored just below the toolbar (top: 60px) so it's visible without
981
+ // scrolling. Fills available vertical space minus a small bottom margin.
982
+ "position:fixed;top:60px;right:16px;width:340px;max-height:calc(100vh - 80px);overflow-y:auto;background:#1a1a2e;color:#fff;padding:12px 16px;border-radius:8px;font-family:-apple-system,sans-serif;font-size:12px;line-height:1.5;z-index:var(--kg-z-top);box-shadow:0 4px 16px rgba(0,0,0,0.4);display:none;"
983
+ );
984
+ document.documentElement.appendChild(div);
985
+ panelEl = div;
986
+ return div;
987
+ };
988
+ var renderPanel = () => {
989
+ const panel = ensurePanel();
990
+ updateToggleButton();
991
+ if (!panelOpen) {
992
+ panel.style.display = "none";
993
+ return;
994
+ }
995
+ panel.style.display = "block";
996
+ const header = state.recording ? `<div style="color:#ef4444;font-weight:600;margin-bottom:6px;">\u{1F4F9} Recording... (${state.recordedSteps.length} steps)</div>` : `<div style="color:#7a89b0;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:6px;">Steps (${state.recordedSteps.length})</div>`;
997
+ const rows = state.recordedSteps.map((s, i) => `<div>${i + 1}. ${renderStepLine(s)}</div>`).join("");
998
+ panel.innerHTML = `${header}${rows || '<div style="color:#888;">(no steps)</div>'}`;
999
+ };
1000
+ var updateToggleButton = () => {
1001
+ const btn = document.getElementById("kg-steps-toggle");
1002
+ if (!btn) return;
1003
+ const n = state.recordedSteps.length;
1004
+ const recording = state.recording;
1005
+ btn.textContent = recording ? `\u{1F4F9} Steps (${n})` : n > 0 ? `\u{1F4CB} Steps (${n})` : "\u{1F4CB} Steps (0)";
1006
+ btn.classList.toggle("has-steps", n > 0 && !panelOpen);
1007
+ btn.classList.toggle("open", panelOpen);
1008
+ };
1009
+ var openPanel = () => {
1010
+ panelOpen = true;
1011
+ renderPanel();
1012
+ };
1013
+ var closePanel = () => {
1014
+ panelOpen = false;
1015
+ renderPanel();
1016
+ };
1017
+ var togglePanel = () => {
1018
+ if (panelOpen) closePanel();
1019
+ else openPanel();
1020
+ };
1021
+ var escapeHtml = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1022
+ var optBadge = (s) => s.optional ? ' <span style="color:#7a89b0;font-size:10px;background:#2a2a4e;padding:1px 5px;border-radius:3px;margin-left:4px;">optional</span>' : "";
1023
+ var renderStepLine = (s) => {
1024
+ switch (s.action) {
1025
+ case "click":
1026
+ return `<b>click</b> ${escapeHtml(s.selector)}${optBadge(s)}`;
1027
+ case "type":
1028
+ return `<b>type</b> ${escapeHtml(s.selector)} \u2192 "${escapeHtml(s.text)}"${optBadge(s)}`;
1029
+ case "select":
1030
+ return `<b>select</b> ${escapeHtml(s.selector)} \u2192 "${escapeHtml(s.value)}"${optBadge(s)}`;
1031
+ case "hover":
1032
+ return `<b>hover</b> ${escapeHtml(s.selector)}${optBadge(s)}`;
1033
+ case "wait":
1034
+ return `<b>wait</b> ${s.ms}ms`;
1035
+ case "waitForSelector":
1036
+ return `<b>waitForSelector</b> ${escapeHtml(s.selector)}${s.timeout ? ` (${s.timeout}ms)` : ""}${optBadge(s)}`;
1037
+ default:
1038
+ return `<b>${s.action}</b>`;
1039
+ }
1040
+ };
1041
+ var updateToolbarLockState = () => {
1042
+ const lock = state.recording;
1043
+ for (const id of [
1044
+ "kg-tool-rect",
1045
+ "kg-tool-arrow",
1046
+ "kg-tool-label",
1047
+ "kg-cap-full",
1048
+ "kg-cap-crop",
1049
+ "kg-delete"
1050
+ ]) {
1051
+ const btn = document.getElementById(id);
1052
+ if (btn) btn.disabled = lock;
1053
+ }
1054
+ const group = document.getElementById("kg-rec-group");
1055
+ if (group) group.classList.toggle("visible", lock);
1056
+ const recBtn = document.getElementById("kg-record");
1057
+ if (recBtn) {
1058
+ recBtn.textContent = state.recording ? "\u23F9 Stop" : "\u{1F534} Record";
1059
+ recBtn.classList.toggle("active", state.recording);
1060
+ }
1061
+ if (svgRef2) {
1062
+ svgRef2.style.pointerEvents = state.recording ? "none" : "";
1063
+ }
1064
+ };
1065
+ var setRecording = (on) => {
1066
+ if (on) {
1067
+ if (state.recordedSteps.length > 0) {
1068
+ const ok = window.confirm(
1069
+ `Recording will replace the existing ${state.recordedSteps.length} step(s). Continue?`
1070
+ );
1071
+ if (!ok) return;
1072
+ }
1073
+ state.recordedSteps = [];
1074
+ state.recording = true;
1075
+ } else {
1076
+ state.recording = false;
1077
+ cancelPicker();
1078
+ }
1079
+ updateToolbarLockState();
1080
+ renderPanel();
1081
+ };
1082
+ var pickerButtonId = (kind) => kind === "hover" ? "kg-rec-hover" : "kg-rec-wfs";
1083
+ var startPicker = (kind) => {
1084
+ if (!state.recording) return;
1085
+ state.pickerKind = kind;
1086
+ document.getElementById(pickerButtonId(kind))?.classList.add("picking");
1087
+ if (svgRef2) svgRef2.style.cursor = "crosshair";
1088
+ showPrompt(
1089
+ kind === "hover" ? "Hover an element, click to confirm. ESC to cancel." : "Hover an element, click to confirm. ESC to cancel."
1090
+ );
1091
+ };
1092
+ var cancelPicker = () => {
1093
+ if (state.pickerKind) {
1094
+ document.getElementById(pickerButtonId(state.pickerKind))?.classList.remove("picking");
1095
+ }
1096
+ state.pickerKind = null;
1097
+ if (svgRef2) svgRef2.style.cursor = "";
1098
+ hidePrompt();
1099
+ hidePickerOutline();
1100
+ };
1101
+ var ensurePickerOutline = () => {
1102
+ if (pickerOutlineEl) return pickerOutlineEl;
1103
+ const div = document.createElement("div");
1104
+ div.className = "kagemusha-picker-outline";
1105
+ div.style.display = "none";
1106
+ document.documentElement.appendChild(div);
1107
+ pickerOutlineEl = div;
1108
+ return div;
1109
+ };
1110
+ var showPickerOutline = (rect) => {
1111
+ const el = ensurePickerOutline();
1112
+ el.style.display = "block";
1113
+ el.style.left = `${rect.left}px`;
1114
+ el.style.top = `${rect.top}px`;
1115
+ el.style.width = `${rect.width}px`;
1116
+ el.style.height = `${rect.height}px`;
1117
+ };
1118
+ var hidePickerOutline = () => {
1119
+ if (pickerOutlineEl) pickerOutlineEl.style.display = "none";
1120
+ };
1121
+ var promptEl = null;
1122
+ var showPrompt = (message) => {
1123
+ if (!promptEl) {
1124
+ const div = document.createElement("div");
1125
+ div.className = "kagemusha-prompt";
1126
+ div.setAttribute(
1127
+ "style",
1128
+ "position:fixed;top:60px;left:50%;transform:translateX(-50%);background:#0ea5e9;color:#fff;padding:10px 18px;border-radius:8px;font-family:-apple-system,sans-serif;font-size:13px;z-index:var(--kg-z-top);box-shadow:0 4px 12px rgba(0,0,0,0.3);"
1129
+ );
1130
+ document.documentElement.appendChild(div);
1131
+ promptEl = div;
1132
+ }
1133
+ promptEl.textContent = message;
1134
+ promptEl.style.display = "block";
1135
+ };
1136
+ var hidePrompt = () => {
1137
+ if (promptEl) promptEl.style.display = "none";
1138
+ };
1139
+ var onClickCapture = (e) => {
1140
+ if (!state.recording) return;
1141
+ if (isOwnUi(e.target)) return;
1142
+ const el = e.target;
1143
+ if (!el) return;
1144
+ if (state.pickerKind) {
1145
+ e.preventDefault();
1146
+ e.stopPropagation();
1147
+ const kind = state.pickerKind;
1148
+ const sel2 = computeSelector(el);
1149
+ if (kind === "hover") {
1150
+ state.recordedSteps.push({ action: "hover", selector: sel2.selector });
1151
+ } else {
1152
+ state.recordedSteps.push({
1153
+ action: "waitForSelector",
1154
+ selector: sel2.selector
1155
+ });
1156
+ }
1157
+ cancelPicker();
1158
+ renderPanel();
1159
+ return;
1160
+ }
1161
+ const sel = computeSelector(el);
1162
+ state.recordedSteps.push({
1163
+ action: "click",
1164
+ selector: sel.selector,
1165
+ optional: true
1166
+ });
1167
+ renderPanel();
1168
+ };
1169
+ var onChangeCapture = (e) => {
1170
+ if (!state.recording) return;
1171
+ if (isOwnUi(e.target)) return;
1172
+ const target = e.target;
1173
+ if (!target) return;
1174
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
1175
+ const input = target;
1176
+ const type = input.type;
1177
+ if (type === "checkbox" || type === "radio") return;
1178
+ const sel = computeSelector(input);
1179
+ state.recordedSteps.push({
1180
+ action: "type",
1181
+ selector: sel.selector,
1182
+ text: input.value,
1183
+ optional: true
1184
+ });
1185
+ renderPanel();
1186
+ return;
1187
+ }
1188
+ if (target.tagName === "SELECT") {
1189
+ const select = target;
1190
+ const sel = computeSelector(select);
1191
+ state.recordedSteps.push({
1192
+ action: "select",
1193
+ selector: sel.selector,
1194
+ value: select.value,
1195
+ optional: true
1196
+ });
1197
+ renderPanel();
1198
+ }
1199
+ };
1200
+ var onKeyDownCapture = (e) => {
1201
+ if (state.pickerKind && e.key === "Escape") {
1202
+ cancelPicker();
1203
+ }
1204
+ };
1205
+ var onMouseMoveCapture = (e) => {
1206
+ if (!state.pickerKind) return;
1207
+ if (isOwnUi(e.target)) {
1208
+ hidePickerOutline();
1209
+ return;
1210
+ }
1211
+ const el = e.target;
1212
+ if (!el) return;
1213
+ showPickerOutline(el.getBoundingClientRect());
1214
+ };
1215
+ var onOutsideClick = (e) => {
1216
+ if (!panelOpen) return;
1217
+ const target = e.target;
1218
+ if (!target) return;
1219
+ if (target.closest(".kagemusha-steps-panel")) return;
1220
+ if (target.closest("#kg-steps-toggle")) return;
1221
+ closePanel();
1222
+ };
1223
+ var promptForWaitMs = () => {
1224
+ const raw = window.prompt("Wait for how many milliseconds?", "3000");
1225
+ if (raw === null) return;
1226
+ const ms = Number.parseInt(raw, 10);
1227
+ if (Number.isNaN(ms) || ms <= 0) return;
1228
+ state.recordedSteps.push({ action: "wait", ms });
1229
+ renderPanel();
1230
+ };
1231
+ var initRecord = (svg2) => {
1232
+ svgRef2 = svg2;
1233
+ ensurePanel();
1234
+ document.getElementById("kg-record")?.addEventListener("click", () => setRecording(!state.recording));
1235
+ document.getElementById("kg-rec-wait")?.addEventListener("click", () => promptForWaitMs());
1236
+ document.getElementById("kg-rec-wfs")?.addEventListener("click", () => startPicker("waitForSelector"));
1237
+ document.getElementById("kg-rec-hover")?.addEventListener("click", () => startPicker("hover"));
1238
+ document.getElementById("kg-steps-toggle")?.addEventListener("click", (e) => {
1239
+ e.stopPropagation();
1240
+ togglePanel();
1241
+ });
1242
+ document.addEventListener("click", onClickCapture, true);
1243
+ document.addEventListener("change", onChangeCapture, true);
1244
+ document.addEventListener("keydown", onKeyDownCapture, true);
1245
+ document.addEventListener("mousemove", onMouseMoveCapture, true);
1246
+ document.addEventListener("click", onOutsideClick);
1247
+ updateToolbarLockState();
1248
+ renderPanel();
1249
+ };
1250
+ var loadSteps = (steps) => {
1251
+ state.recordedSteps = [...steps];
1252
+ renderPanel();
1253
+ };
1254
+ var serializeSteps = () => [...state.recordedSteps];
1255
+
1256
+ // src/editor/inject-script/bridge.ts
1257
+ var save = () => {
1258
+ if (state.captureMode === "crop" && !state.captureCrop) {
1259
+ showErrorToast(
1260
+ "Crop mode is active but no area is drawn.\nDrag to define an area, or switch to Full Page."
1261
+ );
1262
+ return;
1263
+ }
1264
+ const decorations = serializeAnnotations();
1265
+ const capture = serializeCapture();
1266
+ const beforeCapture = serializeSteps();
1267
+ window.__kagemusha_save(
1268
+ JSON.stringify({ decorations, capture, beforeCapture })
1269
+ );
1270
+ };
1271
+ var initBridge = () => {
1272
+ window.__kagemusha_loadAnnotations = (decorations) => {
1273
+ loadAnnotations(decorations);
1274
+ };
1275
+ window.__kagemusha_loadCapture = (capture) => {
1276
+ loadCapture(capture, (mode) => setCaptureMode(mode));
1277
+ };
1278
+ window.__kagemusha_loadSteps = (steps) => {
1279
+ loadSteps(steps);
1280
+ };
1281
+ return { save };
1282
+ };
1283
+
1284
+ // src/editor/inject-script/index.ts
1285
+ document.documentElement.style.overflow = "hidden";
1286
+ var { svg } = initSvgLayer();
1287
+ var bridgeSave = () => {
1288
+ };
1289
+ initToolbar(
1290
+ {
1291
+ onToolChange: () => {
1292
+ },
1293
+ onSave: () => bridgeSave(),
1294
+ onDelete: () => deleteSelected()
1295
+ },
1296
+ svg
1297
+ );
1298
+ initCrop();
1299
+ initRecord(svg);
1300
+ ({ save: bridgeSave } = initBridge());
1301
+ svg.addEventListener("mousedown", (e) => {
1302
+ if (handleMouseDown2(e)) return;
1303
+ handleMouseDown(e);
1304
+ });
1305
+ document.addEventListener("mousemove", (e) => {
1306
+ if (handleMouseMove2(e)) return;
1307
+ handleMouseMove(e);
1308
+ });
1309
+ document.addEventListener("mouseup", (e) => {
1310
+ if (handleMouseUp2(e)) return;
1311
+ handleMouseUp(e);
1312
+ });
1313
+ document.addEventListener(
1314
+ "keydown",
1315
+ (e) => handleKeyDown(e)
1316
+ );
1317
+ })();