@emmaexcel/shakecursor 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Framework-agnostic browser SDK for contextual AI selection overlays.
5
5
  ## NPM-style usage
6
6
 
7
7
  ```ts
8
- import { AIOverlay } from '@shakecursor/overlay-core'
8
+ import { AIOverlay } from '@emmaexcel/shakecursor'
9
9
 
10
10
  const overlay = AIOverlay.init({
11
11
  siteKey: 'pk_demo_shakecursor',
@@ -1,10 +1,21 @@
1
- (function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e,t,n){return Math.min(Math.max(e,t),n)}function n(e){return e.replace(/\s+/g,` `).trim()}function r(e,t=[]){return t.some(t=>{try{return e.matches(t)||!!e.closest(t)}catch{return!1}})}function i(){return/Mac|iPhone|iPad|iPod/i.test(window.navigator.platform)}function a(e){if(e.rangeCount===0)return null;let t=e.getRangeAt(0),n=t.getBoundingClientRect();return n.width>0||n.height>0?n:t.getClientRects()[0]??null}function o(e){return{rect:e,url:window.location.href,title:document.title}}function s(e){let t=n(e.innerText??``),r=e.tagName.toLowerCase(),i=e.getAttribute(`aria-label`),a=e.getAttribute(`title`),o=[i,a,t].find(Boolean);return{label:o?`${r}: ${o.slice(0,64)}`:r,content:[`Selected element: <${r}>`,i?`ARIA label: ${i}`:``,a?`Title: ${a}`:``,t?`Visible text: ${t.slice(0,5e3)}`:``].filter(Boolean).join(`
2
- `)}}function c(e){let t=e.currentSrc||e.src,n=e.alt||`No alt text`;return{label:`image: ${n.slice(0,64)}`,content:[`Selected image`,`Alt text: ${n}`,`Source: ${t}`,`Rendered size: ${Math.round(e.width)}x${Math.round(e.height)}`,`Natural size: ${e.naturalWidth}x${e.naturalHeight}`].join(`
3
- `)}}function l(e,t){return!(r(e,t.blockedSelectors)||t.allowedSelectors.length>0&&!r(e,t.allowedSelectors))}function u(e){let t=t=>{if(!e.isActive())return;let n=t.target;if(!(n instanceof HTMLElement)||e.isOverlayElement(n)){e.onHover(null);return}if(!l(n,e.config)){e.onHover(null);return}e.onHover(n.getBoundingClientRect())},r=()=>{!e.isActive()||!e.config.text||window.setTimeout(()=>{let t=window.getSelection(),r=n(t?.toString()??``);if(!t||!r)return;let i=a(t),s=t.anchorNode?.parentElement;!i||!s||!l(s,e.config)||e.onSelection({kind:`text`,label:`highlighted text`,content:r,...o(i)})},0)},i=t=>{if(!e.isActive())return;let r=t.target;if(!(r instanceof HTMLElement)||e.isOverlayElement(r)||!l(r,e.config)||n(window.getSelection()?.toString()??``))return;let i=r instanceof HTMLImageElement?r:r.querySelector(`img`),a=!!i;if(a&&!e.config.images||!a&&!e.config.elements)return;t.preventDefault(),t.stopPropagation();let u=i?c(i):s(r),d=i?i.getBoundingClientRect():r.getBoundingClientRect();e.onSelection({kind:i?`image`:`element`,label:u.label,content:u.content,...o(d)})};return document.addEventListener(`pointerover`,t),document.addEventListener(`mouseup`,r),document.addEventListener(`click`,i,!0),()=>{document.removeEventListener(`pointerover`,t),document.removeEventListener(`mouseup`,r),document.removeEventListener(`click`,i,!0)}}function d(e){let t=e.windowMs??650,n=e.cooldownMs??1e3,r=e.minSamples??7,i=e.minReversals??5,a=e.minDistance??340,o=e.minDeltaX??16,s=[],c=0,l=l=>{let u=performance.now();if(s.push({x:l.clientX,y:l.clientY,time:u}),s=s.filter(e=>u-e.time<t),s.length<r||u-c<n)return;let d=0,f=0,p=0;for(let e=1;e<s.length;e+=1){let t=s[e].x-s[e-1].x,n=s[e].y-s[e-1].y;if(f+=Math.hypot(t,n),Math.abs(t)<o)continue;let r=Math.sign(t);p!==0&&r!==p&&(d+=1),p=r}d>=i&&f>a&&(c=u,s=[],e.onShake())};return document.addEventListener(`pointermove`,l),()=>{document.removeEventListener(`pointermove`,l)}}var f=`http://localhost:11434/api/chat`,p=`qwen3-coder:480b-cloud`,m=`https://shakeai.onrender.com`;async function h(e,t){let n=e.provider??`ollama`,r=e.endpoint??f,i=e.model??p;if(n===`custom`){let n=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify(t)});if(!n.ok)throw Error(`Custom endpoint returned ${n.status}`);let i=await n.json();return typeof i==`string`?i:i&&typeof i==`object`&&`answer`in i&&typeof i.answer==`string`?i.answer:i&&typeof i==`object`&&`content`in i&&typeof i.content==`string`?i.content:JSON.stringify(i,null,2)}let a=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify({model:i,stream:!1,messages:[{role:`system`,content:`You are an in-page AI assistant. Use the selected website context to answer or transform content. Be direct and practical.`},{role:`user`,content:[`Page title: ${t.selection.title}`,`Page URL: ${t.selection.url}`,`Selection type: ${t.selection.kind}`,`Selection label: ${t.selection.label}`,`Selected context:`,t.selection.content,``,`User request: ${t.question}`].join(`
4
- `)}]})});if(!a.ok)throw Error(`Ollama returned ${a.status}`);let o=await a.json();if(o.error)throw Error(o.error);return o.message?.content?.trim()||`No response returned.`}async function g(e){let t=await fetch(`${e.apiBaseUrl??m}/v1/ask`,{method:`POST`,headers:{"Content-Type":`application/json`,"x-site-key":e.siteKey},body:JSON.stringify(e.payload)}),n=await t.json();if(!t.ok||n.error)throw Error(n.error??`Hosted API returned ${t.status}`);return n.answer??`No response returned.`}function _(e){let n=document.createElement(`div`);n.dataset.aiOverlayRoot=`true`;let r=document.createElement(`style`);r.dataset.aiOverlayStyle=`true`,r.textContent=`body.ai-overlay-active { cursor: crosshair; }`;let i=n.attachShadow({mode:`open`});document.head.append(r),document.body.append(n),i.innerHTML=`
1
+ (function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e,t,n){return Math.min(Math.max(e,t),n)}function n(e){return e.replace(/\s+/g,` `).trim()}function r(e,t=[]){return t.some(t=>{try{return e.matches(t)||!!e.closest(t)}catch{return!1}})}function i(){return/Mac|iPhone|iPad|iPod/i.test(window.navigator.platform)}function a(e){if(e.rangeCount===0)return null;let t=e.getRangeAt(0),n=t.getBoundingClientRect();return n.width>0||n.height>0?n:t.getClientRects()[0]??null}function o(e){return{rect:e,url:window.location.href,title:document.title}}function s(e){let t=n(e.innerText??``),r=e.tagName.toLowerCase(),i=e.getAttribute(`aria-label`),a=e.getAttribute(`title`),o=[i,a,t].find(Boolean);return{label:o?`${r}: ${o.slice(0,64)}`:r,data:void 0,mimeType:void 0,content:[`Selected element: <${r}>`,i?`ARIA label: ${i}`:``,a?`Title: ${a}`:``,t?`Visible text: ${t.slice(0,5e3)}`:``].filter(Boolean).join(`
2
+ `)}}function c(e){let t=e.currentSrc||e.src,n=e.alt||`No alt text`,r,i;try{let t=document.createElement(`canvas`);t.width=e.naturalWidth,t.height=e.naturalHeight;let n=t.getContext(`2d`);if(n){n.drawImage(e,0,0);let[a,o]=t.toDataURL(`image/jpeg`,.8).split(`,`);r=o,i=a.split(`:`)[1].split(`;`)[0]}}catch(e){console.warn(`Failed to capture image data (likely CORS):`,e)}return{label:`image: ${n.slice(0,64)}`,data:r,mimeType:i,content:[`Selected image`,`Alt text: ${n}`,`Source: ${t}`,`Rendered size: ${Math.round(e.width)}x${Math.round(e.height)}`,`Natural size: ${e.naturalWidth}x${e.naturalHeight}`].join(`
3
+ `)}}function l(e,t){return!(r(e,t.blockedSelectors)||t.allowedSelectors.length>0&&!r(e,t.allowedSelectors))}function u(e){let t=t=>{if(!e.isActive())return;let n=t.target;if(!(n instanceof HTMLElement)||e.isOverlayElement(n)){e.onHover(null);return}if(!l(n,e.config)){e.onHover(null);return}e.onHover(n.getBoundingClientRect())},r=()=>{!e.isActive()||!e.config.text||window.setTimeout(()=>{let t=window.getSelection(),r=n(t?.toString()??``);if(!t||!r)return;let i=a(t),s=t.anchorNode?.parentElement;!i||!s||!l(s,e.config)||e.onSelection({kind:`text`,label:`highlighted text`,content:r,...o(i)})},0)},i=t=>{if(!e.isActive())return;let r=t.target;if(!(r instanceof HTMLElement)||e.isOverlayElement(r)||!l(r,e.config)||n(window.getSelection()?.toString()??``))return;let i=r instanceof HTMLImageElement?r:r.querySelector(`img`),a=!!i;if(a&&!e.config.images||!a&&!e.config.elements)return;t.preventDefault(),t.stopPropagation();let u=i?c(i):s(r),d=i?i.getBoundingClientRect():r.getBoundingClientRect();e.onSelection({kind:i?`image`:`element`,label:u.label,content:u.content,data:u.data,mimeType:u.mimeType,...o(d)})};return document.addEventListener(`pointerover`,t),document.addEventListener(`mouseup`,r),document.addEventListener(`click`,i,!0),()=>{document.removeEventListener(`pointerover`,t),document.removeEventListener(`mouseup`,r),document.removeEventListener(`click`,i,!0)}}function d(e){let t=e.windowMs??650,n=e.cooldownMs??1e3,r=e.minSamples??7,i=e.minReversals??5,a=e.minDistance??340,o=e.minDeltaX??16,s=[],c=0,l=l=>{let u=performance.now();if(s.push({x:l.clientX,y:l.clientY,time:u}),s=s.filter(e=>u-e.time<t),s.length<r||u-c<n)return;let d=0,f=0,p=0;for(let e=1;e<s.length;e+=1){let t=s[e].x-s[e-1].x,n=s[e].y-s[e-1].y;if(f+=Math.hypot(t,n),Math.abs(t)<o)continue;let r=Math.sign(t);p!==0&&r!==p&&(d+=1),p=r}d>=i&&f>a&&(c=u,s=[],e.onShake())};return document.addEventListener(`pointermove`,l),()=>{document.removeEventListener(`pointermove`,l)}}var f=`http://localhost:11434/api/chat`,p=`qwen3-coder:480b-cloud`,m=`https://shakeai.onrender.com`;async function h(e,t){let n=e.provider??`ollama`,r=e.endpoint??f,i=e.model??p;if(n===`custom`){let n=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify(t)});if(!n.ok)throw Error(`Custom endpoint returned ${n.status}`);let i=await n.json();return typeof i==`string`?i:i&&typeof i==`object`&&`answer`in i&&typeof i.answer==`string`?i.answer:i&&typeof i==`object`&&`content`in i&&typeof i.content==`string`?i.content:JSON.stringify(i,null,2)}let a=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify({model:i,stream:!1,messages:[{role:`system`,content:`You are an in-page AI assistant. Use the selected website context to answer or transform content. Be direct and practical.`},{role:`user`,content:[`Page title: ${t.selection.title}`,`Page URL: ${t.selection.url}`,`Selection type: ${t.selection.kind}`,`Selection label: ${t.selection.label}`,`Selected context:`,t.selection.content,``,`User request: ${t.question}`].join(`
4
+ `)}]})});if(!a.ok)throw Error(`Ollama returned ${a.status}`);let o=await a.json();if(o.error)throw Error(o.error);return o.message?.content?.trim()||`No response returned.`}async function g(e){let t=await fetch(`${e.apiBaseUrl??m}/v1/ask`,{method:`POST`,headers:{"Content-Type":`application/json`,"x-site-key":e.siteKey},body:JSON.stringify(e.payload)}),n=await t.json();if(!t.ok||n.error)throw Error(n.error??`Hosted API returned ${t.status}`);return n.answer??`No response returned.`}function _(e){let n=document.createElement(`div`);n.dataset.aiOverlayRoot=`true`;let r=document.createElement(`style`);r.dataset.aiOverlayStyle=`true`,r.textContent=`
5
+ body.ai-overlay-active {
6
+ cursor: none !important;
7
+ }
8
+ body.ai-overlay-active * {
9
+ cursor: none !important;
10
+ }
11
+ `;let i=n.attachShadow({mode:`open`});document.head.append(r),document.body.append(n),i.innerHTML=`
5
12
  <style>
6
13
  :host {
7
14
  all: initial;
15
+ --ai-blue: #4285F4;
16
+ --ai-red: #DB4437;
17
+ --ai-yellow: #F4B400;
18
+ --ai-green: #0F9D58;
8
19
  --ai-primary: ${e.theme.primaryColor};
9
20
  --ai-panel: ${e.theme.panelBackground};
10
21
  --ai-text: ${e.theme.textColor};
@@ -12,6 +23,37 @@
12
23
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
24
  }
14
25
 
26
+ .custom-cursor {
27
+ position: fixed;
28
+ left: 0;
29
+ top: 0;
30
+ width: 24px;
31
+ height: 24px;
32
+ pointer-events: none;
33
+ z-index: 2147483647;
34
+ display: none;
35
+ transform: translate(-50%, -50%);
36
+ transition: transform 0.1s ease-out;
37
+ }
38
+
39
+ .custom-cursor.active {
40
+ display: block;
41
+ }
42
+
43
+ .cursor-dot {
44
+ width: 100%;
45
+ height: 100%;
46
+ background: conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue));
47
+ border-radius: 50%;
48
+ box-shadow: 0 0 15px rgba(66, 133, 244, 0.8), 0 0 30px rgba(15, 157, 88, 0.4);
49
+ animation: rotate-gradient 2s linear infinite;
50
+ }
51
+
52
+ @keyframes rotate-gradient {
53
+ from { transform: rotate(0deg); }
54
+ to { transform: rotate(360deg); }
55
+ }
56
+
15
57
  .toast {
16
58
  position: fixed;
17
59
  right: 18px;
@@ -53,11 +95,15 @@
53
95
  z-index: 2147483644;
54
96
  pointer-events: none;
55
97
  display: none;
56
- border: 2px solid var(--ai-primary);
98
+ border: 2px solid transparent;
57
99
  border-radius: var(--ai-radius);
100
+ background: linear-gradient(var(--ai-panel), var(--ai-panel)) padding-box,
101
+ conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue)) border-box;
58
102
  box-shadow:
59
- 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
103
+ 0 0 20px color-mix(in srgb, var(--ai-blue), transparent 80%),
60
104
  0 20px 52px rgba(15, 23, 42, 0.14);
105
+ opacity: 0.3;
106
+ transition: all 0.15s ease-out;
61
107
  }
62
108
 
63
109
  .panel {
@@ -90,7 +136,7 @@
90
136
  .kind {
91
137
  display: block;
92
138
  margin-bottom: 4px;
93
- color: var(--ai-primary);
139
+ color: var(--ai-blue);
94
140
  font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
95
141
  text-transform: uppercase;
96
142
  }
@@ -145,8 +191,8 @@
145
191
  }
146
192
 
147
193
  textarea:focus {
148
- border-color: var(--ai-primary);
149
- outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
194
+ border-color: var(--ai-blue);
195
+ outline: 3px solid color-mix(in srgb, var(--ai-blue), transparent 86%);
150
196
  }
151
197
 
152
198
  .error,
@@ -191,6 +237,9 @@
191
237
  100% { box-shadow: 0 0 0 0 transparent; }
192
238
  }
193
239
  </style>
240
+ <div class="custom-cursor">
241
+ <div class="cursor-dot"></div>
242
+ </div>
194
243
  <div class="toast" role="status">
195
244
  <span class="pulse"></span>
196
245
  <span>AI mode active</span>
@@ -212,4 +261,4 @@
212
261
  <p class="error"></p>
213
262
  <div class="answer"></div>
214
263
  </section>
215
- `;let a=i.querySelector(`.toast`),o=i.querySelector(`.count`),s=i.querySelector(`.hover`),c=i.querySelector(`.panel`),l=i.querySelector(`.kind`),u=i.querySelector(`.label`),d=i.querySelector(`form`),f=i.querySelector(`textarea`),p=i.querySelector(`.submit`),m=i.querySelector(`.close`),h=i.querySelector(`.error`),g=i.querySelector(`.answer`);if(!a||!o||!s||!c||!l||!u||!d||!f||!p||!m||!h||!g)throw Error(`AIOverlay UI failed to initialize`);return d.addEventListener(`submit`,t=>{t.preventDefault();let n=f.value.trim();n&&e.onSubmit(n)}),m.addEventListener(`click`,()=>{e.onCancel()}),{root:n,contains:e=>e===n||n.contains(e),setActive:(e,t)=>{a.classList.toggle(`active`,e),o.textContent=`shakes ${t}`},setHoverRect:e=>{if(!e){s.style.display=`none`;return}s.style.display=`block`,s.style.left=`${e.left}px`,s.style.top=`${e.top}px`,s.style.width=`${e.width}px`,s.style.height=`${e.height}px`},showPrompt:e=>{let n=t(e.rect.left+e.rect.width/2-190,16,window.innerWidth-396),r=t(e.rect.bottom+14,16,window.innerHeight-380);l.textContent=e.kind,u.textContent=e.label,f.value=``,p.disabled=!1,p.textContent=`Ask AI`,h.classList.remove(`visible`),g.classList.remove(`visible`),c.style.left=`${n}px`,c.style.top=`${r}px`,c.classList.add(`visible`),f.focus()},setThinking:e=>{p.disabled=e,p.textContent=e?`Thinking...`:`Ask AI`},setAnswer:e=>{g.textContent=e,g.classList.toggle(`visible`,!!e)},setError:e=>{h.textContent=e,h.classList.toggle(`visible`,!!e)},clearSelection:()=>{c.classList.remove(`visible`),h.classList.remove(`visible`),g.classList.remove(`visible`),f.value=``},destroy:()=>{r.remove(),n.remove()}}}var v={primaryColor:`#14b8a6`,panelBackground:`rgba(255, 255, 255, 0.96)`,textColor:`#111827`,borderRadius:8},y={shake:!0,keyboardShortcut:`mod+k`},b={text:!0,images:!0,elements:!0,blockedSelectors:[`input[type="password"]`,`[data-ai-private]`,`[data-ai-overlay-ignore]`],allowedSelectors:[]},x=class{active=!1;shakeCount=0;selection=null;disposers=[];ui;config;trigger;constructor(e){this.config=e,this.trigger={...y,...e.trigger};let t={...b,...e.selection};this.ui=_({theme:{...v,...e.theme},onSubmit:e=>{this.ask(e)},onCancel:()=>{this.clearSelection()}}),this.trigger.shake&&this.disposers.push(d({onShake:()=>{this.shakeCount+=1,this.activate()}})),this.disposers.push(u({config:t,isActive:()=>this.active,isOverlayElement:e=>this.ui.contains(e),onHover:e=>{this.ui.setHoverRect(this.selection?null:e)},onSelection:e=>{this.selection=e,this.ui.setHoverRect(null),this.ui.showPrompt(e),this.config.onSelection?.(e)}})),this.disposers.push(this.bindKeyboardShortcut())}activate(){if(this.active){this.ui.setActive(!0,this.shakeCount);return}this.active=!0,document.body.classList.add(`ai-overlay-active`),this.ui.setActive(!0,this.shakeCount),this.config.onActivate?.()}deactivate(){this.active&&(this.active=!1,this.clearSelection(),this.ui.setHoverRect(null),this.ui.setActive(!1,this.shakeCount),document.body.classList.remove(`ai-overlay-active`),this.config.onDeactivate?.())}destroy(){for(let e of this.disposers)e();this.disposers=[],this.deactivate(),this.ui.destroy()}isActive(){return this.active}clearSelection(){this.selection=null,this.ui.clearSelection(),window.getSelection()?.removeAllRanges()}async ask(e){if(!this.selection)return;let t={question:e,selection:this.selection};this.config.onAsk?.(t),this.ui.setThinking(!0),this.ui.setAnswer(``),this.ui.setError(``);try{let e=this.config.siteKey?await g({apiBaseUrl:this.config.apiBaseUrl,siteKey:this.config.siteKey,payload:t}):await h(this.config.model??{},t);this.ui.setAnswer(e),this.config.onResponse?.(e,t)}catch(e){let t=e instanceof Error?e:Error(`Unknown AIOverlay error`);this.ui.setError(t.message),this.config.onError?.(t)}finally{this.ui.setThinking(!1)}}bindKeyboardShortcut(){let e=this.trigger.keyboardShortcut.toLowerCase(),t=t=>{let n=e.includes(`mod+`),r=e.split(`+`).at(-1),a=i()?t.metaKey:t.ctrlKey;n&&!a||r&&t.key.toLowerCase()===r&&(t.preventDefault(),this.active?this.deactivate():this.activate())};return document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`keydown`,t)}}},S={init(e={}){return new x(e)}};typeof window<`u`&&(window.AIOverlay=S),e.AIOverlay=S})(this.AIOverlayBundle=this.AIOverlayBundle||{});
264
+ `;let a=i.querySelector(`.custom-cursor`),o=i.querySelector(`.toast`),s=i.querySelector(`.count`),c=i.querySelector(`.hover`),l=i.querySelector(`.panel`),u=i.querySelector(`.kind`),d=i.querySelector(`.label`),f=i.querySelector(`form`),p=i.querySelector(`textarea`),m=i.querySelector(`.submit`),h=i.querySelector(`.close`),g=i.querySelector(`.error`),_=i.querySelector(`.answer`);if(!a||!o||!s||!c||!l||!u||!d||!f||!p||!m||!h||!g||!_)throw Error(`AIOverlay UI failed to initialize`);let v=e=>{a.style.left=`${e.clientX}px`,a.style.top=`${e.clientY}px`};return f.addEventListener(`submit`,t=>{t.preventDefault();let n=p.value.trim();n&&e.onSubmit(n)}),h.addEventListener(`click`,()=>{e.onCancel()}),{root:n,contains:e=>e===n||n.contains(e),setActive:(e,t)=>{o.classList.toggle(`active`,e),a.classList.toggle(`active`,e),s.textContent=`shakes ${t}`,e?window.addEventListener(`mousemove`,v):window.removeEventListener(`mousemove`,v)},setHoverRect:e=>{if(!e){c.style.display=`none`;return}c.style.display=`block`,c.style.left=`${e.left}px`,c.style.top=`${e.top}px`,c.style.width=`${e.width}px`,c.style.height=`${e.height}px`},showPrompt:e=>{let n=t(e.rect.left+e.rect.width/2-190,16,window.innerWidth-396),r=t(e.rect.bottom+14,16,window.innerHeight-380);u.textContent=e.kind,d.textContent=e.label,p.value=``,m.disabled=!1,m.textContent=`Ask AI`,g.classList.remove(`visible`),_.classList.remove(`visible`),l.style.left=`${n}px`,l.style.top=`${r}px`,l.classList.add(`visible`),p.focus()},setThinking:e=>{m.disabled=e,m.textContent=e?`Thinking...`:`Ask AI`},setAnswer:e=>{_.textContent=e,_.classList.toggle(`visible`,!!e)},setError:e=>{g.textContent=e,g.classList.toggle(`visible`,!!e)},clearSelection:()=>{l.classList.remove(`visible`),g.classList.remove(`visible`),_.classList.remove(`visible`),p.value=``},destroy:()=>{r.remove(),n.remove(),window.removeEventListener(`mousemove`,v)}}}var v={primaryColor:`#14b8a6`,panelBackground:`rgba(255, 255, 255, 0.96)`,textColor:`#111827`,borderRadius:8},y={shake:!0,keyboardShortcut:`mod+k`},b={text:!0,images:!0,elements:!0,blockedSelectors:[`input[type="password"]`,`[data-ai-private]`,`[data-ai-overlay-ignore]`],allowedSelectors:[]},x=class{active=!1;shakeCount=0;selection=null;disposers=[];ui;config;trigger;constructor(e){this.config=e,this.trigger={...y,...e.trigger};let t={...b,...e.selection};this.ui=_({theme:{...v,...e.theme},onSubmit:e=>{this.ask(e)},onCancel:()=>{this.clearSelection()}}),this.trigger.shake&&this.disposers.push(d({onShake:()=>{this.shakeCount+=1,this.activate()}})),this.disposers.push(u({config:t,isActive:()=>this.active,isOverlayElement:e=>this.ui.contains(e),onHover:e=>{this.ui.setHoverRect(this.selection?null:e)},onSelection:e=>{this.selection=e,this.ui.setHoverRect(null),this.ui.showPrompt(e),this.config.onSelection?.(e)}})),this.disposers.push(this.bindKeyboardShortcut())}activate(){if(this.active){this.ui.setActive(!0,this.shakeCount);return}this.active=!0,document.body.classList.add(`ai-overlay-active`),this.ui.setActive(!0,this.shakeCount),this.config.onActivate?.()}deactivate(){this.active&&(this.active=!1,this.clearSelection(),this.ui.setHoverRect(null),this.ui.setActive(!1,this.shakeCount),document.body.classList.remove(`ai-overlay-active`),this.config.onDeactivate?.())}destroy(){for(let e of this.disposers)e();this.disposers=[],this.deactivate(),this.ui.destroy()}isActive(){return this.active}clearSelection(){this.selection=null,this.ui.clearSelection(),window.getSelection()?.removeAllRanges()}async ask(e){if(!this.selection)return;let t={question:e,selection:this.selection};this.config.onAsk?.(t),this.ui.setThinking(!0),this.ui.setAnswer(``),this.ui.setError(``);try{let e=this.config.siteKey?await g({apiBaseUrl:this.config.apiBaseUrl,siteKey:this.config.siteKey,payload:t}):await h(this.config.model??{},t);this.ui.setAnswer(e),this.config.onResponse?.(e,t)}catch(e){let t=e instanceof Error?e:Error(`Unknown AIOverlay error`);this.ui.setError(t.message),this.config.onError?.(t)}finally{this.ui.setThinking(!1)}}bindKeyboardShortcut(){let e=this.trigger.keyboardShortcut.toLowerCase(),t=t=>{let n=e.includes(`mod+`),r=e.split(`+`).at(-1),a=i()?t.metaKey:t.ctrlKey;n&&!a||r&&t.key.toLowerCase()===r&&(t.preventDefault(),this.active?this.deactivate():this.activate())};return document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`keydown`,t)}}},S={init(e={}){return new x(e)}};typeof window<`u`&&(window.AIOverlay=S),e.AIOverlay=S})(this.AIOverlayBundle=this.AIOverlayBundle||{});
package/dist/overlay.js CHANGED
@@ -39,6 +39,8 @@ function o(e) {
39
39
  ].find(Boolean);
