@emmaexcel/shakecursor 0.1.0 → 0.1.3

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,33 @@
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: 32px;
31
+ height: 32px;
32
+ pointer-events: none;
33
+ z-index: 2147483647;
34
+ display: none;
35
+ /* Offset to center the "tip" of the pointer */
36
+ margin-left: -4px;
37
+ margin-top: -4px;
38
+ filter: drop-shadow(0 0 8px rgba(66, 133, 244, 0.6));
39
+ }
40
+
41
+ .custom-cursor.active {
42
+ display: block;
43
+ }
44
+
45
+ .cursor-shape {
46
+ width: 100%;
47
+ height: 100%;
48
+ fill: white;
49
+ stroke: var(--ai-blue);
50
+ stroke-width: 2px;
51
+ }
52
+
15
53
  .toast {
16
54
  position: fixed;
17
55
  right: 18px;
@@ -53,11 +91,15 @@
53
91
  z-index: 2147483644;
54
92
  pointer-events: none;
55
93
  display: none;
56
- border: 2px solid var(--ai-primary);
94
+ /* Thicker, more professional border */
95
+ border: 4px solid transparent;
57
96
  border-radius: var(--ai-radius);
97
+ /*Conic gradient on border only, no gray background fill */
98
+ background: linear-gradient(transparent, transparent) padding-box,
99
+ conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue)) border-box;
58
100
  box-shadow:
59
- 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
60
- 0 20px 52px rgba(15, 23, 42, 0.14);
101
+ 0 0 30px color-mix(in srgb, var(--ai-blue), transparent 85%);
102
+ transition: all 0.12s ease-out;
61
103
  }
62
104
 
63
105
  .panel {
@@ -90,7 +132,7 @@
90
132
  .kind {
91
133
  display: block;
92
134
  margin-bottom: 4px;
93
- color: var(--ai-primary);
135
+ color: var(--ai-blue);
94
136
  font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
95
137
  text-transform: uppercase;
96
138
  }
@@ -145,8 +187,8 @@
145
187
  }
146
188
 
147
189
  textarea:focus {
148
- border-color: var(--ai-primary);
149
- outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
190
+ border-color: var(--ai-blue);
191
+ outline: 3px solid color-mix(in srgb, var(--ai-blue), transparent 86%);
150
192
  }
151
193
 
152
194
  .error,
@@ -191,6 +233,11 @@
191
233
  100% { box-shadow: 0 0 0 0 transparent; }
192
234
  }
193
235
  </style>
236
+ <div class="custom-cursor">
237
+ <svg viewBox="0 0 28 32" class="cursor-shape">
238
+ <path d="M2,2 L2,28 L8,22 L14,32 L18,29 L12,19 L22,19 Z" stroke-linejoin="round" stroke-linecap="round" />
239
+ </svg>
240
+ </div>
194
241
  <div class="toast" role="status">
195
242
  <span class="pulse"></span>
196
243
  <span>AI mode active</span>
@@ -212,4 +259,4 @@
212
259
  <p class="error"></p>
213
260
  <div class="answer"></div>
214
261
  </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||{});
