@vite-plugin-opencode-assistant/components 1.0.33 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/es/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import OpenCodeWidget from './open-code-widget';
2
2
  import type { App } from 'vue';
3
- declare const version = "1.0.33";
3
+ declare const version = "1.0.35";
4
4
  declare function install(app: App<any>, options?: any): void;
5
5
  export { install, version, OpenCodeWidget };
6
6
  export default install;
package/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import OpenCodeWidget from "./open-code-widget";
2
- const version = "1.0.33";
2
+ const version = "1.0.35";
3
3
  function install(app, options) {
4
4
  const components = [
5
5
  OpenCodeWidget
@@ -1,23 +1,17 @@
1
1
  import { ref, watch, onMounted, onUnmounted } from "vue";
2
2
  import { truncate } from "@vite-plugin-opencode-assistant/shared";
3
3
  import getCssSelector from "css-selector-generator";
4
- function throttle(fn, delay) {
5
- let lastCall = 0;
6
- let rafId = null;
7
- return ((...args) => {
8
- const now = performance.now();
9
- if (now - lastCall >= delay) {
10
- lastCall = now;
11
- fn(...args);
12
- } else if (!rafId) {
13
- rafId = requestAnimationFrame(() => {
14
- rafId = null;
15
- lastCall = performance.now();
16
- fn(...args);
17
- });
18
- }
19
- });
20
- }
4
+ const IGNORE_SELECTORS = [
5
+ "#vue-inspector-container",
6
+ ".opencode-widget",
7
+ ".opencode-element-highlight",
8
+ ".opencode-element-tooltip",
9
+ ".opencode-select-mode-hint",
10
+ ".floating-bubble"
11
+ ];
12
+ const IGNORE_ATTRIBUTE = "data-v-inspector-ignore";
13
+ const KEY_PROPS_DATA = "__v_inspector";
14
+ const KEY_DATA = "data-v-inspector";
21
15
  function getDirectText(element) {
22
16
  let text = "";
23
17
  for (let i = 0; i < element.childNodes.length; i++) {
@@ -66,74 +60,27 @@ function isStateClass(className) {
66
60
  }
67
61
  return false;
68
62
  }
69
- function filterStateClasses(classes) {
70
- return classes.filter((cls) => !isStateClass(cls));
71
- }
72
63
  function getElementDescription(element) {
73
- try {
74
- const selector = getCssSelector(element, {
75
- selectors: ["id", "class", "tag", "nthchild"],
76
- combineWithinSelector: true,
77
- combineBetweenSelectors: true,
78
- maxCombinations: 100,
79
- maxCandidates: 100,
80
- blacklist: [
81
- (selectorValue) => {
82
- const idMatch = selectorValue.match(/^#(.+)$/);
83
- if (idMatch) {
84
- return isDynamicId(idMatch[1]);
85
- }
86
- const classMatch = selectorValue.match(/^\.([a-zA-Z_-][\w-]*)$/);
87
- if (classMatch) {
88
- return isStateClass(classMatch[1]);
89
- }
90
- return false;
64
+ return getCssSelector(element, {
65
+ selectors: ["id", "class", "tag", "nthchild"],
66
+ combineWithinSelector: true,
67
+ combineBetweenSelectors: true,
68
+ maxCombinations: 100,
69
+ maxCandidates: 100,
70
+ blacklist: [
71
+ (selectorValue) => {
72
+ const idMatch = selectorValue.match(/^#(.+)$/);
73
+ if (idMatch) {
74
+ return isDynamicId(idMatch[1]);
91
75
  }
92
- ]
93
- });
94
- return selector;
95
- } catch (e) {
96
- const tag = element.tagName.toLowerCase();
97
- const parts = [tag];
98
- const id = element.id;
99
- if (id && !isDynamicId(id)) parts.push(`#${id}`);
100
- const className = element.className;
101
- if (typeof className === "string") {
102
- const classes = filterStateClasses(className.trim().split(/\s+/).filter(Boolean)).slice(0, 2);
103
- if (classes.length > 0) parts.push(`.${classes.join(".")}`);
104
- } else {
105
- const svgClass = className.baseVal;
106
- if (svgClass) {
107
- const classes = filterStateClasses(svgClass.trim().split(/\s+/).filter(Boolean)).slice(
108
- 0,
109
- 2
110
- );
111
- if (classes.length > 0) parts.push(`.${classes.join(".")}`);
76
+ const classMatch = selectorValue.match(/^\.([a-zA-Z_-][\w-]*)$/);
77
+ if (classMatch) {
78
+ return isStateClass(classMatch[1]);
79
+ }
80
+ return false;
112
81
  }
113
- }
114
- const name = element.getAttribute("name");
115
- if (name) parts.push(`[name="${name}"]`);
116
- const placeholder = element.getAttribute("placeholder");
117
- if (placeholder) parts.push(`[placeholder="${placeholder.substring(0, 20)}"]`);
118
- const src = element.getAttribute("src");
119
- if (src) parts.push(`[src]`);
120
- const href = element.getAttribute("href");
121
- if (href && href !== "#") parts.push(`[href]`);
122
- return parts.join("");
123
- }
124
- }
125
- function getFileInfoFromAttributes(element) {
126
- const file = element.getAttribute("data-v-inspector-file");
127
- if (file) {
128
- const line = element.getAttribute("data-v-inspector-line");
129
- const column = element.getAttribute("data-v-inspector-column");
130
- return {
131
- file,
132
- line: line ? parseInt(line, 10) : null,
133
- column: column ? parseInt(column, 10) : null
134
- };
135
- }
136
- return null;
82
+ ]
83
+ });
137
84
  }
138
85
  function getFileInfoFromVueInstance(element) {
139
86
  var _a, _b, _c, _d;
@@ -161,61 +108,83 @@ function getFileInfoFromVueInstance(element) {
161
108
  }
162
109
  return null;
163
110
  }
164
- function findFileInfo(element, inspector) {
111
+ function shouldIgnoreElement(el) {
112
+ if (el.hasAttribute(IGNORE_ATTRIBUTE)) return true;
113
+ for (const selector of IGNORE_SELECTORS) {
114
+ if (el.closest(selector)) return true;
115
+ }
116
+ return false;
117
+ }
118
+ function getDataFromElement(el) {
165
119
  var _a, _b;
120
+ const vnodeData = (_b = (_a = el.__vnode) == null ? void 0 : _a.props) == null ? void 0 : _b[KEY_PROPS_DATA];
121
+ if (vnodeData) return vnodeData;
122
+ const attr = el.getAttribute(KEY_DATA);
123
+ return attr != null ? attr : void 0;
124
+ }
125
+ function findInspectorFileInfo(element) {
166
126
  let current = element;
167
- let fallbackFileInfo = null;
168
127
  while (current) {
169
- const attrInfo = getFileInfoFromAttributes(current);
170
- if (attrInfo && attrInfo.line !== null) {
171
- return attrInfo;
128
+ const data = getDataFromElement(current);
129
+ if (data) {
130
+ const splitRE = /(.+):([\d]+):([\d]+)$/;
131
+ const match = data.match(splitRE);
132
+ if (match) {
133
+ return {
134
+ file: match[1],
135
+ line: parseInt(match[2], 10),
136
+ column: parseInt(match[3], 10)
137
+ };
138
+ }
172
139
  }
173
- if (attrInfo && !fallbackFileInfo) {
174
- fallbackFileInfo = attrInfo;
140
+ current = current.parentElement;
141
+ }
142
+ return null;
143
+ }
144
+ function mergeFileInfo(inspectorFileInfo, vueFileInfo) {
145
+ if (!(inspectorFileInfo == null ? void 0 : inspectorFileInfo.file) && !(vueFileInfo == null ? void 0 : vueFileInfo.file)) {
146
+ return { file: null, line: null, column: null };
147
+ }
148
+ const isNodeModules = (path) => path.includes("node_modules");
149
+ if ((inspectorFileInfo == null ? void 0 : inspectorFileInfo.file) && (vueFileInfo == null ? void 0 : vueFileInfo.file)) {
150
+ if (!isNodeModules(inspectorFileInfo.file)) {
151
+ return inspectorFileInfo;
152
+ } else if (!isNodeModules(vueFileInfo.file)) {
153
+ return vueFileInfo;
154
+ } else {
155
+ return inspectorFileInfo;
175
156
  }
176
- const fakeEvent = {
177
- clientX: 0,
178
- clientY: 0,
179
- target: current,
180
- currentTarget: current
181
- };
182
- const { params } = inspector.getTargetNode(fakeEvent);
183
- if (params && params.file) {
184
- const info = {
157
+ } else if (inspectorFileInfo == null ? void 0 : inspectorFileInfo.file) {
158
+ return inspectorFileInfo;
159
+ } else {
160
+ return vueFileInfo;
161
+ }
162
+ }
163
+ function getTargetElement(e) {
164
+ if (!e.target || !(e.target instanceof Element)) return null;
165
+ const el = e.target;
166
+ if (shouldIgnoreElement(el)) return null;
167
+ return el;
168
+ }
169
+ function getFileInfo(e, element) {
170
+ var _a, _b;
171
+ const inspector = window.__VUE_INSPECTOR__;
172
+ let inspectorFileInfo = null;
173
+ if (inspector) {
174
+ const { targetNode, params } = inspector.getTargetNode(e);
175
+ if (targetNode && params && params.file) {
176
+ inspectorFileInfo = {
185
177
  file: params.file,
186
178
  line: (_a = params.line) != null ? _a : null,
187
179
  column: (_b = params.column) != null ? _b : null
188
180
  };
189
- if (info.line !== null) {
190
- return info;
191
- }
192
- if (!fallbackFileInfo) {
193
- fallbackFileInfo = info;
194
- }
195
181
  }
196
- const vueInfo = getFileInfoFromVueInstance(current);
197
- if (vueInfo && !fallbackFileInfo) {
198
- fallbackFileInfo = vueInfo;
199
- }
200
- current = current.parentElement;
201
182
  }
202
- return fallbackFileInfo || { file: null, line: null, column: null };
203
- }
204
- function getPreciseElementAtPoint(x, y, boundary) {
205
- const elements = document.elementsFromPoint(x, y);
206
- for (const el of elements) {
207
- if (el.closest("#vue-inspector-container")) continue;
208
- if (el.closest(".opencode-widget")) continue;
209
- if (el.hasAttribute("data-v-inspector-ignore")) continue;
210
- if (boundary) {
211
- if (boundary.contains(el) || el === boundary) {
212
- return el;
213
- }
214
- } else {
215
- return el;
216
- }
183
+ if (element && !inspectorFileInfo) {
184
+ inspectorFileInfo = findInspectorFileInfo(element);
217
185
  }
218
- return null;
186
+ const vueFileInfo = element ? getFileInfoFromVueInstance(element) : null;
187
+ return mergeFileInfo(inspectorFileInfo, vueFileInfo);
219
188
  }
220
189
  function useInspector(options) {
221
190
  const highlightVisible = ref(false);
@@ -230,84 +199,50 @@ function useInspector(options) {
230
199
  const tooltipContent = ref({ description: "", fileInfo: "" });
231
200
  const INSPECTOR_CHECK_INTERVAL = 500;
232
201
  let inspectorCheckTimer = null;
233
- let currentHighlightElement = null;
234
- let currentFileInfo = { file: null, line: null, column: null };
235
202
  let currentPrimary = "#3b82f6";
236
203
  let currentPrimaryBg = "rgba(59, 130, 246, 0.1)";
237
- let currentDescription = "";
238
- let currentFileInfoText = "";
204
+ function setPointerEventsNone(elements) {
205
+ elements.forEach((el) => {
206
+ if (el) el.style.pointerEvents = "none";
207
+ });
208
+ }
209
+ function setPointerEventsAuto(elements) {
210
+ elements.forEach((el) => {
211
+ if (el) el.style.pointerEvents = "";
212
+ });
213
+ }
239
214
  function handleMouseMoveCore(e) {
240
- var _a, _b;
241
215
  if (!options.selectMode.value) return;
242
- const inspector = window.__VUE_INSPECTOR__;
243
216
  const highlight = document.querySelector(".opencode-element-highlight");
244
217
  const tooltip = document.querySelector(".opencode-element-tooltip");
245
- if (highlight) highlight.style.pointerEvents = "none";
246
- if (tooltip) tooltip.style.pointerEvents = "none";
247
- let elementToHighlight = null;
248
- let targetNode;
249
- let fileInfo = { file: null, line: null, column: null };
250
- try {
251
- if (inspector) {
252
- const result = inspector.getTargetNode(e);
253
- targetNode = result.targetNode;
254
- const params = result.params;
255
- if (targetNode) {
256
- const preciseElement = getPreciseElementAtPoint(e.clientX, e.clientY, targetNode);
257
- elementToHighlight = preciseElement || targetNode;
258
- if (params && params.file) {
259
- fileInfo = {
260
- file: params.file,
261
- line: (_a = params.line) != null ? _a : null,
262
- column: (_b = params.column) != null ? _b : null
263
- };
264
- } else {
265
- fileInfo = findFileInfo(targetNode, inspector);
266
- }
267
- }
268
- }
269
- if (!elementToHighlight) {
270
- elementToHighlight = getPreciseElementAtPoint(e.clientX, e.clientY, null);
271
- }
272
- if (elementToHighlight && !fileInfo.file) {
273
- fileInfo = getFileInfoFromVueInstance(elementToHighlight) || fileInfo;
274
- }
275
- } finally {
276
- if (highlight) highlight.style.pointerEvents = "";
277
- if (tooltip) tooltip.style.pointerEvents = "";
278
- }
218
+ const selectHint = document.querySelector(".opencode-select-mode-hint");
219
+ const floatingBubble = document.querySelector(".floating-bubble");
220
+ const uiElements = [highlight, tooltip, selectHint, floatingBubble];
221
+ setPointerEventsNone(uiElements);
222
+ const elementToHighlight = getTargetElement(e);
223
+ const fileInfo = getFileInfo(e, elementToHighlight);
224
+ setPointerEventsAuto(uiElements);
279
225
  if (elementToHighlight) {
280
- const elementChanged = currentHighlightElement !== elementToHighlight;
281
- if (elementChanged) {
282
- currentHighlightElement = elementToHighlight;
283
- currentFileInfo = fileInfo;
284
- const widget = document.querySelector(".opencode-widget");
285
- if (widget) {
286
- const style = getComputedStyle(widget);
287
- currentPrimary = style.getPropertyValue("--oc-primary").trim() || currentPrimary;
288
- currentPrimaryBg = style.getPropertyValue("--oc-primary-bg").trim() || currentPrimaryBg;
289
- }
290
- currentDescription = getElementDescription(elementToHighlight);
291
- } else if (!currentFileInfo.file && fileInfo.file) {
292
- currentFileInfo = fileInfo;
226
+ const widget = document.querySelector(".opencode-widget");
227
+ if (widget) {
228
+ const style = getComputedStyle(widget);
229
+ currentPrimary = style.getPropertyValue("--oc-primary").trim() || currentPrimary;
230
+ currentPrimaryBg = style.getPropertyValue("--oc-primary-bg").trim() || currentPrimaryBg;
293
231
  }
294
- const fileName = currentFileInfo.file ? currentFileInfo.file.split("/").pop() : "";
232
+ const description = getElementDescription(elementToHighlight);
233
+ const fileName = fileInfo.file ? fileInfo.file.split("/").pop() : "";
295
234
  let lineInfo = "";
296
- if (currentFileInfo.line) {
297
- lineInfo = `:${currentFileInfo.line}`;
298
- if (currentFileInfo.column) {
299
- lineInfo += `:${currentFileInfo.column}`;
235
+ if (fileInfo.line) {
236
+ lineInfo = `:${fileInfo.line}`;
237
+ if (fileInfo.column) {
238
+ lineInfo += `:${fileInfo.column}`;
300
239
  }
301
240
  }
302
- const newFileInfoText = fileName ? `${fileName}${lineInfo}` : "";
303
- const fileInfoChanged = currentFileInfoText !== newFileInfoText;
304
- if (elementChanged || fileInfoChanged) {
305
- currentFileInfoText = newFileInfoText;
306
- tooltipContent.value = {
307
- description: currentDescription,
308
- fileInfo: currentFileInfoText
309
- };
310
- }
241
+ const fileInfoText = fileName ? `${fileName}${lineInfo}` : "";
242
+ tooltipContent.value = {
243
+ description,
244
+ fileInfo: fileInfoText
245
+ };
311
246
  const rect = elementToHighlight.getBoundingClientRect();
312
247
  const newTop = `${rect.top}px`;
313
248
  const newLeft = `${rect.left}px`;
@@ -325,13 +260,20 @@ function useInspector(options) {
325
260
  }
326
261
  const tooltipHeight = 50;
327
262
  const tooltipWidth = 200;
263
+ const margin = 10;
328
264
  let tooltipTop = rect.top - tooltipHeight - 8;
329
265
  let tooltipLeft = rect.left;
330
- if (tooltipTop < 10) {
266
+ if (tooltipTop < margin) {
331
267
  tooltipTop = rect.bottom + 8;
332
268
  }
333
- if (tooltipLeft + tooltipWidth > window.innerWidth - 10) {
334
- tooltipLeft = window.innerWidth - tooltipWidth - 10;
269
+ if (tooltipTop + tooltipHeight > window.innerHeight - margin) {
270
+ tooltipTop = Math.max(margin, rect.top - tooltipHeight - 8);
271
+ }
272
+ if (tooltipLeft < margin) {
273
+ tooltipLeft = margin;
274
+ }
275
+ if (tooltipLeft + tooltipWidth > window.innerWidth - margin) {
276
+ tooltipLeft = window.innerWidth - tooltipWidth - margin;
335
277
  }
336
278
  const newTooltipTop = `${tooltipTop}px`;
337
279
  const newTooltipLeft = `${tooltipLeft}px`;
@@ -341,57 +283,24 @@ function useInspector(options) {
341
283
  left: newTooltipLeft
342
284
  };
343
285
  }
344
- if (!highlightVisible.value) {
345
- highlightVisible.value = true;
346
- }
347
- if (!tooltipVisible.value) {
348
- tooltipVisible.value = true;
349
- }
286
+ highlightVisible.value = true;
287
+ tooltipVisible.value = true;
350
288
  } else {
351
- currentHighlightElement = null;
352
- currentDescription = "";
353
- currentFileInfoText = "";
354
- currentFileInfo = { file: null, line: null, column: null };
355
- if (highlightVisible.value) {
356
- highlightVisible.value = false;
357
- }
358
- if (tooltipVisible.value) {
359
- tooltipVisible.value = false;
360
- }
289
+ highlightVisible.value = false;
290
+ tooltipVisible.value = false;
361
291
  }
362
292
  }
363
- const handleMouseMove = throttle(handleMouseMoveCore, 16);
293
+ const handleMouseMove = handleMouseMoveCore;
364
294
  function setupInspectorHook() {
365
295
  const inspector = window.__VUE_INSPECTOR__;
366
296
  if (!inspector || inspector.__opencode_hooked) return;
367
297
  const originalHandleClick = inspector.handleClick.bind(inspector);
368
298
  inspector.handleClick = function(e) {
369
- var _a, _b;
370
299
  if (options.selectMode.value) {
371
300
  e.preventDefault();
372
301
  e.stopPropagation();
373
- let elementToSelect = null;
374
- let fileInfo = { file: null, line: null, column: null };
375
- const { targetNode, params } = inspector.getTargetNode(e);
376
- if (targetNode) {
377
- const preciseElement = getPreciseElementAtPoint(e.clientX, e.clientY, targetNode);
378
- elementToSelect = preciseElement || targetNode;
379
- if (params && params.file) {
380
- fileInfo = {
381
- file: params.file,
382
- line: (_a = params.line) != null ? _a : null,
383
- column: (_b = params.column) != null ? _b : null
384
- };
385
- } else if (elementToSelect) {
386
- fileInfo = findFileInfo(elementToSelect, inspector);
387
- }
388
- }
389
- if (!elementToSelect) {
390
- elementToSelect = getPreciseElementAtPoint(e.clientX, e.clientY, null);
391
- }
392
- if (elementToSelect && !fileInfo.file) {
393
- fileInfo = getFileInfoFromVueInstance(elementToSelect) || fileInfo;
394
- }
302
+ const elementToSelect = getTargetElement(e);
303
+ const fileInfo = getFileInfo(e, elementToSelect);
395
304
  if (elementToSelect) {
396
305
  const innerText = getDirectText(elementToSelect);
397
306
  const description = getElementDescription(elementToSelect);
@@ -431,10 +340,6 @@ function useInspector(options) {
431
340
  }
432
341
  document.removeEventListener("mousemove", handleMouseMove);
433
342
  document.removeEventListener("keydown", handleKeydown, true);
434
- currentHighlightElement = null;
435
- currentDescription = "";
436
- currentFileInfoText = "";
437
- currentFileInfo = { file: null, line: null, column: null };
438
343
  highlightVisible.value = false;
439
344
  tooltipVisible.value = false;
440
345
  }
@@ -1 +1 @@
1
- .opencode-button{width:42px;height:42px;border-radius:50%;background:#fff;border:none;cursor:pointer;box-shadow:0 4px 12px rgba(102,126,234,.4);transition:all .3s ease;display:flex;align-items:center;justify-content:center;padding:0;position:relative}.opencode-button svg{transform:rotate(180deg) scale(1.1);transition:transform .3s ease;width:100%;height:100%;display:block}.opencode-button:hover svg{transform:rotate(180deg) scale(1.1)}.opencode-button:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(102,126,234,.5)}.opencode-button.thinking{background:linear-gradient(135deg,#667eea,#764ba2);animation:thinking-glow 1.5s ease-in-out infinite,thinking-pulse 1.5s ease-in-out infinite;box-shadow:0 0 20px rgba(102,126,234,.6),0 0 40px rgba(118,75,162,.4),0 0 60px rgba(102,126,234,.2)}.opencode-button.thinking svg path{fill:#fff}.opencode-button.thinking:before{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:50%;background:linear-gradient(135deg,#8b9cf5,#9d6bc7);z-index:-1}.opencode-button.thinking:after{content:"";position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border-radius:50%;background:conic-gradient(from 180deg,transparent,rgba(102,126,234,.3),transparent,rgba(118,75,162,.3),transparent);z-index:-2;animation:thinking-rotate 2s linear infinite reverse;filter:blur(8px)}@keyframes thinking-glow{0%,to{box-shadow:0 0 20px rgba(102,126,234,.6),0 0 40px rgba(118,75,162,.4),0 0 60px rgba(102,126,234,.2)}50%{box-shadow:0 0 30px rgba(102,126,234,.8),0 0 60px rgba(118,75,162,.6),0 0 90px rgba(102,126,234,.3)}}@keyframes thinking-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes thinking-pulse{0%,to{transform:scale(1)}50%{transform:scale(.85)}}.opencode-button.opencode-theme-dark{background:linear-gradient(135deg,#667eea,#764ba2);box-shadow:0 4px 12px rgba(102,126,234,.3)}.opencode-button.opencode-theme-dark:before{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:50%;background:linear-gradient(135deg,#8b9cf5,#9d6bc7);z-index:-1}.opencode-button.opencode-theme-dark:hover{box-shadow:0 6px 16px rgba(102,126,234,.4)}.opencode-button.opencode-theme-dark svg path{fill:#fff}
1
+ .opencode-button{width:42px;height:42px;border-radius:50%;background:#fff;border:none;cursor:pointer;box-shadow:0 4px 12px rgba(102,126,234,.4);transition:all .3s ease;display:flex;align-items:center;justify-content:center;padding:0;position:relative}.opencode-button svg{transform:rotate(180deg) scale(1.1);transition:transform .3s ease;width:100%;height:100%;display:block}.opencode-button:hover svg{transform:rotate(180deg) scale(1.1)}.opencode-button:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(102,126,234,.5)}.opencode-button.thinking{background:linear-gradient(135deg,#667eea,#764ba2);animation:thinking-glow 2s ease-in-out infinite,thinking-pulse 2s ease-in-out infinite;box-shadow:0 0 20px rgba(102,126,234,.6),0 0 40px rgba(118,75,162,.4),0 0 60px rgba(102,126,234,.2)}.opencode-button.thinking svg path{fill:#fff}.opencode-button.thinking:before{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:50%;background:linear-gradient(135deg,#8b9cf5,#9d6bc7);z-index:-1}.opencode-button.thinking:after{content:"";position:absolute;top:-3px;right:-3px;bottom:-3px;left:-3px;border-radius:50%;background:conic-gradient(from 180deg,transparent,rgba(102,126,234,.3),transparent,rgba(118,75,162,.3),transparent);z-index:-2;animation:thinking-rotate 2s linear infinite reverse;filter:blur(8px)}@keyframes thinking-glow{0%,to{box-shadow:0 0 20px rgba(102,126,234,.6),0 0 40px rgba(118,75,162,.4),0 0 60px rgba(102,126,234,.2)}50%{box-shadow:0 0 30px rgba(102,126,234,.8),0 0 60px rgba(118,75,162,.6),0 0 90px rgba(102,126,234,.3)}}@keyframes thinking-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes thinking-pulse{0%,to{transform:scale(1)}50%{transform:scale(.92)}}.opencode-button.opencode-theme-dark{background:linear-gradient(135deg,#667eea,#764ba2);box-shadow:0 4px 12px rgba(102,126,234,.3)}.opencode-button.opencode-theme-dark:before{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:50%;background:linear-gradient(135deg,#8b9cf5,#9d6bc7);z-index:-1}.opencode-button.opencode-theme-dark:hover{box-shadow:0 6px 16px rgba(102,126,234,.4)}.opencode-button.opencode-theme-dark svg path{fill:#fff}