ilse-design 0.3.0-beta.1

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 (190) hide show
  1. package/README.md +187 -0
  2. package/dist/agent/executor.d.ts +20 -0
  3. package/dist/agent/executor.d.ts.map +1 -0
  4. package/dist/agent/executor.js +130 -0
  5. package/dist/agent/executor.js.map +1 -0
  6. package/dist/ai/provider.d.ts +45 -0
  7. package/dist/ai/provider.d.ts.map +1 -0
  8. package/dist/ai/provider.js +374 -0
  9. package/dist/ai/provider.js.map +1 -0
  10. package/dist/analysis/classify-element.d.ts +23 -0
  11. package/dist/analysis/classify-element.d.ts.map +1 -0
  12. package/dist/analysis/classify-element.js +79 -0
  13. package/dist/analysis/classify-element.js.map +1 -0
  14. package/dist/analysis/compare-family.d.ts +22 -0
  15. package/dist/analysis/compare-family.d.ts.map +1 -0
  16. package/dist/analysis/compare-family.js +82 -0
  17. package/dist/analysis/compare-family.js.map +1 -0
  18. package/dist/analysis/profile-component.d.ts +32 -0
  19. package/dist/analysis/profile-component.d.ts.map +1 -0
  20. package/dist/analysis/profile-component.js +119 -0
  21. package/dist/analysis/profile-component.js.map +1 -0
  22. package/dist/bridge/augment.d.ts +20 -0
  23. package/dist/bridge/augment.d.ts.map +1 -0
  24. package/dist/bridge/augment.js +110 -0
  25. package/dist/bridge/augment.js.map +1 -0
  26. package/dist/bridge/store.d.ts +17 -0
  27. package/dist/bridge/store.d.ts.map +1 -0
  28. package/dist/bridge/store.js +70 -0
  29. package/dist/bridge/store.js.map +1 -0
  30. package/dist/bridge/ws-server.d.ts +12 -0
  31. package/dist/bridge/ws-server.d.ts.map +1 -0
  32. package/dist/bridge/ws-server.js +136 -0
  33. package/dist/bridge/ws-server.js.map +1 -0
  34. package/dist/cli/check.d.ts +8 -0
  35. package/dist/cli/check.d.ts.map +1 -0
  36. package/dist/cli/check.js +303 -0
  37. package/dist/cli/check.js.map +1 -0
  38. package/dist/cli/default.d.ts +2 -0
  39. package/dist/cli/default.d.ts.map +1 -0
  40. package/dist/cli/default.js +166 -0
  41. package/dist/cli/default.js.map +1 -0
  42. package/dist/cli/extract.d.ts +5 -0
  43. package/dist/cli/extract.d.ts.map +1 -0
  44. package/dist/cli/extract.js +62 -0
  45. package/dist/cli/extract.js.map +1 -0
  46. package/dist/cli/index.d.ts +3 -0
  47. package/dist/cli/index.d.ts.map +1 -0
  48. package/dist/cli/index.js +111 -0
  49. package/dist/cli/index.js.map +1 -0
  50. package/dist/cli/init.d.ts +5 -0
  51. package/dist/cli/init.d.ts.map +1 -0
  52. package/dist/cli/init.js +190 -0
  53. package/dist/cli/init.js.map +1 -0
  54. package/dist/cli/insight.d.ts +5 -0
  55. package/dist/cli/insight.d.ts.map +1 -0
  56. package/dist/cli/insight.js +141 -0
  57. package/dist/cli/insight.js.map +1 -0
  58. package/dist/cli/listen.d.ts +10 -0
  59. package/dist/cli/listen.d.ts.map +1 -0
  60. package/dist/cli/listen.js +94 -0
  61. package/dist/cli/listen.js.map +1 -0
  62. package/dist/cli/login.d.ts +15 -0
  63. package/dist/cli/login.d.ts.map +1 -0
  64. package/dist/cli/login.js +302 -0
  65. package/dist/cli/login.js.map +1 -0
  66. package/dist/config/limits.d.ts +13 -0
  67. package/dist/config/limits.d.ts.map +1 -0
  68. package/dist/config/limits.js +26 -0
  69. package/dist/config/limits.js.map +1 -0
  70. package/dist/config/mcp-register.d.ts +5 -0
  71. package/dist/config/mcp-register.d.ts.map +1 -0
  72. package/dist/config/mcp-register.js +50 -0
  73. package/dist/config/mcp-register.js.map +1 -0
  74. package/dist/config/preferences.d.ts +22 -0
  75. package/dist/config/preferences.d.ts.map +1 -0
  76. package/dist/config/preferences.js +159 -0
  77. package/dist/config/preferences.js.map +1 -0
  78. package/dist/config/user-config.d.ts +10 -0
  79. package/dist/config/user-config.d.ts.map +1 -0
  80. package/dist/config/user-config.js +32 -0
  81. package/dist/config/user-config.js.map +1 -0
  82. package/dist/constants.d.ts +11 -0
  83. package/dist/constants.d.ts.map +1 -0
  84. package/dist/constants.js +49 -0
  85. package/dist/constants.js.map +1 -0
  86. package/dist/extraction/find-inconsistencies.d.ts +17 -0
  87. package/dist/extraction/find-inconsistencies.d.ts.map +1 -0
  88. package/dist/extraction/find-inconsistencies.js +93 -0
  89. package/dist/extraction/find-inconsistencies.js.map +1 -0
  90. package/dist/extraction/find-patterns.d.ts +20 -0
  91. package/dist/extraction/find-patterns.d.ts.map +1 -0
  92. package/dist/extraction/find-patterns.js +179 -0
  93. package/dist/extraction/find-patterns.js.map +1 -0
  94. package/dist/extraction/index-tokens.d.ts +16 -0
  95. package/dist/extraction/index-tokens.d.ts.map +1 -0
  96. package/dist/extraction/index-tokens.js +103 -0
  97. package/dist/extraction/index-tokens.js.map +1 -0
  98. package/dist/fixer/autofix.d.ts +11 -0
  99. package/dist/fixer/autofix.d.ts.map +1 -0
  100. package/dist/fixer/autofix.js +202 -0
  101. package/dist/fixer/autofix.js.map +1 -0
  102. package/dist/mcp/server.d.ts +3 -0
  103. package/dist/mcp/server.d.ts.map +1 -0
  104. package/dist/mcp/server.js +271 -0
  105. package/dist/mcp/server.js.map +1 -0
  106. package/dist/parsers/repo.d.ts +22 -0
  107. package/dist/parsers/repo.d.ts.map +1 -0
  108. package/dist/parsers/repo.js +165 -0
  109. package/dist/parsers/repo.js.map +1 -0
  110. package/dist/parsers/snapshot.d.ts +15 -0
  111. package/dist/parsers/snapshot.d.ts.map +1 -0
  112. package/dist/parsers/snapshot.js +136 -0
  113. package/dist/parsers/snapshot.js.map +1 -0
  114. package/dist/parsers/tokens.d.ts +10 -0
  115. package/dist/parsers/tokens.d.ts.map +1 -0
  116. package/dist/parsers/tokens.js +119 -0
  117. package/dist/parsers/tokens.js.map +1 -0
  118. package/dist/react/annotator.d.ts +64 -0
  119. package/dist/react/annotator.d.ts.map +1 -0
  120. package/dist/react/annotator.js +279 -0
  121. package/dist/react/annotator.js.map +1 -0
  122. package/dist/react/area-selector.d.ts +14 -0
  123. package/dist/react/area-selector.d.ts.map +1 -0
  124. package/dist/react/area-selector.js +76 -0
  125. package/dist/react/area-selector.js.map +1 -0
  126. package/dist/react/freeze.d.ts +5 -0
  127. package/dist/react/freeze.d.ts.map +1 -0
  128. package/dist/react/freeze.js +109 -0
  129. package/dist/react/freeze.js.map +1 -0
  130. package/dist/react/image-input.d.ts +14 -0
  131. package/dist/react/image-input.d.ts.map +1 -0
  132. package/dist/react/image-input.js +100 -0
  133. package/dist/react/image-input.js.map +1 -0
  134. package/dist/react/index.d.ts +6 -0
  135. package/dist/react/index.d.ts.map +1 -0
  136. package/dist/react/index.js +13 -0
  137. package/dist/react/index.js.map +1 -0
  138. package/dist/react/toolbar.d.ts +2 -0
  139. package/dist/react/toolbar.d.ts.map +1 -0
  140. package/dist/react/toolbar.js +755 -0
  141. package/dist/react/toolbar.js.map +1 -0
  142. package/dist/react/ws-client.d.ts +8 -0
  143. package/dist/react/ws-client.d.ts.map +1 -0
  144. package/dist/react/ws-client.js +71 -0
  145. package/dist/react/ws-client.js.map +1 -0
  146. package/dist/reporters/coverage.d.ts +6 -0
  147. package/dist/reporters/coverage.d.ts.map +1 -0
  148. package/dist/reporters/coverage.js +74 -0
  149. package/dist/reporters/coverage.js.map +1 -0
  150. package/dist/reporters/ds-report.d.ts +8 -0
  151. package/dist/reporters/ds-report.d.ts.map +1 -0
  152. package/dist/reporters/ds-report.js +66 -0
  153. package/dist/reporters/ds-report.js.map +1 -0
  154. package/dist/reporters/terminal.d.ts +43 -0
  155. package/dist/reporters/terminal.d.ts.map +1 -0
  156. package/dist/reporters/terminal.js +361 -0
  157. package/dist/reporters/terminal.js.map +1 -0
  158. package/dist/setup/framework-detect.d.ts +8 -0
  159. package/dist/setup/framework-detect.d.ts.map +1 -0
  160. package/dist/setup/framework-detect.js +109 -0
  161. package/dist/setup/framework-detect.js.map +1 -0
  162. package/dist/setup/inject-component.d.ts +12 -0
  163. package/dist/setup/inject-component.d.ts.map +1 -0
  164. package/dist/setup/inject-component.js +98 -0
  165. package/dist/setup/inject-component.js.map +1 -0
  166. package/dist/types.d.ts +170 -0
  167. package/dist/types.d.ts.map +1 -0
  168. package/dist/types.js +5 -0
  169. package/dist/types.js.map +1 -0
  170. package/dist/utils/ast.d.ts +18 -0
  171. package/dist/utils/ast.d.ts.map +1 -0
  172. package/dist/utils/ast.js +57 -0
  173. package/dist/utils/ast.js.map +1 -0
  174. package/dist/utils/files.d.ts +6 -0
  175. package/dist/utils/files.d.ts.map +1 -0
  176. package/dist/utils/files.js +25 -0
  177. package/dist/utils/files.js.map +1 -0
  178. package/dist/utils/suggest.d.ts +37 -0
  179. package/dist/utils/suggest.d.ts.map +1 -0
  180. package/dist/utils/suggest.js +158 -0
  181. package/dist/utils/suggest.js.map +1 -0
  182. package/dist/validators/coverage.d.ts +24 -0
  183. package/dist/validators/coverage.d.ts.map +1 -0
  184. package/dist/validators/coverage.js +156 -0
  185. package/dist/validators/coverage.js.map +1 -0
  186. package/dist/validators/static.d.ts +14 -0
  187. package/dist/validators/static.d.ts.map +1 -0
  188. package/dist/validators/static.js +398 -0
  189. package/dist/validators/static.js.map +1 -0
  190. package/package.json +64 -0