40
40
  return {
41
41
  label: o ? `${r}: ${o.slice(0, 64)}` : r,
42
+ data: void 0,
43
+ mimeType: void 0,
42
44
  content: [
43
45
  `Selected element: <${r}>`,
44
46
  i ? `ARIA label: ${i}` : "",
@@ -48,9 +50,23 @@ function o(e) {
48
50
  };
49
51
  }
50
52
  function s(e) {
51
- let t = e.currentSrc || e.src, n = e.alt || "No alt text";
53
+ let t = e.currentSrc || e.src, n = e.alt || "No alt text", r, i;
54
+ try {
55
+ let t = document.createElement("canvas");
56
+ t.width = e.naturalWidth, t.height = e.naturalHeight;
57
+ let n = t.getContext("2d");
58
+ if (n) {
59
+ n.drawImage(e, 0, 0);
60
+ let [a, o] = t.toDataURL("image/jpeg", .8).split(",");
61
+ r = o, i = a.split(":")[1].split(";")[0];
62
+ }
63
+ } catch (e) {
64
+ console.warn("Failed to capture image data (likely CORS):", e);
65
+ }
52
66
  return {
53
67
  label: `image: ${n.slice(0, 64)}`,
68
+ data: r,
69
+ mimeType: i,
54
70
  content: [
55
71
  "Selected image",
56
72
  `Alt text: ${n}`,
@@ -100,6 +116,8 @@ function l(e) {
100
116
  kind: i ? "image" : "element",
101
117
  label: u.label,
102
118
  content: u.content,
119
+ data: u.data,
120
+ mimeType: u.mimeType,
103
121
  ...a(d)
104
122
  });
105
123
  };
@@ -198,12 +216,16 @@ function g(t) {
198
216
  let n = document.createElement("div");
199
217
  n.dataset.aiOverlayRoot = "true";
200
218
  let r = document.createElement("style");
201
- r.dataset.aiOverlayStyle = "true", r.textContent = "body.ai-overlay-active { cursor: crosshair; }";
219
+ r.dataset.aiOverlayStyle = "true", r.textContent = "\n body.ai-overlay-active { \n cursor: none !important; \n }\n body.ai-overlay-active * { \n cursor: none !important; \n }\n ";
202
220
  let i = n.attachShadow({ mode: "open" });
203
221
  document.head.append(r), document.body.append(n), i.innerHTML = `
204
222
  <style>
205
223
  :host {
206
224
  all: initial;
225
+ --ai-blue: #4285F4;
226
+ --ai-red: #DB4437;
227
+ --ai-yellow: #F4B400;
228
+ --ai-green: #0F9D58;
207
229
  --ai-primary: ${t.theme.primaryColor};
208
230
  --ai-panel: ${t.theme.panelBackground};
209
231
  --ai-text: ${t.theme.textColor};
@@ -211,6 +233,37 @@ function g(t) {
211
233
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
212
234
  }
213
235
 
236
+ .custom-cursor {
237
+ position: fixed;
238
+ left: 0;
239
+ top: 0;
240
+ width: 24px;
241
+ height: 24px;
242
+ pointer-events: none;
243
+ z-index: 2147483647;
244
+ display: none;
245
+ transform: translate(-50%, -50%);
246
+ transition: transform 0.1s ease-out;
247
+ }
248
+
249
+ .custom-cursor.active {
250
+ display: block;
251
+ }
252
+
253
+ .cursor-dot {
254
+ width: 100%;
255
+ height: 100%;
256
+ background: conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue));
257
+ border-radius: 50%;
258
+ box-shadow: 0 0 15px rgba(66, 133, 244, 0.8), 0 0 30px rgba(15, 157, 88, 0.4);
259
+ animation: rotate-gradient 2s linear infinite;
260
+ }
261
+
262
+ @keyframes rotate-gradient {
263
+ from { transform: rotate(0deg); }
264
+ to { transform: rotate(360deg); }
265
+ }
266
+
214
267
  .toast {
215
268
  position: fixed;
216
269
  right: 18px;
@@ -252,11 +305,15 @@ function g(t) {
252
305
  z-index: 2147483644;
253
306
  pointer-events: none;
254
307
  display: none;
255
- border: 2px solid var(--ai-primary);
308
+ border: 2px solid transparent;
256
309
  border-radius: var(--ai-radius);
310
+ background: linear-gradient(var(--ai-panel), var(--ai-panel)) padding-box,
311
+ conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue)) border-box;
257
312
  box-shadow:
258
- 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
313
+ 0 0 20px color-mix(in srgb, var(--ai-blue), transparent 80%),
259
314
  0 20px 52px rgba(15, 23, 42, 0.14);
315
+ opacity: 0.3;
316
+ transition: all 0.15s ease-out;
260
317
  }
261
318
 
262
319
  .panel {
@@ -289,7 +346,7 @@ function g(t) {
289
346
  .kind {
290
347
  display: block;
291
348
  margin-bottom: 4px;
292
- color: var(--ai-primary);
349
+ color: var(--ai-blue);
293
350
  font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
294
351
  text-transform: uppercase;
295
352
  }
@@ -344,8 +401,8 @@ function g(t) {
344
401
  }
345
402
 
346
403
  textarea:focus {
347
- border-color: var(--ai-primary);
348
- outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
404
+ border-color: var(--ai-blue);
405
+ outline: 3px solid color-mix(in srgb, var(--ai-blue), transparent 86%);
349
406
  }
350
407
 
351
408
  .error,
@@ -390,6 +447,9 @@ function g(t) {
390
447
  100% { box-shadow: 0 0 0 0 transparent; }
391
448
  }
392
449
  </style>
450
+ <div class="custom-cursor">
451
+ <div class="cursor-dot"></div>
452
+ </div>
393
453
  <div class="toast" role="status">
394
454
  <span class="pulse"></span>
395
455
  <span>AI mode active</span>
@@ -412,45 +472,48 @@ function g(t) {
412
472
  <div class="answer"></div>
413
473
  </section>
414
474
  `;
415
- let a = i.querySelector(".toast"), o = i.querySelector(".count"), s = i.querySelector(".hover"), c = i.querySelector(".panel"), l = i.querySelector(".kind"), u = i.querySelector(".label"), d = i.querySelector("form"), f = i.querySelector("textarea"), p = i.querySelector(".submit"), m = i.querySelector(".close"), h = i.querySelector(".error"), g = i.querySelector(".answer");
416
- if (!a || !o || !s || !c || !l || !u || !d || !f || !p || !m || !h || !g) throw Error("AIOverlay UI failed to initialize");
417
- return d.addEventListener("submit", (e) => {
475
+ let a = i.querySelector(".custom-cursor"), o = i.querySelector(".toast"), s = i.querySelector(".count"), c = i.querySelector(".hover"), l = i.querySelector(".panel"), u = i.querySelector(".kind"), d = i.querySelector(".label"), f = i.querySelector("form"), p = i.querySelector("textarea"), m = i.querySelector(".submit"), h = i.querySelector(".close"), g = i.querySelector(".error"), _ = i.querySelector(".answer");
476
+ if (!a || !o || !s || !c || !l || !u || !d || !f || !p || !m || !h || !g || !_) throw Error("AIOverlay UI failed to initialize");
477
+ let v = (e) => {
478
+ a.style.left = `${e.clientX}px`, a.style.top = `${e.clientY}px`;
479
+ };
480
+ return f.addEventListener("submit", (e) => {
418
481
  e.preventDefault();
419
- let n = f.value.trim();
482
+ let n = p.value.trim();
420
483
  n && t.onSubmit(n);
421
- }), m.addEventListener("click", () => {
484
+ }), h.addEventListener("click", () => {
422
485
  t.onCancel();
423
486
  }), {
424
487
  root: n,
425
488
  contains: (e) => e === n || n.contains(e),
426
489
  setActive: (e, t) => {
427
- a.classList.toggle("active", e), o.textContent = `shakes ${t}`;
490
+ o.classList.toggle("active", e), a.classList.toggle("active", e), s.textContent = `shakes ${t}`, e ? window.addEventListener("mousemove", v) : window.removeEventListener("mousemove", v);
428
491
  },
429
492
  setHoverRect: (e) => {
430
493
  if (!e) {
431
- s.style.display = "none";
494
+ c.style.display = "none";
432
495
  return;
433
496
  }
434
- s.style.display = "block", s.style.left = `${e.left}px`, s.style.top = `${e.top}px`, s.style.width = `${e.width}px`, s.style.height = `${e.height}px`;
497
+ c.style.display = "block", c.style.left = `${e.left}px`, c.style.top = `${e.top}px`, c.style.width = `${e.width}px`, c.style.height = `${e.height}px`;
435
498
  },
436
499
  showPrompt: (t) => {
437
500
  let n = e(t.rect.left + t.rect.width / 2 - 190, 16, window.innerWidth - 396), r = e(t.rect.bottom + 14, 16, window.innerHeight - 380);
438
- l.textContent = t.kind, u.textContent = t.label, f.value = "", p.disabled = !1, p.textContent = "Ask AI", h.classList.remove("visible"), g.classList.remove("visible"), c.style.left = `${n}px`, c.style.top = `${r}px`, c.classList.add("visible"), f.focus();
501
+ u.textContent = t.kind, d.textContent = t.label, p.value = "", m.disabled = !1, m.textContent = "Ask AI", g.classList.remove("visible"), _.classList.remove("visible"), l.style.left = `${n}px`, l.style.top = `${r}px`, l.classList.add("visible"), p.focus();
439
502
  },
440
503
  setThinking: (e) => {
441
- p.disabled = e, p.textContent = e ? "Thinking..." : "Ask AI";
504
+ m.disabled = e, m.textContent = e ? "Thinking..." : "Ask AI";
442
505
  },
443
506
  setAnswer: (e) => {
444
- g.textContent = e, g.classList.toggle("visible", !!e);
507
+ _.textContent = e, _.classList.toggle("visible", !!e);
445
508
  },
446
509
  setError: (e) => {
447
- h.textContent = e, h.classList.toggle("visible", !!e);
510
+ g.textContent = e, g.classList.toggle("visible", !!e);
448
511
  },
449
512
  clearSelection: () => {
450
- c.classList.remove("visible"), h.classList.remove("visible"), g.classList.remove("visible"), f.value = "";
513
+ l.classList.remove("visible"), g.classList.remove("visible"), _.classList.remove("visible"), p.value = "";
451
514
  },
452
515
  destroy: () => {
453
- r.remove(), n.remove();
516
+ r.remove(), n.remove(), window.removeEventListener("mousemove", v);
454
517
  }
455
518
  };
456
519
  }
package/dist/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export type AIOverlaySelection = {
6
6
  rect: DOMRect;
7
7
  url: string;
8
8
  title: string;
9
+ data?: string;
10
+ mimeType?: string;
9
11
  };
10
12
  export type AIOverlayAskPayload = {
11
13
  question: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emmaexcel/shakecursor",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Framework-agnostic browser SDK for contextual AI selection overlays.",
5
5
  "type": "module",
6
6
  "main": "./dist/overlay.global.js",
@@ -37,4 +37,4 @@
37
37
  ],
38
38
  "author": "EmmaExcel",
39
39
  "license": "MIT"
40
- }
40
+ }