262
+ `;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,33 @@ 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: 32px;
241
+ height: 32px;
242
+ pointer-events: none;
243
+ z-index: 2147483647;
244
+ display: none;
245
+ /* Offset to center the "tip" of the pointer */
246
+ margin-left: -4px;
247
+ margin-top: -4px;
248
+ filter: drop-shadow(0 0 8px rgba(66, 133, 244, 0.6));
249
+ }
250
+
251
+ .custom-cursor.active {
252
+ display: block;
253
+ }
254
+
255
+ .cursor-shape {
256
+ width: 100%;
257
+ height: 100%;
258
+ fill: white;
259
+ stroke: var(--ai-blue);
260
+ stroke-width: 2px;
261
+ }
262
+
214
263
  .toast {
215
264
  position: fixed;
216
265
  right: 18px;
@@ -252,11 +301,15 @@ function g(t) {
252
301
  z-index: 2147483644;
253
302
  pointer-events: none;
254
303
  display: none;
255
- border: 2px solid var(--ai-primary);
304
+ /* Thicker, more professional border */
305
+ border: 4px solid transparent;
256
306
  border-radius: var(--ai-radius);
307
+ /*Conic gradient on border only, no gray background fill */
308
+ background: linear-gradient(transparent, transparent) padding-box,
309
+ conic-gradient(var(--ai-blue), var(--ai-red), var(--ai-yellow), var(--ai-green), var(--ai-blue)) border-box;
257
310
  box-shadow:
258
- 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
259
- 0 20px 52px rgba(15, 23, 42, 0.14);
311
+ 0 0 30px color-mix(in srgb, var(--ai-blue), transparent 85%);
312
+ transition: all 0.12s ease-out;
260
313
  }
261
314
 
262
315
  .panel {
@@ -289,7 +342,7 @@ function g(t) {
289
342
  .kind {
290
343
  display: block;
291
344
  margin-bottom: 4px;
292
- color: var(--ai-primary);
345
+ color: var(--ai-blue);
293
346
  font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
294
347
  text-transform: uppercase;
295
348
  }
@@ -344,8 +397,8 @@ function g(t) {
344
397
  }
345
398
 
346
399
  textarea:focus {
347
- border-color: var(--ai-primary);
348
- outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
400
+ border-color: var(--ai-blue);
401
+ outline: 3px solid color-mix(in srgb, var(--ai-blue), transparent 86%);
349
402
  }
350
403
 
351
404
  .error,
@@ -390,6 +443,11 @@ function g(t) {
390
443
  100% { box-shadow: 0 0 0 0 transparent; }
391
444
  }
392
445
  </style>
446
+ <div class="custom-cursor">
447
+ <svg viewBox="0 0 28 32" class="cursor-shape">
448
+ <path d="M2,2 L2,28 L8,22 L14,32 L18,29 L12,19 L22,19 Z" stroke-linejoin="round" stroke-linecap="round" />
449
+ </svg>
450
+ </div>
393
451
  <div class="toast" role="status">
394
452
  <span class="pulse"></span>
395
453
  <span>AI mode active</span>
@@ -412,45 +470,48 @@ function g(t) {
412
470
  <div class="answer"></div>
413
471
  </section>
414
472
  `;
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) => {
473
+ 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");
474
+ if (!a || !o || !s || !c || !l || !u || !d || !f || !p || !m || !h || !g || !_) throw Error("AIOverlay UI failed to initialize");
475
+ let v = (e) => {
476
+ a.style.left = `${e.clientX}px`, a.style.top = `${e.clientY}px`;
477
+ };
478
+ return f.addEventListener("submit", (e) => {
418
479
  e.preventDefault();
419
- let n = f.value.trim();
480
+ let n = p.value.trim();
420
481
  n && t.onSubmit(n);
421
- }), m.addEventListener("click", () => {
482
+ }), h.addEventListener("click", () => {
422
483
  t.onCancel();
423
484
  }), {
424
485
  root: n,
425
486
  contains: (e) => e === n || n.contains(e),
426
487
  setActive: (e, t) => {
427
- a.classList.toggle("active", e), o.textContent = `shakes ${t}`;
488
+ o.classList.toggle("active", e), a.classList.toggle("active", e), s.textContent = `shakes ${t}`, e ? window.addEventListener("mousemove", v) : window.removeEventListener("mousemove", v);
428
489
  },
429
490
  setHoverRect: (e) => {
430
491
  if (!e) {
431
- s.style.display = "none";
492
+ c.style.display = "none";
432
493
  return;
433
494
  }
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`;
495
+ 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
496
  },
436
497
  showPrompt: (t) => {
437
498
  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();
499
+ 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
500
  },
440
501
  setThinking: (e) => {
441
- p.disabled = e, p.textContent = e ? "Thinking..." : "Ask AI";
502
+ m.disabled = e, m.textContent = e ? "Thinking..." : "Ask AI";
442
503
  },
443
504
  setAnswer: (e) => {
444
- g.textContent = e, g.classList.toggle("visible", !!e);
505
+ _.textContent = e, _.classList.toggle("visible", !!e);
445
506
  },
446
507
  setError: (e) => {
447
- h.textContent = e, h.classList.toggle("visible", !!e);
508
+ g.textContent = e, g.classList.toggle("visible", !!e);
448
509
  },
449
510
  clearSelection: () => {
450
- c.classList.remove("visible"), h.classList.remove("visible"), g.classList.remove("visible"), f.value = "";
511
+ l.classList.remove("visible"), g.classList.remove("visible"), _.classList.remove("visible"), p.value = "";
451
512
  },
452
513
  destroy: () => {
453
- r.remove(), n.remove();
514
+ r.remove(), n.remove(), window.removeEventListener("mousemove", v);
454
515
  }
455
516
  };
456
517
  }
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.3",
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
+ }