@@ -0,0 +1,755 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef, useCallback } from 'react';
3
+ import { captureElement, captureTextSelection, captureArea, captureEnvironment } from './annotator.js';
4
+ import { connect, disconnect, send, onMessage, isConnected } from './ws-client.js';
5
+ import { useImageInput, ImagePreview } from './image-input.js';
6
+ import { toggleFreeze, isFrozen } from './freeze.js';
7
+ // ── Clipboard fallback: format annotations as markdown ──────────────────────
8
+ function formatAnnotationMarkdown(ann, index, env) {
9
+ const lines = [];
10
+ lines.push(`### ${index + 1}. ${ann.capture.element}${ann.capture.component ? ` (${ann.capture.component})` : ''}`);
11
+ if (ann.capture.grepPattern)
12
+ lines.push(`**Grep:** \`${ann.capture.grepPattern}\``);
13
+ if (ann.capture.componentStack?.length)
14
+ lines.push(`**Stack:** ${ann.capture.componentStack.join(' > ')}`);
15
+ if (ann.capture.domPath)
16
+ lines.push(`**DOM:** ${ann.capture.domPath}`);
17
+ if (ann.note)
18
+ lines.push(`**Feedback:** ${ann.note}`);
19
+ const styleEntries = Object.entries(ann.capture.styles).slice(0, 10);
20
+ if (styleEntries.length > 0) {
21
+ lines.push(`**Styles:** ${styleEntries.map(([k, v]) => `${k}: ${v}`).join('; ')}`);
22
+ }
23
+ if (ann.capture.parent)
24
+ lines.push(`**Parent:** ${ann.capture.parent}`);
25
+ return lines.join('\n');
26
+ }
27
+ function formatAllAsMarkdown(annotations) {
28
+ const env = captureEnvironment();
29
+ const pending = annotations.filter(a => a.status === 'pending');
30
+ const lines = [
31
+ `## Page Feedback: ${env.url}`,
32
+ '',
33
+ `**Environment:** ${env.viewport} · DPR ${env.devicePixelRatio}`,
34
+ `**Timestamp:** ${env.timestamp}`,
35
+ '',
36
+ ...pending.map((ann, i) => formatAnnotationMarkdown(ann, i, env)),
37
+ '',
38
+ '---',
39
+ '*Gerado por Ilse — cole no seu agente de código para corrigir.*',
40
+ ];
41
+ return lines.join('\n');
42
+ }
43
+ // ─── AnnotationEditor (inside settings panel) ─────────────────────────────────
44
+ function AnnotationEditor({ note, imageRef, onNoteChange, onImageChange, onSend, }) {
45
+ const { handlePaste, handleFileChange, openFilePicker, fileRef } = useImageInput(onImageChange);
46
+ return (_jsxs("div", { style: { marginTop: 6 }, onClick: (e) => e.stopPropagation(), children: [_jsx("textarea", { value: note, onChange: (e) => onNoteChange(e.target.value), onPaste: handlePaste, placeholder: "O que est\u00E1 errado?", style: {
47
+ width: '100%', minHeight: 48, padding: 6,
48
+ backgroundColor: '#0f0f23', border: '1px solid #2d2d4a',
49
+ borderRadius: 6, color: '#e2e8f0', fontSize: 12,
50
+ resize: 'vertical', outline: 'none', boxSizing: 'border-box',
51
+ } }), _jsx(ImagePreview, { value: imageRef, onRemove: () => onImageChange(undefined), onUpload: openFilePicker, fileRef: fileRef, onFileChange: handleFileChange }), _jsx("button", { onClick: onSend, style: {
52
+ marginTop: 4, padding: '4px 10px', backgroundColor: '#6366f1',
53
+ color: 'white', border: 'none', borderRadius: 6, cursor: 'pointer', fontSize: 12,
54
+ }, children: "Enviar" })] }));
55
+ }
56
+ // ─── AnnotationPopover ────────────────────────────────────────────────────────
57
+ function AnnotationPopover({ context, rect, note, grepPattern, onNoteChange, onAdd, onCancel, }) {
58
+ const [pos, setPos] = useState({ top: 0, left: 0 });
59
+ const [copied, setCopied] = useState(false);
60
+ useEffect(() => {
61
+ if (typeof window === 'undefined')
62
+ return;
63
+ const width = 300;
64
+ const height = grepPattern ? 196 : 168;
65
+ const gap = 8;
66
+ let top = rect.top + rect.height + gap;
67
+ let left = rect.left;
68
+ if (left + width > window.innerWidth - 8)
69
+ left = window.innerWidth - width - 8;
70
+ if (left < 8)
71
+ left = 8;
72
+ if (top + height > window.innerHeight - 8)
73
+ top = rect.top - height - gap;
74
+ if (top < 8)
75
+ top = 8;
76
+ setPos({ top, left });
77
+ }, [rect, grepPattern]);
78
+ const copyGrep = () => {
79
+ if (!grepPattern)
80
+ return;
81
+ navigator.clipboard.writeText(grepPattern).then(() => {
82
+ setCopied(true);
83
+ setTimeout(() => setCopied(false), 1500);
84
+ });
85
+ };
86
+ return (_jsxs("div", { "data-ilse-toolbar": true, style: {
87
+ position: 'fixed', top: pos.top, left: pos.left, width: 300,
88
+ backgroundColor: '#1a1a2e', border: '1px solid #2d2d4a',
89
+ borderRadius: 17, padding: 16, zIndex: 99999,
90
+ boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
91
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
92
+ }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontSize: 12, color: '#64748b', marginBottom: grepPattern ? 6 : 10, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: context }), grepPattern && (_jsxs("div", { onClick: copyGrep, title: "Copiar padr\u00E3o para grep", style: {
93
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
94
+ backgroundColor: '#0f0f23', border: '1px solid #2d2d4a',
95
+ borderRadius: 6, padding: '4px 8px', marginBottom: 10,
96
+ cursor: 'pointer', gap: 6,
97
+ }, children: [_jsx("code", { style: { fontSize: 11, color: '#a78bfa', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }, children: grepPattern }), _jsx("span", { style: { fontSize: 10, color: copied ? '#4ade80' : '#64748b', flexShrink: 0 }, children: copied ? '✓' : '⎘' })] })), _jsx("textarea", { autoFocus: true, value: note, onChange: (e) => onNoteChange(e.target.value), onKeyDown: (e) => {
98
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
99
+ e.preventDefault();
100
+ onAdd();
101
+ }
102
+ if (e.key === 'Escape') {
103
+ e.preventDefault();
104
+ onCancel();
105
+ }
106
+ }, placeholder: "Descreva o problema...", style: {
107
+ width: '100%', minHeight: 52, padding: 8,
108
+ backgroundColor: '#0f0f23', border: '1px solid #2d2d4a',
109
+ borderRadius: 8, color: '#e2e8f0', fontSize: 13,
110
+ resize: 'none', outline: 'none', boxSizing: 'border-box',
111
+ marginBottom: 10, display: 'block',
112
+ } }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 }, children: [_jsx("button", { onClick: onCancel, style: { padding: '6px 14px', background: 'none', border: 'none', color: '#94a3b8', cursor: 'pointer', fontSize: 13, borderRadius: 8 }, children: "Cancelar" }), _jsx("button", { onClick: onAdd, style: { padding: '6px 16px', backgroundColor: '#6366f1', color: 'white', border: 'none', borderRadius: 8, cursor: 'pointer', fontSize: 13, fontWeight: 600 }, children: "Adicionar" })] })] }));
113
+ }
114
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
115
+ function normalizeRect(start, end) {
116
+ return {
117
+ top: Math.min(start.y, end.y),
118
+ left: Math.min(start.x, end.x),
119
+ width: Math.abs(end.x - start.x),
120
+ height: Math.abs(end.y - start.y),
121
+ };
122
+ }
123
+ function clampToViewport(pos, buttonSize = 40) {
124
+ if (typeof window === 'undefined')
125
+ return pos;
126
+ const vw = window.innerWidth;
127
+ const vh = window.innerHeight;
128
+ return {
129
+ x: Math.min(Math.max(pos.x, 8), vw - buttonSize - 8),
130
+ y: Math.min(Math.max(pos.y, 8), vh - buttonSize - 8),
131
+ };
132
+ }
133
+ function getInitialToolbarPos() {
134
+ if (typeof window === 'undefined')
135
+ return { x: 0, y: 0 };
136
+ try {
137
+ const saved = sessionStorage.getItem('ilse-toolbar-pos');
138
+ if (saved)
139
+ return clampToViewport(JSON.parse(saved));
140
+ }
141
+ catch { /* ignore */ }
142
+ return { x: Math.max(0, window.innerWidth / 2 - 160), y: window.innerHeight - 72 };
143
+ }
144
+ // ─── IlseToolbar ─────────────────────────────────────────────────────────────
145
+ export function IlseToolbar() {
146
+ const [mounted, setMounted] = useState(false);
147
+ const [isOpen, setIsOpen] = useState(false);
148
+ const [annotating, setAnnotating] = useState(false);
149
+ const [annotations, setAnnotations] = useState([]);
150
+ const [activeMarker, setActiveMarker] = useState(null);
151
+ const [markersVisible, setMarkersVisible] = useState(true);
152
+ const [showSettings, setShowSettings] = useState(false);
153
+ const [paused, setPaused] = useState(false);
154
+ const [connected, setConnected] = useState(false);
155
+ const [clearConfirm, setClearConfirm] = useState(false);
156
+ const [sendFeedback, setSendFeedback] = useState(null);
157
+ const [clipboardToast, setClipboardToast] = useState(null);
158
+ const [highlight, setHighlight] = useState(null);
159
+ const [dragRect, setDragRect] = useState(null);
160
+ const [pendingCapture, setPendingCapture] = useState(null);
161
+ const [toolbarPos, setToolbarPos] = useState({ x: 0, y: 0 });
162
+ const [hoverLabel, setHoverLabel] = useState(null);
163
+ const [hoverLabelRect, setHoverLabelRect] = useState(null);
164
+ const toolbarDragRef = useRef(null);
165
+ const annotationDragRef = useRef(null);
166
+ const isDraggingAnnotationRef = useRef(false);
167
+ const clearTimerRef = useRef(null);
168
+ const toolbarPosRef = useRef(toolbarPos);
169
+ useEffect(() => { toolbarPosRef.current = toolbarPos; }, [toolbarPos]);
170
+ // ── Mount guard (SSR safety) ───────────────────────────────────────────────
171
+ useEffect(() => {
172
+ setMounted(true);
173
+ setToolbarPos(getInitialToolbarPos());
174
+ }, []);
175
+ // ── Clamp position on resize (monitor/window changes) ─────────────────────
176
+ useEffect(() => {
177
+ const onResize = () => setToolbarPos(prev => clampToViewport(prev));
178
+ window.addEventListener('resize', onResize);
179
+ return () => window.removeEventListener('resize', onResize);
180
+ }, []);
181
+ // ── WS connection ──────────────────────────────────────────────────────────
182
+ useEffect(() => {
183
+ connect();
184
+ const checkConnection = setInterval(() => setConnected(isConnected()), 1000);
185
+ const unsub = onMessage((msg) => {
186
+ if (msg.type === 'annotated' && msg.annotation) {
187
+ const a = msg.annotation;
188
+ setAnnotations(prev => prev.map(ann => !ann.id && ann.capture.element === a.element ? { ...ann, id: a.id } : ann));
189
+ }
190
+ if (msg.type === 'resolved' && msg.annotation) {
191
+ const a = msg.annotation;
192
+ setAnnotations(prev => prev.map(ann => ann.id === a.id ? { ...ann, status: 'resolved', resolvedSummary: a.resolvedSummary } : ann));
193
+ }
194
+ if (msg.type === 'status') {
195
+ setAnnotations(prev => prev.map(ann => ann.id === msg.id ? { ...ann, status: msg.status } : ann));
196
+ }
197
+ });
198
+ return () => { clearInterval(checkConnection); unsub(); disconnect(); };
199
+ }, []);
200
+ // ── Annotation helpers ─────────────────────────────────────────────────────
201
+ const addAnnotation = useCallback((capture, prefillNote = '', markerType = 'element') => {
202
+ setAnnotations(prev => [...prev, { capture, note: prefillNote, status: 'pending', markerType }]);
203
+ }, []);
204
+ const confirmCapture = useCallback(() => {
205
+ if (!pendingCapture)
206
+ return;
207
+ addAnnotation(pendingCapture.capture, pendingCapture.note, pendingCapture.markerType);
208
+ setPendingCapture(null);
209
+ setAnnotating(true); // re-enter annotation mode after confirming
210
+ }, [pendingCapture, addAnnotation]);
211
+ const cancelCapture = useCallback(() => {
212
+ setPendingCapture(null);
213
+ setAnnotating(true);
214
+ }, []);
215
+ // ── Toolbar drag ───────────────────────────────────────────────────────────
216
+ const handleToolbarMouseDown = useCallback((e) => {
217
+ e.preventDefault();
218
+ toolbarDragRef.current = {
219
+ startMouseX: e.clientX,
220
+ startMouseY: e.clientY,
221
+ startPosX: toolbarPosRef.current.x,
222
+ startPosY: toolbarPosRef.current.y,
223
+ };
224
+ document.body.style.cursor = 'grabbing';
225
+ }, []);
226
+ useEffect(() => {
227
+ const onMove = (e) => {
228
+ if (!toolbarDragRef.current)
229
+ return;
230
+ const dx = e.clientX - toolbarDragRef.current.startMouseX;
231
+ const dy = e.clientY - toolbarDragRef.current.startMouseY;
232
+ setToolbarPos({
233
+ x: toolbarDragRef.current.startPosX + dx,
234
+ y: toolbarDragRef.current.startPosY + dy,
235
+ });
236
+ };
237
+ const onUp = (e) => {
238
+ if (!toolbarDragRef.current)
239
+ return;
240
+ const dx = e.clientX - toolbarDragRef.current.startMouseX;
241
+ const dy = e.clientY - toolbarDragRef.current.startMouseY;
242
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3)
243
+ wasDraggedRef.current = true;
244
+ const finalPos = { x: toolbarDragRef.current.startPosX + dx, y: toolbarDragRef.current.startPosY + dy };
245
+ try {
246
+ sessionStorage.setItem('ilse-toolbar-pos', JSON.stringify(finalPos));
247
+ }
248
+ catch { /* ignore */ }
249
+ toolbarDragRef.current = null;
250
+ document.body.style.cursor = '';
251
+ };
252
+ document.addEventListener('mousemove', onMove);
253
+ document.addEventListener('mouseup', onUp);
254
+ return () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
255
+ }, []);
256
+ // ── Unified annotation mode handlers ──────────────────────────────────────
257
+ const handleMouseDown = useCallback((e) => {
258
+ if (toolbarDragRef.current)
259
+ return;
260
+ if (e.target.closest('[data-ilse-toolbar]'))
261
+ return;
262
+ annotationDragRef.current = { x: e.clientX, y: e.clientY };
263
+ isDraggingAnnotationRef.current = false;
264
+ }, []);
265
+ const handleMouseMove = useCallback((e) => {
266
+ if (toolbarDragRef.current)
267
+ return;
268
+ const target = e.target;
269
+ if (annotationDragRef.current && e.buttons === 1) {
270
+ const dx = e.clientX - annotationDragRef.current.x;
271
+ const dy = e.clientY - annotationDragRef.current.y;
272
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
273
+ isDraggingAnnotationRef.current = true;
274
+ setHighlight(null);
275
+ setHoverLabel(null);
276
+ setHoverLabelRect(null);
277
+ setDragRect(normalizeRect(annotationDragRef.current, { x: e.clientX, y: e.clientY }));
278
+ return;
279
+ }
280
+ }
281
+ if (!target.closest('[data-ilse-toolbar]')) {
282
+ const rect = target.getBoundingClientRect();
283
+ const r = { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
284
+ setHighlight(r);
285
+ const tag = target.tagName.toLowerCase();
286
+ const id = target.id ? `#${target.id}` : '';
287
+ const cls = !id && target.classList.length > 0 ? `.${target.classList[0]}` : '';
288
+ setHoverLabel(`${tag}${id}${cls}`);
289
+ setHoverLabelRect(r);
290
+ }
291
+ else {
292
+ setHighlight(null);
293
+ }
294
+ }, []);
295
+ const handleMouseUp = useCallback((e) => {
296
+ if (toolbarDragRef.current)
297
+ return;
298
+ const target = e.target;
299
+ if (target.closest('[data-ilse-toolbar]'))
300
+ return;
301
+ const start = annotationDragRef.current;
302
+ const wasDragging = isDraggingAnnotationRef.current;
303
+ annotationDragRef.current = null;
304
+ isDraggingAnnotationRef.current = false;
305
+ setDragRect(null);
306
+ if (wasDragging && start) {
307
+ const rect = normalizeRect(start, { x: e.clientX, y: e.clientY });
308
+ if (rect.width > 10 && rect.height > 10) {
309
+ const area = captureArea(rect);
310
+ setPendingCapture({
311
+ capture: { element: `[área: ${area.elementCount} elementos]`, styles: {}, rect: area.region, _scrollX: window.scrollX, _scrollY: window.scrollY },
312
+ context: `Área: ${area.elementCount} elementos`,
313
+ rect,
314
+ note: `${area.elements.slice(0, 5).map(el => el.selector).join(', ')}${area.elementCount > 5 ? '...' : ''}`,
315
+ markerType: 'area',
316
+ });
317
+ setAnnotating(false);
318
+ }
319
+ return;
320
+ }
321
+ const textCapture = captureTextSelection();
322
+ if (textCapture) {
323
+ setPendingCapture({
324
+ capture: {
325
+ element: textCapture.element,
326
+ component: textCapture.component,
327
+ styles: textCapture.styles,
328
+ parent: undefined,
329
+ rect: textCapture.rect,
330
+ grepPattern: textCapture.grepPattern,
331
+ componentStack: textCapture.componentStack,
332
+ _scrollX: window.scrollX,
333
+ _scrollY: window.scrollY,
334
+ },
335
+ context: `"${textCapture.text}"`,
336
+ rect: textCapture.rect,
337
+ note: '',
338
+ markerType: 'text',
339
+ });
340
+ window.getSelection()?.removeAllRanges();
341
+ setAnnotating(false);
342
+ return;
343
+ }
344
+ const capture = captureElement(target);
345
+ const domRect = target.getBoundingClientRect();
346
+ setPendingCapture({
347
+ capture,
348
+ context: capture.element,
349
+ rect: { top: Math.round(domRect.top), left: Math.round(domRect.left), width: Math.round(domRect.width), height: Math.round(domRect.height) },
350
+ note: '',
351
+ markerType: 'element',
352
+ });
353
+ setAnnotating(false);
354
+ }, []);
355
+ useEffect(() => {
356
+ if (annotating) {
357
+ document.addEventListener('mousedown', handleMouseDown, true);
358
+ document.addEventListener('mousemove', handleMouseMove, true);
359
+ document.addEventListener('mouseup', handleMouseUp, true);
360
+ document.body.style.cursor = 'crosshair';
361
+ }
362
+ else {
363
+ document.body.style.cursor = '';
364
+ setHighlight(null);
365
+ setDragRect(null);
366
+ setHoverLabel(null);
367
+ setHoverLabelRect(null);
368
+ }
369
+ return () => {
370
+ document.removeEventListener('mousedown', handleMouseDown, true);
371
+ document.removeEventListener('mousemove', handleMouseMove, true);
372
+ document.removeEventListener('mouseup', handleMouseUp, true);
373
+ document.body.style.cursor = '';
374
+ };
375
+ }, [annotating, handleMouseDown, handleMouseMove, handleMouseUp]);
376
+ // ── Escape: close everything ───────────────────────────────────────────────
377
+ useEffect(() => {
378
+ const onEsc = (e) => {
379
+ if (e.key !== 'Escape')
380
+ return;
381
+ setAnnotating(false);
382
+ setIsOpen(false);
383
+ setPendingCapture(null);
384
+ setActiveMarker(null);
385
+ setShowSettings(false);
386
+ };
387
+ document.addEventListener('keydown', onEsc);
388
+ return () => document.removeEventListener('keydown', onEsc);
389
+ }, []);
390
+ // ── Send / remove ──────────────────────────────────────────────────────────
391
+ const sendAnnotation = useCallback((index) => {
392
+ const ann = annotations[index];
393
+ if (!ann)
394
+ return;
395
+ const env = captureEnvironment();
396
+ const payload = {
397
+ type: 'annotate',
398
+ note: ann.note,
399
+ element: ann.capture.element,
400
+ component: ann.capture.component,
401
+ styles: ann.capture.styles,
402
+ parent: ann.capture.parent,
403
+ grepPattern: ann.capture.grepPattern,
404
+ componentStack: ann.capture.componentStack,
405
+ domPath: ann.capture.domPath,
406
+ nearbyElements: ann.capture.nearbyElements,
407
+ position: ann.capture.rect,
408
+ environment: env,
409
+ };
410
+ if (ann.imageRef)
411
+ payload.imageRef = ann.imageRef;
412
+ // Smart fallback: MCP → clipboard → show markdown
413
+ if (send(payload)) {
414
+ // WS connected — sent to MCP
415
+ setAnnotations(prev => prev.map((a, i) => i === index ? { ...a, status: 'sent' } : a));
416
+ setSendFeedback('sending');
417
+ setTimeout(() => setSendFeedback(null), 1500);
418
+ }
419
+ else {
420
+ // WS not connected — copy to clipboard
421
+ const markdown = formatAnnotationMarkdown(ann, index);
422
+ navigator.clipboard.writeText(markdown).then(() => {
423
+ setAnnotations(prev => prev.map((a, i) => i === index ? { ...a, status: 'sent' } : a));
424
+ setClipboardToast('Copiado! Cole no seu agente (Cmd+V)');
425
+ setTimeout(() => setClipboardToast(null), 4000);
426
+ }).catch(() => {
427
+ setClipboardToast('Não foi possível copiar. Use o botão copiar.');
428
+ setTimeout(() => setClipboardToast(null), 4000);
429
+ });
430
+ }
431
+ }, [annotations]);
432
+ const sendAll = useCallback(() => {
433
+ if (isConnected()) {
434
+ // WS connected — send each via MCP
435
+ annotations.forEach((ann, i) => { if (ann.status === 'pending')
436
+ sendAnnotation(i); });
437
+ }
438
+ else {
439
+ // WS not connected — copy all as markdown
440
+ const markdown = formatAllAsMarkdown(annotations);
441
+ navigator.clipboard.writeText(markdown).then(() => {
442
+ setAnnotations(prev => prev.map(a => a.status === 'pending' ? { ...a, status: 'sent' } : a));
443
+ setClipboardToast('Copiado! Cole no seu agente (Cmd+V)');
444
+ setTimeout(() => setClipboardToast(null), 4000);
445
+ }).catch(() => {
446
+ setClipboardToast('Não foi possível copiar.');
447
+ setTimeout(() => setClipboardToast(null), 4000);
448
+ });
449
+ }
450
+ }, [annotations, sendAnnotation]);
451
+ const removeAnnotation = useCallback((index) => {
452
+ setAnnotations(prev => prev.filter((_, i) => i !== index));
453
+ if (activeMarker === index)
454
+ setActiveMarker(null);
455
+ }, [activeMarker]);
456
+ const updateNote = (index, note) => {
457
+ setAnnotations(prev => prev.map((a, i) => i === index ? { ...a, note } : a));
458
+ };
459
+ const updateImageRef = (index, imageRef) => {
460
+ setAnnotations(prev => prev.map((a, i) => i === index ? { ...a, imageRef } : a));
461
+ };
462
+ const handleClear = () => {
463
+ if (clearConfirm) {
464
+ setAnnotations([]);
465
+ setActiveMarker(null);
466
+ setClearConfirm(false);
467
+ if (clearTimerRef.current)
468
+ clearTimeout(clearTimerRef.current);
469
+ }
470
+ else {
471
+ setClearConfirm(true);
472
+ clearTimerRef.current = setTimeout(() => setClearConfirm(false), 2000);
473
+ }
474
+ };
475
+ const pendingCount = annotations.filter(a => a.status === 'pending').length;
476
+ // ── Toolbar open/close ─────────────────────────────────────────────────────
477
+ const wasDraggedRef = useRef(false);
478
+ const openToolbar = () => {
479
+ if (wasDraggedRef.current) {
480
+ wasDraggedRef.current = false;
481
+ return;
482
+ }
483
+ setIsOpen(true);
484
+ setAnnotating(true);
485
+ };
486
+ const closeToolbar = () => {
487
+ setIsOpen(false);
488
+ setAnnotating(false);
489
+ setPendingCapture(null);
490
+ setActiveMarker(null);
491
+ setShowSettings(false);
492
+ };
493
+ // ── Render ─────────────────────────────────────────────────────────────────
494
+ if (!mounted)
495
+ return null;
496
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
497
+ @keyframes ilse-pulse {
498
+ 0%, 100% { box-shadow: 0 0 0 3px rgba(255,108,3,0.4), 0 0 20px rgba(255,108,3,0.3), 0 4px 16px rgba(255,108,3,0.2); }
499
+ 50% { box-shadow: 0 0 0 6px rgba(255,108,3,0.2), 0 0 30px rgba(255,108,3,0.4), 0 4px 16px rgba(255,108,3,0.3); }
500
+ }
501
+ @keyframes ilse-pixel {
502
+ 0%, 100% { transform: scale(0.3); opacity: 0.2; }
503
+ 50% { transform: scale(1); opacity: 1; }
504
+ }
505
+ @keyframes ilse-processing {
506
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(255,108,3,0.6); }
507
+ 50% { box-shadow: 0 0 0 8px rgba(255,108,3,0); }
508
+ }
509
+ @keyframes ilse-resolved {
510
+ 0% { transform: scale(1); }
511
+ 50% { transform: scale(1.3); }
512
+ 100% { transform: scale(1); }
513
+ }
514
+ @keyframes ilse-open {
515
+ from { clip-path: inset(0 0 0 calc(100% - 52px) round 22px); }
516
+ to { clip-path: inset(0 0 0 0% round 17px); }
517
+ }
518
+ ` }), annotating && highlight && !dragRect && (_jsx("div", { style: {
519
+ position: 'fixed', top: highlight.top, left: highlight.left,
520
+ width: highlight.width, height: highlight.height,
521
+ border: '2px solid #6366f1', backgroundColor: 'rgba(99,102,241,0.08)',
522
+ pointerEvents: 'none', zIndex: 99996, borderRadius: 4,
523
+ transition: 'all 0.08s ease',
524
+ } })), annotating && hoverLabel && hoverLabelRect && !dragRect && (_jsx("div", { style: {
525
+ position: 'fixed',
526
+ top: hoverLabelRect.top + hoverLabelRect.height + 6,
527
+ left: hoverLabelRect.left,
528
+ backgroundColor: 'rgba(15,15,35,0.92)',
529
+ border: '1px solid #2d2d4a',
530
+ borderRadius: 5,
531
+ padding: '3px 8px',
532
+ fontSize: 11,
533
+ color: '#94a3b8',
534
+ pointerEvents: 'none',
535
+ zIndex: 99997,
536
+ whiteSpace: 'nowrap',
537
+ boxShadow: '0 2px 8px rgba(0,0,0,0.35)',
538
+ fontFamily: 'ui-monospace, "SF Mono", Menlo, monospace',
539
+ }, children: hoverLabel })), dragRect && (_jsx("div", { style: {
540
+ position: 'fixed', top: dragRect.top, left: dragRect.left,
541
+ width: dragRect.width, height: dragRect.height,
542
+ border: '2px dashed #6366f1', backgroundColor: 'rgba(99,102,241,0.06)',
543
+ pointerEvents: 'none', zIndex: 99996, borderRadius: 4,
544
+ } })), pendingCapture && (_jsx(AnnotationPopover, { context: pendingCapture.context, rect: pendingCapture.rect, note: pendingCapture.note, grepPattern: pendingCapture.capture.grepPattern, onNoteChange: (note) => setPendingCapture(prev => prev ? { ...prev, note } : null), onAdd: confirmCapture, onCancel: cancelCapture })), isOpen && markersVisible && annotations.map((ann, i) => {
545
+ const r = ann.capture.rect;
546
+ const scrollX = ann.capture._scrollX ?? 0;
547
+ const scrollY = ann.capture._scrollY ?? 0;
548
+ const isArea = ann.markerType === 'area';
549
+ const isSent = ann.status === 'sent';
550
+ const isResolved = ann.status === 'resolved';
551
+ const markerColor = isResolved ? '#22c55e' : isSent ? '#FF6C03' : isArea ? '#22c55e' : '#f97316';
552
+ return (_jsx("div", { "data-ilse-toolbar": true, onClick: () => setActiveMarker(activeMarker === i ? null : i), style: {
553
+ position: 'absolute',
554
+ top: isArea ? (r.top + scrollY) - 6 : (r.top + scrollY) + r.height / 2 - 11,
555
+ left: (r.left + scrollX) - 6,
556
+ width: 22, height: 22,
557
+ borderRadius: 7,
558
+ backgroundColor: markerColor,
559
+ color: 'white', fontSize: 11, fontWeight: 700,
560
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
561
+ zIndex: 99997, cursor: 'pointer',
562
+ boxShadow: '0 1px 6px rgba(0,0,0,0.35)',
563
+ fontFamily: '-apple-system, sans-serif',
564
+ outline: activeMarker === i ? '2px solid white' : 'none',
565
+ outlineOffset: 1,
566
+ animation: isSent ? 'ilse-processing 1.5s ease-in-out infinite' : isResolved ? 'ilse-resolved 0.4s ease' : 'none',
567
+ }, children: isResolved ? '\u2713' : isSent ? '\u2026' : i + 1 }, i));
568
+ }), activeMarker !== null && annotations[activeMarker] && (() => {
569
+ const ann = annotations[activeMarker];
570
+ const r = ann.capture.rect;
571
+ const scrollX = ann.capture._scrollX ?? 0;
572
+ const scrollY = ann.capture._scrollY ?? 0;
573
+ const absTop = r.top + scrollY;
574
+ const absLeft = r.left + scrollX;
575
+ let cardTop = absTop - 90;
576
+ let cardLeft = absLeft;
577
+ if (cardTop < window.scrollY + 8)
578
+ cardTop = absTop + r.height + 8;
579
+ if (cardLeft + 240 > window.scrollX + window.innerWidth - 8)
580
+ cardLeft = window.scrollX + window.innerWidth - 248;
581
+ if (cardLeft < window.scrollX + 8)
582
+ cardLeft = window.scrollX + 8;
583
+ return (_jsxs("div", { "data-ilse-toolbar": true, style: {
584
+ position: 'absolute', top: cardTop, left: cardLeft,
585
+ width: 240, backgroundColor: '#1a1a2e',
586
+ border: '1px solid #2d2d4a', borderRadius: 10, padding: 10,
587
+ zIndex: 99998, boxShadow: '0 4px 20px rgba(0,0,0,0.4)',
588
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
589
+ }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 6 }, children: [_jsx("code", { style: { fontSize: 10, color: '#a78bfa', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }, children: ann.capture.element }), _jsx("span", { style: {
590
+ fontSize: 10, padding: '1px 5px', borderRadius: 4, marginLeft: 6, flexShrink: 0,
591
+ backgroundColor: ann.status === 'pending' ? '#854d0e' : ann.status === 'sent' ? '#7c2d12' : '#14532d',
592
+ color: ann.status === 'pending' ? '#fbbf24' : ann.status === 'sent' ? '#FF6C03' : '#4ade80',
593
+ }, children: ann.status === 'sent' ? 'corrigindo...' : ann.status === 'resolved' ? 'corrigido' : 'pendente' })] }), ann.note ? (_jsx("div", { style: { fontSize: 12, color: '#e2e8f0', marginBottom: 8, lineHeight: 1.4 }, children: ann.note })) : (_jsx("div", { style: { fontSize: 12, color: '#64748b', marginBottom: 8 }, children: "sem nota" })), ann.status === 'sent' && (_jsx("div", { style: { fontSize: 11, color: '#FF6C03', marginBottom: 8, lineHeight: 1.4 }, children: "Corrigindo... Seu agente pode pedir permiss\u00E3o no terminal." })), ann.status === 'resolved' && ann.resolvedSummary && (_jsx("div", { style: { fontSize: 11, color: '#4ade80', marginBottom: 8 }, children: ann.resolvedSummary })), ann.status === 'pending' && (_jsx(AnnotationEditor, { note: ann.note, imageRef: ann.imageRef, onNoteChange: (note) => updateNote(activeMarker, note), onImageChange: (img) => updateImageRef(activeMarker, img), onSend: () => { sendAnnotation(activeMarker); setActiveMarker(null); } })), _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', marginTop: 6 }, children: _jsx("button", { onClick: () => removeAnnotation(activeMarker), style: { background: 'none', border: 'none', color: '#ef4444', cursor: 'pointer', fontSize: 11, padding: '2px 4px' }, children: "\u2715 Remover" }) })] }));
594
+ })(), _jsxs("div", { "data-ilse-toolbar": true, style: { position: 'fixed', zIndex: 99999, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }, children: [!isOpen && (_jsx("div", { style: { position: 'fixed', top: toolbarPos.y, left: toolbarPos.x }, onMouseDown: handleToolbarMouseDown, children: _jsx(IlsePixelButton, { onClick: openToolbar, badge: pendingCount > 0 ? pendingCount : undefined }) })), isOpen && (() => {
595
+ // Container anchored at the button's right edge, extending left via translateX(-100%).
596
+ // Width-agnostic: works regardless of actual pill width.
597
+ return (_jsxs("div", { style: {
598
+ position: 'fixed',
599
+ top: toolbarPos.y,
600
+ left: toolbarPos.x + PILL_W,
601
+ transform: 'translateX(-100%)',
602
+ }, children: [showSettings && (_jsxs("div", { style: {
603
+ position: 'absolute', bottom: 'calc(100% + 8px)', right: 0,
604
+ backgroundColor: '#1a1a2e', border: '1px solid #2d2d4a',
605
+ borderRadius: 10, padding: 12,
606
+ width: 200, boxShadow: '0 4px 20px rgba(0,0,0,0.4)',
607
+ color: '#e2e8f0', fontSize: 12,
608
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: '50%', backgroundColor: connected ? '#22c55e' : '#ef4444', display: 'inline-block' } }), _jsx("span", { style: { color: '#94a3b8', fontSize: 11 }, children: connected ? 'WS conectado' : 'offline' })] }), annotations.length === 0 ? (_jsx("div", { style: { color: '#64748b', textAlign: 'center', padding: '8px 0' }, children: "Nenhuma anota\u00E7\u00E3o" })) : (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [
609
+ { label: 'pendente', count: annotations.filter(a => a.status === 'pending').length, color: '#fbbf24' },
610
+ { label: 'enviado', count: annotations.filter(a => a.status === 'sent').length, color: '#60a5fa' },
611
+ { label: 'resolvido', count: annotations.filter(a => a.status === 'resolved').length, color: '#4ade80' },
612
+ ].filter(s => s.count > 0).map(s => (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#94a3b8' }, children: s.label }), _jsx("span", { style: { color: s.color, fontWeight: 600 }, children: s.count })] }, s.label))) }))] })), _jsxs("div", { onMouseDown: handleToolbarMouseDown, style: {
613
+ display: 'inline-flex', alignItems: 'center', gap: 4,
614
+ backgroundColor: '#ffffff', border: '1px solid #e5e5e5',
615
+ borderRadius: 17, padding: '8px 14px',
616
+ boxShadow: sendFeedback
617
+ ? '0 0 0 3px rgba(255,108,3,0.4), 0 0 20px rgba(255,108,3,0.3), 0 4px 16px rgba(255,108,3,0.2)'
618
+ : annotating
619
+ ? '0 2px 12px rgba(255,108,3,0.15), 0 0 0 1px rgba(255,108,3,0.2)'
620
+ : '0 2px 12px rgba(255,108,3,0.1), 0 4px 16px rgba(0,0,0,0.06)',
621
+ cursor: 'grab', userSelect: 'none',
622
+ transition: 'box-shadow 0.3s ease',
623
+ animation: sendFeedback
624
+ ? 'ilse-pulse 1s ease-in-out infinite'
625
+ : 'ilse-open 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards',
626
+ }, children: [_jsx(PillBtn, { title: paused ? 'Retomar animações' : 'Pausar animações', active: paused, onClick: () => { toggleFreeze(); setPaused(isFrozen()); }, children: paused
627
+ ? _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: _jsx("path", { d: "M4 2l10 6-10 6V2z", fill: "currentColor" }) })
628
+ : _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [_jsx("rect", { x: "2", y: "2", width: "4", height: "12", rx: "1", fill: "currentColor" }), _jsx("rect", { x: "10", y: "2", width: "4", height: "12", rx: "1", fill: "currentColor" })] }) }), _jsx(PillBtn, { title: markersVisible ? 'Ocultar marcadores' : 'Mostrar marcadores', active: !markersVisible, onClick: () => setMarkersVisible(v => !v), children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [_jsx("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "2" }), markersVisible && _jsx("circle", { cx: "8", cy: "8", r: "3", fill: "currentColor" })] }) }), _jsx(PillBtn, { title: clearConfirm ? 'Clique novamente para confirmar' : 'Limpar anotações', active: clearConfirm, danger: clearConfirm, onClick: handleClear, disabled: annotations.length === 0, children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [_jsx("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "2" }), _jsx("path", { d: "M5.5 5.5l5 5M10.5 5.5l-5 5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }) }), _jsx(PillBtn, { title: "Enviar pendentes", onClick: sendAll, disabled: pendingCount === 0, badge: pendingCount > 0 ? pendingCount : undefined, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: _jsx("path", { d: "M8.5 1L4 9h3.5v6L12 7H8.5V1z", fill: "currentColor" }) }) }), _jsx("div", { style: { width: 1, height: 18, backgroundColor: '#e0e0e0', margin: '0 6px' } }), _jsx(PillBtn, { title: "Configura\u00E7\u00F5es", active: showSettings, onClick: () => setShowSettings(v => !v), children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [_jsx("circle", { cx: "8", cy: "8", r: "2", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("path", { d: "M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }) }), _jsx(PillBtn, { title: "Fechar", onClick: closeToolbar, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: _jsx("path", { d: "M4 4l8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })] })] }));
629
+ })(), clipboardToast && (_jsx("div", { style: {
630
+ position: 'fixed',
631
+ bottom: 80,
632
+ left: '50%',
633
+ transform: 'translateX(-50%)',
634
+ backgroundColor: '#1a1a2e',
635
+ color: '#e2e8f0',
636
+ padding: '10px 20px',
637
+ borderRadius: 10,
638
+ fontSize: 13,
639
+ fontFamily: '-apple-system, sans-serif',
640
+ boxShadow: '0 4px 20px rgba(0,0,0,0.4)',
641
+ zIndex: 100000,
642
+ border: '1px solid #FF6C03',
643
+ whiteSpace: 'nowrap',
644
+ }, children: clipboardToast }))] })] }));
645
+ }
646
+ // ─── PillBtn helper ───────────────────────────────────────────────────────────
647
+ // ─── Pixel pill button (closed state) ────────────────────────────────────────
648
+ const PILL_COLORS = ['#FFFFFF', '#F6F6F6', '#FFECDE', '#FFC194', '#FF6C03', '#D9D9D9'];
649
+ // Seeded pseudo-random for deterministic grid
650
+ function seededRandom(seed) {
651
+ let s = seed;
652
+ return () => {
653
+ s = (s * 16807 + 0) % 2147483647;
654
+ return (s - 1) / 2147483646;
655
+ };
656
+ }
657
+ // Opacity per color index — lighter colors are more transparent
658
+ const COLOR_OPACITY = [0.22, 0.25, 0.35, 0.55, 1.0, 0.20]; // #FFF, #F6F6, #FFECDE, #FFC194, #FF6C03, #D9D9
659
+ function generatePixelGrid(cols, rows) {
660
+ const rand = seededRandom(42);
661
+ const grid = [];
662
+ for (let y = 0; y < rows; y++) {
663
+ for (let x = 0; x < cols; x++) {
664
+ const distFromCenter = Math.sqrt(Math.pow((x - cols / 2) / (cols / 2), 2) +
665
+ Math.pow((y - rows / 2) / (rows / 2), 2));
666
+ // Radial soft edge — outer pixels excluded progressively (organic shape)
667
+ const r0 = rand();
668
+ if (distFromCenter > 1.0)
669
+ continue;
670
+ if (distFromCenter > 0.85 && r0 < 0.55)
671
+ continue;
672
+ if (distFromCenter > 0.70 && r0 < 0.20)
673
+ continue;
674
+ // Small random holes in the dense core (~10%)
675
+ if (distFromCenter < 0.70 && r0 < 0.10)
676
+ continue;
677
+ // More orange in center, lighter at edges
678
+ const r = rand();
679
+ let colorIdx;
680
+ if (distFromCenter < 0.5) {
681
+ colorIdx = r < 0.5 ? 4 : r < 0.75 ? 3 : r < 0.9 ? 2 : 0;
682
+ }
683
+ else {
684
+ colorIdx = r < 0.25 ? 4 : r < 0.5 ? 3 : r < 0.7 ? 2 : r < 0.85 ? 0 : 1;
685
+ }
686
+ const isOrange = colorIdx === 4;
687
+ // Radial brightness falloff — edges fade out smoothly
688
+ const radialFade = Math.max(0.3, 1 - distFromCenter * 0.5);
689
+ const baseBrightness = COLOR_OPACITY[colorIdx] ?? 0.3;
690
+ grid.push({
691
+ x, y,
692
+ color: PILL_COLORS[colorIdx],
693
+ size: 2 + Math.floor(rand() * 3),
694
+ delay: rand() * 5,
695
+ duration: 1.8 + rand() * 3,
696
+ brightness: baseBrightness * radialFade,
697
+ glow: isOrange,
698
+ });
699
+ }
700
+ }
701
+ return grid;
702
+ }
703
+ const PILL_W = 52;
704
+ const PILL_H = 44;
705
+ const CELL = 4;
706
+ const PIXEL_DATA = generatePixelGrid(Math.floor(PILL_W / CELL), Math.floor(PILL_H / CELL));
707
+ function IlsePixelButton({ onClick, badge }) {
708
+ return (_jsxs("button", { onClick: onClick, style: {
709
+ width: PILL_W, height: PILL_H,
710
+ position: 'relative',
711
+ background: 'none',
712
+ border: 'none',
713
+ cursor: 'grab', userSelect: 'none',
714
+ padding: 0, display: 'block',
715
+ }, children: [PIXEL_DATA.map((p, i) => (_jsx("div", { style: {
716
+ position: 'absolute',
717
+ top: p.y * CELL + (CELL - p.size) / 2,
718
+ left: p.x * CELL + (CELL - p.size) / 2,
719
+ width: p.size,
720
+ height: p.size,
721
+ backgroundColor: p.color,
722
+ opacity: p.brightness,
723
+ animation: `ilse-pixel ${p.duration.toFixed(1)}s ease-in-out ${p.delay.toFixed(1)}s infinite`,
724
+ boxShadow: p.glow ? '0 0 6px rgba(255,108,3,0.5), 0 0 12px rgba(255,108,3,0.2)' : 'none',
725
+ } }, i))), badge !== undefined && (_jsx("span", { style: {
726
+ position: 'absolute', top: -4, right: -4,
727
+ backgroundColor: '#FF6C03', color: 'white',
728
+ fontSize: 9, borderRadius: 5, width: 16, height: 16,
729
+ display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700,
730
+ zIndex: 1,
731
+ }, children: badge }))] }));
732
+ }
733
+ // ─── PillBtn helper ───────────────────────────────────────────────────────────
734
+ function PillBtn({ children, title, onClick, active, danger, disabled, badge, }) {
735
+ return (_jsxs("button", { title: title, onClick: (e) => { e.stopPropagation(); onClick?.(); }, disabled: disabled, style: {
736
+ background: active ? (danger ? 'rgba(239,68,68,0.08)' : 'rgba(255,108,3,0.1)') : 'none',
737
+ border: 'none',
738
+ color: disabled ? '#c0c0c0' : danger ? '#ef4444' : active ? '#FF6C03' : '#2D2D2D',
739
+ cursor: disabled ? 'default' : 'pointer',
740
+ fontSize: 16,
741
+ padding: '6px 9px',
742
+ borderRadius: 8,
743
+ lineHeight: 1,
744
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
745
+ position: 'relative',
746
+ transition: 'color 0.15s, background 0.15s',
747
+ }, onMouseEnter: (e) => { if (!disabled)
748
+ e.currentTarget.style.background = danger ? 'rgba(239,68,68,0.08)' : 'rgba(255,108,3,0.06)'; }, onMouseLeave: (e) => { e.currentTarget.style.background = active ? (danger ? 'rgba(239,68,68,0.08)' : 'rgba(255,108,3,0.1)') : 'none'; }, children: [children, badge !== undefined && (_jsx("span", { style: {
749
+ position: 'absolute', top: -3, right: -3,
750
+ backgroundColor: '#FF6C03', color: 'white',
751
+ fontSize: 9, borderRadius: 5, width: 14, height: 14,
752
+ display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700,
753
+ }, children: badge }))] }));
754
+ }
755
+ //# sourceMappingURL=toolbar.js.map