@intentai/react 1.0.2 → 2.0.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.
package/dist/index.mjs CHANGED
@@ -1,2 +1,1571 @@
1
- import $,{createContext,forwardRef,useContext,useState,useRef,useMemo,useEffect,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';var Y="https://intetnt-ai-v1-web-app-production.up.railway.app/widget/v1.js",k=null,E=false;function H(e){return E&&window.IntentAI?Promise.resolve():k||(k=new Promise((t,r)=>{if(document.querySelector(`script[src^="${Y}"]`)){window.IntentAI?(E=true,t()):window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true});return}let n=document.createElement("script");n.src=Y,n.async=true,n.id="intentai-widget-script",n.setAttribute("data-api-key",e.apiKey),n.onload=()=>{window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true}),setTimeout(()=>{!E&&window.IntentAI&&(E=true,t());},3e3);},n.onerror=()=>{k=null,r(new Error("Failed to load Intent AI widget script"));},document.head.appendChild(n);}),k)}var L={};var G=createContext(null),v=typeof window!="undefined";function j(e){if(e)return e;if(typeof process!="undefined"&&process.env){let t=process.env.NEXT_PUBLIC_INTENT_AI_KEY||process.env.REACT_APP_INTENT_AI_KEY||process.env.VITE_INTENT_AI_KEY;if(t)return t}try{if(typeof L!="undefined"&&L.env)return L.env.VITE_INTENT_AI_KEY}catch(t){}}function ee({children:e,apiKey:t,user:r,position:n="bottom-right",theme:p="auto",primaryColor:s,autoShow:a=true,metadata:I,onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C}){let[b,z]=useState(false),[N,F]=useState(null),R=useRef(r),l=useRef({onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C});l.current={onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C};let w=useMemo(()=>j(t),[t]);useEffect(()=>{w&&!w.startsWith("pk_")&&console.error('[IntentAI] Invalid API key format. Public keys should start with "pk_". Never use secret keys (sk_*) in client-side code.');},[w]),useEffect(()=>{if(!v)return;if(!w){console.warn("[IntentAI] No API key provided. Set the apiKey prop or NEXT_PUBLIC_INTENT_AI_KEY environment variable.");return}window.IntentAIConfig={apiKey:w,position:n,theme:p,primaryColor:s,autoShow:a,metadata:I};let d=()=>{var i,o,c;z(true),(o=(i=l.current).onReady)==null||o.call(i),R.current&&((c=window.IntentAI)==null||c.identify(R.current));},y=()=>{var i,o;(o=(i=l.current).onOpen)==null||o.call(i);},A=()=>{var i,o;(o=(i=l.current).onClose)==null||o.call(i);},W=i=>{var o,c;(c=(o=l.current).onSubmit)==null||c.call(o,i.detail);},V=i=>{var o,c;F(i.detail),(c=(o=l.current).onError)==null||c.call(o,i.detail);};return window.addEventListener("intentai:ready",d),window.addEventListener("intentai:open",y),window.addEventListener("intentai:close",A),window.addEventListener("intentai:submit",W),window.addEventListener("intentai:error",V),H({apiKey:w}).catch(i=>{var o,c;F(i),(c=(o=l.current).onError)==null||c.call(o,i);}),()=>{window.removeEventListener("intentai:ready",d),window.removeEventListener("intentai:open",y),window.removeEventListener("intentai:close",A),window.removeEventListener("intentai:submit",W),window.removeEventListener("intentai:error",V);}},[w,n,p,s,a,I]),useEffect(()=>{R.current=r,v&&b&&r&&window.IntentAI&&window.IntentAI.identify(r);},[r,b]);let M=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.open(d):console.warn("[IntentAI] Widget not ready. Call will be ignored."));},[]),U=useCallback(()=>{var d;v&&((d=window.IntentAI)==null||d.close());},[]),B=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.identify(d):R.current=d);},[]),K=useCallback((d,y)=>{var A;v&&((A=window.IntentAI)==null||A.track(d,y));},[]),S=useCallback(d=>{var y;v&&((y=window.IntentAI)==null||y.setMetadata(d));},[]),J=useMemo(()=>({isReady:b,error:N,open:M,close:U,identify:B,track:K,setMetadata:S}),[b,N,M,U,B,K,S]);return jsx(G.Provider,{value:J,children:e})}function u(){let e=useContext(G);if(!e)throw new Error('useIntentAI must be used within an IntentAIProvider. Wrap your app with <IntentAIProvider apiKey="pk_...">.');return e}function te(e){let{identify:t,isReady:r}=u();useEffect(()=>{r&&(e!=null&&e.id)&&t(e);},[r,e==null?void 0:e.id,e==null?void 0:e.email,e==null?void 0:e.name,t,e]);}var oe=forwardRef(function({openOptions:t,children:r="Feedback",onClick:n,disabled:p,...s},a){let{open:I,isReady:f}=u();return jsx("button",{ref:a,type:"button",onClick:m=>{n==null||n(m),m.defaultPrevented||I(t);},disabled:p||!f,"aria-label":"Open feedback",...s,children:r})});function ie({children:e,openOptions:t}){let{open:r,isReady:n,error:p}=u(),s=a=>{r(a||t);};return typeof e=="function"?e({open:s,isReady:n,error:p}):$.cloneElement(e,{onClick:a=>{var I,f;(f=(I=e.props).onClick)==null||f.call(I,a),a.defaultPrevented||s();},disabled:e.props.disabled||!n})}function de({user:e,children:t}){let{identify:r,isReady:n}=u();return useEffect(()=>{n&&(e!=null&&e.id)&&r(e);},[n,e,r]),jsx(Fragment,{children:t})}function ae({metadata:e,children:t}){let{setMetadata:r,isReady:n}=u();return useEffect(()=>{n&&e&&r(e);},[n,e,r]),jsx(Fragment,{children:t})}function se({event:e,properties:t,trigger:r="mount",children:n}){let{track:p,isReady:s}=u(),a=$.useRef(false);return useEffect(()=>{r==="mount"&&s&&!a.current&&(p(e,t),a.current=true);},[s,e,t,p,r]),r==="render"&&s&&p(e,t),jsx(Fragment,{children:n})}
2
- export{oe as FeedbackButton,ie as FeedbackTrigger,de as IdentifyUser,ee as IntentAIProvider,ae as SetMetadata,se as TrackEvent,te as useIdentify,u as useIntentAI};
1
+ "use client";
2
+
3
+ // src/index.tsx
4
+ import { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
5
+
6
+ // src/components/PremiumVoiceWidget.tsx
7
+ import {
8
+ useState,
9
+ useEffect,
10
+ useRef,
11
+ useCallback,
12
+ useMemo,
13
+ createContext,
14
+ useContext
15
+ } from "react";
16
+ import { motion, AnimatePresence } from "framer-motion";
17
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
+ var WidgetContext = createContext(null);
19
+ var useWidget = () => {
20
+ const context = useContext(WidgetContext);
21
+ if (!context) throw new Error("useWidget must be used within WidgetProvider");
22
+ return context;
23
+ };
24
+ var useReducedMotion = () => {
25
+ const [reducedMotion, setReducedMotion] = useState(false);
26
+ useEffect(() => {
27
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
28
+ setReducedMotion(mediaQuery.matches);
29
+ const handler = (e) => setReducedMotion(e.matches);
30
+ mediaQuery.addEventListener("change", handler);
31
+ return () => mediaQuery.removeEventListener("change", handler);
32
+ }, []);
33
+ return reducedMotion;
34
+ };
35
+ var useScrollAwareness = () => {
36
+ const [scrollState, setScrollState] = useState("idle");
37
+ const scrollTimeout = useRef(null);
38
+ useEffect(() => {
39
+ const handleScroll = () => {
40
+ setScrollState("scrolling");
41
+ if (scrollTimeout.current) {
42
+ clearTimeout(scrollTimeout.current);
43
+ }
44
+ scrollTimeout.current = setTimeout(() => {
45
+ setScrollState("stopped");
46
+ setTimeout(() => setScrollState("idle"), 300);
47
+ }, 150);
48
+ };
49
+ window.addEventListener("scroll", handleScroll, { passive: true });
50
+ return () => window.removeEventListener("scroll", handleScroll);
51
+ }, []);
52
+ return scrollState;
53
+ };
54
+ var useFrustrationDetection = () => {
55
+ const [frustrationLevel, setFrustrationLevel] = useState(0);
56
+ const clickTimestamps = useRef([]);
57
+ const signals = useRef([]);
58
+ useEffect(() => {
59
+ const handleClick = () => {
60
+ const now = Date.now();
61
+ clickTimestamps.current.push(now);
62
+ clickTimestamps.current = clickTimestamps.current.filter((t) => now - t < 500);
63
+ if (clickTimestamps.current.length >= 4) {
64
+ signals.current.push({ weight: 3, timestamp: now });
65
+ clickTimestamps.current = [];
66
+ }
67
+ };
68
+ const handleFormError = () => {
69
+ signals.current.push({ weight: 2, timestamp: Date.now() });
70
+ };
71
+ document.addEventListener("click", handleClick);
72
+ document.addEventListener("invalid", handleFormError, true);
73
+ const decayInterval = setInterval(() => {
74
+ const now = Date.now();
75
+ signals.current = signals.current.filter((s) => now - s.timestamp < 3e4);
76
+ const totalWeight = signals.current.reduce((sum, s) => sum + s.weight, 0);
77
+ setFrustrationLevel(Math.min(10, totalWeight));
78
+ }, 1e3);
79
+ return () => {
80
+ document.removeEventListener("click", handleClick);
81
+ document.removeEventListener("invalid", handleFormError, true);
82
+ clearInterval(decayInterval);
83
+ };
84
+ }, []);
85
+ return frustrationLevel;
86
+ };
87
+ var useTimeOnPage = () => {
88
+ const [timeOnPage, setTimeOnPage] = useState(0);
89
+ useEffect(() => {
90
+ const interval = setInterval(() => {
91
+ setTimeOnPage((prev) => prev + 1);
92
+ }, 1e3);
93
+ return () => clearInterval(interval);
94
+ }, []);
95
+ return timeOnPage;
96
+ };
97
+ var useAudioLevel = (isRecording) => {
98
+ const [audioLevel, setAudioLevel] = useState(0);
99
+ const analyserRef = useRef(null);
100
+ const animationRef = useRef();
101
+ useEffect(() => {
102
+ if (!isRecording) {
103
+ setAudioLevel(0);
104
+ return;
105
+ }
106
+ const initAudio = async () => {
107
+ try {
108
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
109
+ const audioContext = new AudioContext();
110
+ const source = audioContext.createMediaStreamSource(stream);
111
+ const analyser = audioContext.createAnalyser();
112
+ analyser.fftSize = 256;
113
+ source.connect(analyser);
114
+ analyserRef.current = analyser;
115
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
116
+ const updateLevel = () => {
117
+ if (!analyserRef.current) return;
118
+ analyserRef.current.getByteFrequencyData(dataArray);
119
+ const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
120
+ setAudioLevel(average / 255);
121
+ animationRef.current = requestAnimationFrame(updateLevel);
122
+ };
123
+ updateLevel();
124
+ } catch (error) {
125
+ console.error("Failed to access microphone:", error);
126
+ }
127
+ };
128
+ initAudio();
129
+ return () => {
130
+ if (animationRef.current) {
131
+ cancelAnimationFrame(animationRef.current);
132
+ }
133
+ };
134
+ }, [isRecording]);
135
+ return audioLevel;
136
+ };
137
+ var springPresets = {
138
+ snappy: { type: "spring", stiffness: 500, damping: 30 },
139
+ bouncy: { type: "spring", stiffness: 400, damping: 15 },
140
+ smooth: { type: "spring", stiffness: 300, damping: 30 },
141
+ gentle: { type: "spring", stiffness: 200, damping: 25 }
142
+ };
143
+ var triggerVariants = {
144
+ idle: {
145
+ scale: 1,
146
+ opacity: 1
147
+ },
148
+ hover: {
149
+ scale: 1.08,
150
+ transition: springPresets.bouncy
151
+ },
152
+ tap: {
153
+ scale: 0.95,
154
+ transition: springPresets.snappy
155
+ },
156
+ scrolling: {
157
+ scale: 0.85,
158
+ opacity: 0.3,
159
+ transition: { duration: 0.2 }
160
+ },
161
+ hidden: {
162
+ scale: 0,
163
+ opacity: 0,
164
+ transition: springPresets.smooth
165
+ }
166
+ };
167
+ var containerVariants = {
168
+ trigger: {
169
+ width: 56,
170
+ height: 56,
171
+ borderRadius: 28,
172
+ transition: springPresets.smooth
173
+ },
174
+ expanded: {
175
+ width: 360,
176
+ height: "auto",
177
+ borderRadius: 20,
178
+ transition: {
179
+ ...springPresets.smooth,
180
+ staggerChildren: 0.05,
181
+ delayChildren: 0.15
182
+ }
183
+ }
184
+ };
185
+ var contentVariants = {
186
+ hidden: {
187
+ opacity: 0,
188
+ y: 20,
189
+ scale: 0.95
190
+ },
191
+ visible: {
192
+ opacity: 1,
193
+ y: 0,
194
+ scale: 1,
195
+ transition: springPresets.smooth
196
+ },
197
+ exit: {
198
+ opacity: 0,
199
+ y: -10,
200
+ scale: 0.95,
201
+ transition: { duration: 0.2 }
202
+ }
203
+ };
204
+ var categoryVariants = {
205
+ hidden: { opacity: 0, y: 20, scale: 0.9 },
206
+ visible: (i) => ({
207
+ opacity: 1,
208
+ y: 0,
209
+ scale: 1,
210
+ transition: {
211
+ delay: i * 0.05,
212
+ ...springPresets.smooth
213
+ }
214
+ }),
215
+ hover: {
216
+ scale: 1.05,
217
+ y: -2,
218
+ transition: springPresets.bouncy
219
+ },
220
+ tap: {
221
+ scale: 0.95
222
+ },
223
+ selected: {
224
+ scale: 1,
225
+ borderColor: "var(--widget-primary)"
226
+ }
227
+ };
228
+ var BreathingRing = ({ isActive }) => {
229
+ const { reducedMotion } = useWidget();
230
+ if (reducedMotion) return null;
231
+ return /* @__PURE__ */ jsx(
232
+ motion.div,
233
+ {
234
+ className: "fiq-breathing-ring",
235
+ animate: {
236
+ scale: isActive ? [1, 1.15, 1] : 1,
237
+ opacity: isActive ? [0.5, 0.8, 0.5] : 0.5
238
+ },
239
+ transition: {
240
+ duration: 3,
241
+ repeat: Infinity,
242
+ ease: "easeInOut"
243
+ },
244
+ style: {
245
+ position: "absolute",
246
+ inset: -8,
247
+ borderRadius: "50%",
248
+ background: "radial-gradient(circle, var(--widget-primary-glow) 0%, transparent 70%)",
249
+ pointerEvents: "none"
250
+ }
251
+ }
252
+ );
253
+ };
254
+ var AmbientOrb = ({ audioLevel, isRecording }) => {
255
+ const { reducedMotion } = useWidget();
256
+ const [particles, setParticles] = useState([]);
257
+ useEffect(() => {
258
+ if (!isRecording || audioLevel < 0.3 || reducedMotion) return;
259
+ const particleCount = Math.floor(audioLevel * 4);
260
+ const newParticles = Array.from({ length: particleCount }, () => ({
261
+ id: Math.random().toString(36).slice(2, 11),
262
+ x: 70,
263
+ y: 70,
264
+ angle: Math.random() * Math.PI * 2,
265
+ speed: 1 + Math.random() * 3,
266
+ size: 2 + Math.random() * 3,
267
+ color: Math.random() > 0.5 ? "var(--widget-primary-light)" : "var(--widget-accent)"
268
+ }));
269
+ setParticles((prev) => [...prev, ...newParticles].slice(-40));
270
+ }, [audioLevel, isRecording, reducedMotion]);
271
+ useEffect(() => {
272
+ if (particles.length === 0) return;
273
+ const timeout = setTimeout(() => {
274
+ setParticles((prev) => prev.slice(1));
275
+ }, 100);
276
+ return () => clearTimeout(timeout);
277
+ }, [particles]);
278
+ const orbScale = 1 + audioLevel * 0.2;
279
+ return /* @__PURE__ */ jsx("div", { className: "fiq-ambient-orb", style: { position: "relative", width: 140, height: 140, margin: "0 auto" }, children: /* @__PURE__ */ jsxs("svg", { width: 140, height: 140, viewBox: "0 0 140 140", children: [
280
+ /* @__PURE__ */ jsxs("defs", { children: [
281
+ /* @__PURE__ */ jsxs("radialGradient", { id: "orbGradient", cx: "30%", cy: "30%", children: [
282
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--widget-accent-light)" }),
283
+ /* @__PURE__ */ jsx("stop", { offset: "40%", stopColor: "var(--widget-primary)" }),
284
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--widget-primary-hover)" })
285
+ ] }),
286
+ /* @__PURE__ */ jsxs("filter", { id: "orbGlow", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
287
+ /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "8", result: "blur" }),
288
+ /* @__PURE__ */ jsx("feComposite", { in: "SourceGraphic", in2: "blur", operator: "over" })
289
+ ] })
290
+ ] }),
291
+ /* @__PURE__ */ jsx(
292
+ motion.circle,
293
+ {
294
+ cx: 70,
295
+ cy: 70,
296
+ r: 50,
297
+ fill: "none",
298
+ stroke: "var(--widget-primary-glow)",
299
+ strokeWidth: 2,
300
+ animate: {
301
+ r: isRecording ? [50, 58, 50] : 50,
302
+ opacity: isRecording ? [0.3, 0.6, 0.3] : 0.3
303
+ },
304
+ transition: {
305
+ duration: 2,
306
+ repeat: Infinity,
307
+ ease: "easeInOut"
308
+ }
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsx(
312
+ motion.circle,
313
+ {
314
+ cx: 70,
315
+ cy: 70,
316
+ r: 40,
317
+ fill: "url(#orbGradient)",
318
+ filter: "url(#orbGlow)",
319
+ animate: { scale: reducedMotion ? 1 : orbScale },
320
+ transition: springPresets.snappy,
321
+ style: { transformOrigin: "center" }
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsxs("g", { transform: "translate(58, 55)", children: [
325
+ /* @__PURE__ */ jsx(
326
+ motion.path,
327
+ {
328
+ d: "M20 4a6 6 0 0 0-6 6v16a6 6 0 0 0 12 0V10a6 6 0 0 0-6-6z",
329
+ fill: "none",
330
+ stroke: "white",
331
+ strokeWidth: "2.5",
332
+ strokeLinecap: "round",
333
+ animate: {
334
+ opacity: isRecording ? [1, 0.7, 1] : 1
335
+ },
336
+ transition: {
337
+ duration: 1,
338
+ repeat: isRecording ? Infinity : 0
339
+ }
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsx(
343
+ "path",
344
+ {
345
+ d: "M32 20v4a14 14 0 0 1-28 0v-4M20 38v10M12 48h16",
346
+ fill: "none",
347
+ stroke: "white",
348
+ strokeWidth: "2.5",
349
+ strokeLinecap: "round"
350
+ }
351
+ )
352
+ ] }),
353
+ particles.map((particle) => /* @__PURE__ */ jsx(
354
+ motion.circle,
355
+ {
356
+ cx: 70,
357
+ cy: 70,
358
+ r: particle.size,
359
+ fill: particle.color,
360
+ initial: { opacity: 0.8, scale: 1 },
361
+ animate: {
362
+ cx: 70 + Math.cos(particle.angle) * 50,
363
+ cy: 70 + Math.sin(particle.angle) * 50,
364
+ opacity: 0,
365
+ scale: 0.5
366
+ },
367
+ transition: { duration: 1.5, ease: "easeOut" }
368
+ },
369
+ particle.id
370
+ ))
371
+ ] }) });
372
+ };
373
+ var RecordingTimer = ({
374
+ seconds,
375
+ maxDuration
376
+ }) => {
377
+ const minutes = Math.floor(seconds / 60);
378
+ const secs = seconds % 60;
379
+ const progress = seconds / maxDuration;
380
+ const isNearLimit = seconds >= maxDuration - 30;
381
+ return /* @__PURE__ */ jsxs("div", { className: "fiq-timer", style: { textAlign: "center" }, children: [
382
+ /* @__PURE__ */ jsxs(
383
+ motion.div,
384
+ {
385
+ style: {
386
+ fontSize: 36,
387
+ fontWeight: 700,
388
+ fontFamily: "ui-monospace, monospace",
389
+ color: isNearLimit ? "var(--widget-recording)" : "var(--widget-text-primary)"
390
+ },
391
+ animate: isNearLimit ? { scale: [1, 1.05, 1] } : {},
392
+ transition: { duration: 0.5, repeat: isNearLimit ? Infinity : 0 },
393
+ children: [
394
+ String(minutes).padStart(2, "0"),
395
+ ":",
396
+ String(secs).padStart(2, "0")
397
+ ]
398
+ }
399
+ ),
400
+ /* @__PURE__ */ jsx(
401
+ "div",
402
+ {
403
+ style: {
404
+ width: 160,
405
+ height: 4,
406
+ background: "var(--widget-glass-border)",
407
+ borderRadius: 2,
408
+ margin: "12px auto",
409
+ overflow: "hidden"
410
+ },
411
+ children: /* @__PURE__ */ jsx(
412
+ motion.div,
413
+ {
414
+ style: {
415
+ height: "100%",
416
+ background: isNearLimit ? "var(--widget-recording)" : "var(--widget-primary)",
417
+ borderRadius: 2
418
+ },
419
+ initial: { width: 0 },
420
+ animate: { width: `${progress * 100}%` },
421
+ transition: { duration: 0.5 }
422
+ }
423
+ )
424
+ }
425
+ )
426
+ ] });
427
+ };
428
+ var TranscriptionDisplay = ({ text, isLive }) => {
429
+ const { reducedMotion } = useWidget();
430
+ const [displayedText, setDisplayedText] = useState("");
431
+ const [cursorVisible, setCursorVisible] = useState(true);
432
+ useEffect(() => {
433
+ if (reducedMotion) {
434
+ setDisplayedText(text);
435
+ return;
436
+ }
437
+ if (text.length > displayedText.length) {
438
+ const timeout = setTimeout(() => {
439
+ setDisplayedText(text.slice(0, displayedText.length + 1));
440
+ }, 30 + Math.random() * 40);
441
+ return () => clearTimeout(timeout);
442
+ }
443
+ }, [text, displayedText, reducedMotion]);
444
+ useEffect(() => {
445
+ if (!isLive) return;
446
+ const interval = setInterval(() => setCursorVisible((v) => !v), 530);
447
+ return () => clearInterval(interval);
448
+ }, [isLive]);
449
+ return /* @__PURE__ */ jsxs(
450
+ "div",
451
+ {
452
+ className: "fiq-transcription",
453
+ style: {
454
+ padding: 16,
455
+ background: "var(--widget-glass-bg)",
456
+ borderRadius: 12,
457
+ border: "1px solid var(--widget-glass-border)",
458
+ minHeight: 80,
459
+ maxHeight: 120,
460
+ overflow: "auto"
461
+ },
462
+ children: [
463
+ /* @__PURE__ */ jsxs("p", { style: { color: "var(--widget-text-primary)", fontSize: 14, lineHeight: 1.6, margin: 0 }, children: [
464
+ displayedText || /* @__PURE__ */ jsx("span", { style: { color: "var(--widget-text-muted)", fontStyle: "italic" }, children: "Start speaking..." }),
465
+ isLive && /* @__PURE__ */ jsx(
466
+ motion.span,
467
+ {
468
+ animate: { opacity: cursorVisible ? 1 : 0 },
469
+ style: {
470
+ display: "inline-block",
471
+ width: 2,
472
+ height: "1.2em",
473
+ background: "var(--widget-primary)",
474
+ marginLeft: 2,
475
+ verticalAlign: "text-bottom"
476
+ }
477
+ }
478
+ )
479
+ ] }),
480
+ isLive && /* @__PURE__ */ jsxs(
481
+ motion.div,
482
+ {
483
+ initial: { opacity: 0, y: 5 },
484
+ animate: { opacity: 1, y: 0 },
485
+ style: {
486
+ display: "flex",
487
+ alignItems: "center",
488
+ gap: 8,
489
+ marginTop: 12,
490
+ color: "var(--widget-text-secondary)",
491
+ fontSize: 12
492
+ },
493
+ children: [
494
+ /* @__PURE__ */ jsx(ListeningDots, {}),
495
+ /* @__PURE__ */ jsx("span", { children: "Listening..." })
496
+ ]
497
+ }
498
+ )
499
+ ]
500
+ }
501
+ );
502
+ };
503
+ var CategoryIcon = ({ icon, selected }) => {
504
+ const color = selected ? "white" : "var(--widget-primary)";
505
+ const size = 20;
506
+ const icons = {
507
+ bug: /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
508
+ /* @__PURE__ */ jsx("path", { d: "M8 2l1.88 1.88M14.12 3.88L16 2M9 7.13v-1a3.003 3.003 0 116 0v1" }),
509
+ /* @__PURE__ */ jsx("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 014-4h4a4 4 0 014 4v3c0 3.3-2.7 6-6 6" }),
510
+ /* @__PURE__ */ jsx("path", { d: "M12 20v-9M6.53 9C4.6 8.8 3 7.1 3 5M6 13H2M3 21c0-2.1 1.7-3.9 3.8-4M20.97 5c0 2.1-1.6 3.8-3.5 4M22 13h-4M17.2 17c2.1.1 3.8 1.9 3.8 4" })
511
+ ] }),
512
+ sparkles: /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 3l1.912 5.813a2 2 0 001.275 1.275L21 12l-5.813 1.912a2 2 0 00-1.275 1.275L12 21l-1.912-5.813a2 2 0 00-1.275-1.275L3 12l5.813-1.912a2 2 0 001.275-1.275L12 3z" }) }),
513
+ message: /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z" }) })
514
+ };
515
+ return /* @__PURE__ */ jsx(Fragment, { children: icons[icon] || icons.message });
516
+ };
517
+ var ListeningDots = () => /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
518
+ motion.span,
519
+ {
520
+ animate: {
521
+ scale: [1, 1.4, 1],
522
+ opacity: [0.5, 1, 0.5]
523
+ },
524
+ transition: {
525
+ duration: 1,
526
+ repeat: Infinity,
527
+ delay: i * 0.15
528
+ },
529
+ style: {
530
+ width: 6,
531
+ height: 6,
532
+ borderRadius: "50%",
533
+ background: "var(--widget-primary)"
534
+ }
535
+ },
536
+ i
537
+ )) });
538
+ var CategorySelector = ({ categories, selected, onSelect }) => {
539
+ return /* @__PURE__ */ jsxs("div", { className: "fiq-category-selector", children: [
540
+ /* @__PURE__ */ jsx(
541
+ "label",
542
+ {
543
+ style: {
544
+ display: "block",
545
+ fontSize: 13,
546
+ fontWeight: 600,
547
+ color: "var(--widget-text-secondary)",
548
+ marginBottom: 10,
549
+ textTransform: "uppercase",
550
+ letterSpacing: "0.05em"
551
+ },
552
+ children: "Category"
553
+ }
554
+ ),
555
+ /* @__PURE__ */ jsx(
556
+ "div",
557
+ {
558
+ style: {
559
+ display: "grid",
560
+ gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
561
+ gap: 10
562
+ },
563
+ children: categories.map((category, index) => /* @__PURE__ */ jsxs(
564
+ motion.button,
565
+ {
566
+ custom: index,
567
+ variants: categoryVariants,
568
+ initial: "hidden",
569
+ animate: selected === category.id ? "selected" : "visible",
570
+ whileHover: "hover",
571
+ whileTap: "tap",
572
+ onClick: () => onSelect(category.id),
573
+ style: {
574
+ display: "flex",
575
+ flexDirection: "column",
576
+ alignItems: "center",
577
+ gap: 6,
578
+ padding: "12px 8px",
579
+ background: selected === category.id ? "var(--widget-primary)" : "var(--widget-glass-bg)",
580
+ border: `2px solid ${selected === category.id ? "var(--widget-primary)" : "var(--widget-glass-border)"}`,
581
+ borderRadius: 12,
582
+ cursor: "pointer",
583
+ transition: "background 0.2s, border-color 0.2s"
584
+ },
585
+ children: [
586
+ /* @__PURE__ */ jsx(
587
+ motion.span,
588
+ {
589
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
590
+ animate: selected === category.id ? { scale: [1, 1.2, 1] } : {},
591
+ transition: { duration: 0.3 },
592
+ children: /* @__PURE__ */ jsx(CategoryIcon, { icon: category.icon, selected: selected === category.id })
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsx(
596
+ "span",
597
+ {
598
+ style: {
599
+ fontSize: 12,
600
+ fontWeight: 500,
601
+ color: selected === category.id ? "white" : "var(--widget-text-primary)"
602
+ },
603
+ children: category.label
604
+ }
605
+ )
606
+ ]
607
+ },
608
+ category.id
609
+ ))
610
+ }
611
+ )
612
+ ] });
613
+ };
614
+ var SuccessCelebration = ({ onComplete }) => {
615
+ const { reducedMotion } = useWidget();
616
+ const confettiParticles = useMemo(
617
+ () => reducedMotion ? [] : Array.from({ length: 30 }, (_, i) => ({
618
+ id: i,
619
+ angle: i / 30 * Math.PI * 2,
620
+ distance: 100 + Math.random() * 80,
621
+ size: 4 + Math.random() * 6,
622
+ color: ["#10b981", "#22d3d3", "#fbbf24", "#f472b6"][Math.floor(Math.random() * 4)],
623
+ rotation: Math.random() * 360
624
+ })),
625
+ [reducedMotion]
626
+ );
627
+ useEffect(() => {
628
+ const timer = setTimeout(onComplete, 3e3);
629
+ return () => clearTimeout(timer);
630
+ }, [onComplete]);
631
+ return /* @__PURE__ */ jsxs(
632
+ motion.div,
633
+ {
634
+ initial: { opacity: 0, scale: 0.8 },
635
+ animate: { opacity: 1, scale: 1 },
636
+ exit: { opacity: 0, scale: 0.8 },
637
+ style: {
638
+ display: "flex",
639
+ flexDirection: "column",
640
+ alignItems: "center",
641
+ justifyContent: "center",
642
+ padding: 40,
643
+ textAlign: "center",
644
+ position: "relative",
645
+ overflow: "hidden"
646
+ },
647
+ children: [
648
+ confettiParticles.map((particle) => /* @__PURE__ */ jsx(
649
+ motion.div,
650
+ {
651
+ initial: { x: 0, y: 0, scale: 0, rotate: 0 },
652
+ animate: {
653
+ x: Math.cos(particle.angle) * particle.distance,
654
+ y: Math.sin(particle.angle) * particle.distance + 50,
655
+ scale: [0, 1, 1, 0],
656
+ rotate: particle.rotation
657
+ },
658
+ transition: { duration: 1.2, ease: [0.22, 1, 0.36, 1] },
659
+ style: {
660
+ position: "absolute",
661
+ top: "50%",
662
+ left: "50%",
663
+ width: particle.size,
664
+ height: particle.size,
665
+ background: particle.color,
666
+ borderRadius: Math.random() > 0.5 ? "50%" : 2
667
+ }
668
+ },
669
+ particle.id
670
+ )),
671
+ /* @__PURE__ */ jsx(motion.div, { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxs("svg", { width: 80, height: 80, viewBox: "0 0 80 80", children: [
672
+ /* @__PURE__ */ jsx(
673
+ motion.circle,
674
+ {
675
+ cx: 40,
676
+ cy: 40,
677
+ r: 36,
678
+ fill: "var(--widget-success)",
679
+ initial: { scale: 0 },
680
+ animate: { scale: 1 },
681
+ transition: springPresets.bouncy
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsx(
685
+ motion.path,
686
+ {
687
+ d: "M24 40 L35 51 L56 28",
688
+ fill: "none",
689
+ stroke: "white",
690
+ strokeWidth: 5,
691
+ strokeLinecap: "round",
692
+ strokeLinejoin: "round",
693
+ initial: { pathLength: 0 },
694
+ animate: { pathLength: 1 },
695
+ transition: { delay: 0.3, duration: 0.5, ease: "easeOut" }
696
+ }
697
+ )
698
+ ] }) }),
699
+ /* @__PURE__ */ jsx(
700
+ motion.h3,
701
+ {
702
+ initial: { opacity: 0, y: 20 },
703
+ animate: { opacity: 1, y: 0 },
704
+ transition: { delay: 0.5 },
705
+ style: {
706
+ fontSize: 24,
707
+ fontWeight: 700,
708
+ color: "var(--widget-text-primary)",
709
+ margin: 0,
710
+ marginBottom: 8
711
+ },
712
+ children: "Thank you!"
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsx(
716
+ motion.p,
717
+ {
718
+ initial: { opacity: 0, y: 20 },
719
+ animate: { opacity: 1, y: 0 },
720
+ transition: { delay: 0.6 },
721
+ style: {
722
+ fontSize: 14,
723
+ color: "var(--widget-text-secondary)",
724
+ margin: 0
725
+ },
726
+ children: "Your feedback helps us improve"
727
+ }
728
+ ),
729
+ /* @__PURE__ */ jsx(
730
+ motion.div,
731
+ {
732
+ style: {
733
+ position: "absolute",
734
+ bottom: 20,
735
+ left: "50%",
736
+ transform: "translateX(-50%)",
737
+ width: 100,
738
+ height: 3,
739
+ background: "var(--widget-glass-border)",
740
+ borderRadius: 2,
741
+ overflow: "hidden"
742
+ },
743
+ children: /* @__PURE__ */ jsx(
744
+ motion.div,
745
+ {
746
+ initial: { width: "100%" },
747
+ animate: { width: 0 },
748
+ transition: { duration: 3, ease: "linear" },
749
+ style: {
750
+ height: "100%",
751
+ background: "var(--widget-primary)"
752
+ }
753
+ }
754
+ )
755
+ }
756
+ )
757
+ ]
758
+ }
759
+ );
760
+ };
761
+ var MagneticButton = ({ children, onClick, variant = "primary", disabled, loading }) => {
762
+ const buttonRef = useRef(null);
763
+ const [magnetOffset, setMagnetOffset] = useState({ x: 0, y: 0 });
764
+ const handleMouseMove = (e) => {
765
+ if (!buttonRef.current || disabled) return;
766
+ const rect = buttonRef.current.getBoundingClientRect();
767
+ const centerX = rect.left + rect.width / 2;
768
+ const centerY = rect.top + rect.height / 2;
769
+ setMagnetOffset({
770
+ x: (e.clientX - centerX) * 0.1,
771
+ y: (e.clientY - centerY) * 0.1
772
+ });
773
+ };
774
+ const handleMouseLeave = () => {
775
+ setMagnetOffset({ x: 0, y: 0 });
776
+ };
777
+ const baseStyles = {
778
+ width: "100%",
779
+ padding: "14px 24px",
780
+ fontSize: 15,
781
+ fontWeight: 600,
782
+ borderRadius: 12,
783
+ border: "none",
784
+ cursor: disabled ? "not-allowed" : "pointer",
785
+ transition: "background 0.2s, opacity 0.2s",
786
+ display: "flex",
787
+ alignItems: "center",
788
+ justifyContent: "center",
789
+ gap: 8
790
+ };
791
+ const variantStyles = {
792
+ primary: {
793
+ background: "var(--widget-primary)",
794
+ color: "white"
795
+ },
796
+ secondary: {
797
+ background: "var(--widget-glass-bg)",
798
+ color: "var(--widget-text-primary)",
799
+ border: "1px solid var(--widget-glass-border)"
800
+ },
801
+ ghost: {
802
+ background: "transparent",
803
+ color: "var(--widget-text-secondary)"
804
+ }
805
+ };
806
+ return /* @__PURE__ */ jsx(
807
+ motion.button,
808
+ {
809
+ ref: buttonRef,
810
+ onClick,
811
+ disabled: disabled || loading,
812
+ onMouseMove: handleMouseMove,
813
+ onMouseLeave: handleMouseLeave,
814
+ animate: {
815
+ x: magnetOffset.x,
816
+ y: magnetOffset.y,
817
+ opacity: disabled ? 0.5 : 1
818
+ },
819
+ whileHover: { scale: disabled ? 1 : 1.02 },
820
+ whileTap: { scale: disabled ? 1 : 0.97 },
821
+ transition: springPresets.snappy,
822
+ style: { ...baseStyles, ...variantStyles[variant] },
823
+ children: loading ? /* @__PURE__ */ jsx(
824
+ motion.div,
825
+ {
826
+ animate: { rotate: 360 },
827
+ transition: { duration: 1, repeat: Infinity, ease: "linear" },
828
+ style: {
829
+ width: 18,
830
+ height: 18,
831
+ border: "2px solid rgba(255,255,255,0.3)",
832
+ borderTopColor: "white",
833
+ borderRadius: "50%"
834
+ }
835
+ }
836
+ ) : children
837
+ }
838
+ );
839
+ };
840
+ var DEFAULT_CATEGORIES = [
841
+ { id: "bug", label: "Bug", icon: "bug" },
842
+ { id: "feature", label: "Feature", icon: "sparkles" },
843
+ { id: "feedback", label: "Feedback", icon: "message" }
844
+ ];
845
+ var PremiumVoiceWidget = ({
846
+ position = "bottom-right",
847
+ primaryColor = "#10b981",
848
+ accentColor = "#22d3d3",
849
+ theme = "dark",
850
+ categories = DEFAULT_CATEGORIES,
851
+ allowVoice = true,
852
+ allowText = true,
853
+ maxRecordingDuration = 180,
854
+ onSubmit,
855
+ onOpen,
856
+ onClose
857
+ }) => {
858
+ const [state, setState] = useState("idle");
859
+ const [isExpanded, setIsExpanded] = useState(false);
860
+ const [activeTab, setActiveTab] = useState(allowVoice ? "voice" : "text");
861
+ const [selectedCategory, setSelectedCategory] = useState(null);
862
+ const [isRecording, setIsRecording] = useState(false);
863
+ const [recordingSeconds, setRecordingSeconds] = useState(0);
864
+ const [transcription, setTranscription] = useState("");
865
+ const [textContent, setTextContent] = useState("");
866
+ const [isSubmitting, setIsSubmitting] = useState(false);
867
+ const reducedMotion = useReducedMotion();
868
+ const scrollState = useScrollAwareness();
869
+ const frustrationLevel = useFrustrationDetection();
870
+ const timeOnPage = useTimeOnPage();
871
+ const audioLevel = useAudioLevel(isRecording);
872
+ const triggerRef = useRef(null);
873
+ const recordingInterval = useRef(null);
874
+ const positionStyles = {
875
+ "bottom-right": { bottom: 24, right: 24 },
876
+ "bottom-left": { bottom: 24, left: 24 },
877
+ "top-right": { top: 24, right: 24 },
878
+ "top-left": { top: 24, left: 24 }
879
+ };
880
+ const cssVariables = {
881
+ "--widget-primary": primaryColor,
882
+ "--widget-primary-hover": adjustColor(primaryColor, -20),
883
+ "--widget-primary-light": adjustColor(primaryColor, 30),
884
+ "--widget-primary-glow": `${primaryColor}66`,
885
+ "--widget-accent": accentColor,
886
+ "--widget-accent-light": adjustColor(accentColor, 30),
887
+ "--widget-accent-glow": `${accentColor}4d`,
888
+ "--widget-glass-bg": theme === "dark" ? "rgba(24, 24, 27, 0.85)" : "rgba(255, 255, 255, 0.85)",
889
+ "--widget-glass-border": theme === "dark" ? "rgba(63, 63, 70, 0.5)" : "rgba(0, 0, 0, 0.1)",
890
+ "--widget-glass-highlight": theme === "dark" ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.02)",
891
+ "--widget-text-primary": theme === "dark" ? "#fafafa" : "#18181b",
892
+ "--widget-text-secondary": theme === "dark" ? "#a1a1aa" : "#71717a",
893
+ "--widget-text-muted": theme === "dark" ? "#71717a" : "#a1a1aa",
894
+ "--widget-recording": "#ef4444",
895
+ "--widget-recording-glow": "rgba(239, 68, 68, 0.4)",
896
+ "--widget-success": "#22c55e",
897
+ "--widget-success-glow": "rgba(34, 197, 94, 0.4)",
898
+ "--widget-frustrated": "#f97316"
899
+ };
900
+ const handleOpen = useCallback(() => {
901
+ setIsExpanded(true);
902
+ setState("open");
903
+ onOpen?.();
904
+ }, [onOpen]);
905
+ const handleClose = useCallback(() => {
906
+ setIsExpanded(false);
907
+ setState("idle");
908
+ setIsRecording(false);
909
+ setRecordingSeconds(0);
910
+ setTranscription("");
911
+ onClose?.();
912
+ }, [onClose]);
913
+ const handleStartRecording = useCallback(() => {
914
+ setIsRecording(true);
915
+ setState("recording");
916
+ setRecordingSeconds(0);
917
+ const samplePhrases = [
918
+ "I really like the new dashboard design, ",
919
+ "but I think the navigation could be improved. ",
920
+ "Sometimes I have trouble finding the settings page."
921
+ ];
922
+ let phraseIndex = 0;
923
+ recordingInterval.current = setInterval(() => {
924
+ setRecordingSeconds((prev) => {
925
+ if (prev >= maxRecordingDuration) {
926
+ handleStopRecording();
927
+ return prev;
928
+ }
929
+ return prev + 1;
930
+ });
931
+ if (phraseIndex < samplePhrases.length && Math.random() > 0.7) {
932
+ setTranscription((prev) => prev + samplePhrases[phraseIndex]);
933
+ phraseIndex++;
934
+ }
935
+ }, 1e3);
936
+ }, [maxRecordingDuration]);
937
+ const handleStopRecording = useCallback(() => {
938
+ setIsRecording(false);
939
+ setState("open");
940
+ if (recordingInterval.current) {
941
+ clearInterval(recordingInterval.current);
942
+ }
943
+ }, []);
944
+ const handleSubmit = useCallback(async () => {
945
+ if (!selectedCategory) return;
946
+ setIsSubmitting(true);
947
+ setState("processing");
948
+ try {
949
+ await onSubmit?.({
950
+ type: activeTab,
951
+ content: activeTab === "voice" ? transcription : textContent,
952
+ category: selectedCategory,
953
+ transcription: activeTab === "voice" ? transcription : void 0
954
+ });
955
+ setState("success");
956
+ } catch (error) {
957
+ setState("error");
958
+ } finally {
959
+ setIsSubmitting(false);
960
+ }
961
+ }, [activeTab, selectedCategory, transcription, textContent, onSubmit]);
962
+ const handleSuccessComplete = useCallback(() => {
963
+ handleClose();
964
+ setSelectedCategory(null);
965
+ setTextContent("");
966
+ setTranscription("");
967
+ }, [handleClose]);
968
+ const contextValue = {
969
+ state,
970
+ setState,
971
+ audioLevel,
972
+ isRecording,
973
+ transcription,
974
+ selectedCategory,
975
+ setSelectedCategory,
976
+ frustrationLevel,
977
+ reducedMotion
978
+ };
979
+ const triggerState = useMemo(() => {
980
+ if (isExpanded) return "hidden";
981
+ if (scrollState === "scrolling") return "scrolling";
982
+ if (state === "hover") return "hover";
983
+ return "idle";
984
+ }, [isExpanded, scrollState, state]);
985
+ const shouldShowAttentionPulse = timeOnPage === 60 && !isExpanded && frustrationLevel < 3;
986
+ const [showPrompt, setShowPrompt] = useState(false);
987
+ const promptMessages = [
988
+ "Help us improve!",
989
+ "Got feedback?",
990
+ "Share your thoughts",
991
+ "We'd love to hear from you"
992
+ ];
993
+ const [promptMessage, setPromptMessage] = useState(promptMessages[0]);
994
+ useEffect(() => {
995
+ if (isExpanded) return;
996
+ const showTimes = [30, 90, 180];
997
+ if (showTimes.includes(timeOnPage)) {
998
+ setPromptMessage(promptMessages[Math.floor(Math.random() * promptMessages.length)]);
999
+ setShowPrompt(true);
1000
+ const timer = setTimeout(() => setShowPrompt(false), 4e3);
1001
+ return () => clearTimeout(timer);
1002
+ }
1003
+ }, [timeOnPage, isExpanded]);
1004
+ return /* @__PURE__ */ jsx(WidgetContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
1005
+ "div",
1006
+ {
1007
+ className: "fiq-premium-widget",
1008
+ style: {
1009
+ ...cssVariables,
1010
+ position: "fixed",
1011
+ ...positionStyles[position],
1012
+ zIndex: 2147483640,
1013
+ fontFamily: "system-ui, -apple-system, sans-serif"
1014
+ },
1015
+ children: [
1016
+ /* @__PURE__ */ jsxs(AnimatePresence, { mode: "wait", children: [
1017
+ !isExpanded && /* @__PURE__ */ jsxs(
1018
+ motion.button,
1019
+ {
1020
+ ref: triggerRef,
1021
+ variants: triggerVariants,
1022
+ initial: "idle",
1023
+ animate: triggerState,
1024
+ exit: "hidden",
1025
+ whileHover: "hover",
1026
+ whileTap: "tap",
1027
+ onClick: handleOpen,
1028
+ onMouseEnter: () => setState("hover"),
1029
+ onMouseLeave: () => setState("idle"),
1030
+ "aria-label": "Open feedback widget",
1031
+ "aria-expanded": isExpanded,
1032
+ style: {
1033
+ width: 56,
1034
+ height: 56,
1035
+ borderRadius: "50%",
1036
+ border: "none",
1037
+ cursor: "pointer",
1038
+ position: "relative",
1039
+ background: "var(--widget-glass-bg)",
1040
+ backdropFilter: "blur(20px) saturate(180%)",
1041
+ WebkitBackdropFilter: "blur(20px) saturate(180%)",
1042
+ boxShadow: `
1043
+ inset 0 0 0 1px var(--widget-glass-highlight),
1044
+ 0 4px 24px -4px rgba(0, 0, 0, 0.5),
1045
+ 0 0 40px -10px var(--widget-primary-glow)
1046
+ `
1047
+ },
1048
+ children: [
1049
+ /* @__PURE__ */ jsx(BreathingRing, { isActive: !reducedMotion && triggerState === "idle" }),
1050
+ /* @__PURE__ */ jsx(
1051
+ "div",
1052
+ {
1053
+ style: {
1054
+ position: "absolute",
1055
+ inset: 3,
1056
+ borderRadius: "50%",
1057
+ background: `linear-gradient(135deg, var(--widget-primary) 0%, var(--widget-accent) 100%)`,
1058
+ opacity: 0.9
1059
+ }
1060
+ }
1061
+ ),
1062
+ /* @__PURE__ */ jsxs(
1063
+ "svg",
1064
+ {
1065
+ width: 24,
1066
+ height: 24,
1067
+ viewBox: "0 0 24 24",
1068
+ fill: "none",
1069
+ stroke: "white",
1070
+ strokeWidth: 2,
1071
+ strokeLinecap: "round",
1072
+ style: {
1073
+ position: "absolute",
1074
+ top: "50%",
1075
+ left: "50%",
1076
+ transform: "translate(-50%, -50%)",
1077
+ zIndex: 1
1078
+ },
1079
+ children: [
1080
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
1081
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1082
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
1083
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
1084
+ ]
1085
+ }
1086
+ ),
1087
+ shouldShowAttentionPulse && /* @__PURE__ */ jsx(
1088
+ motion.div,
1089
+ {
1090
+ initial: { scale: 1, opacity: 1 },
1091
+ animate: { scale: 1.5, opacity: 0 },
1092
+ transition: { duration: 1, repeat: 3 },
1093
+ style: {
1094
+ position: "absolute",
1095
+ inset: 0,
1096
+ borderRadius: "50%",
1097
+ border: "2px solid var(--widget-primary)",
1098
+ pointerEvents: "none"
1099
+ }
1100
+ }
1101
+ ),
1102
+ frustrationLevel >= 5 && /* @__PURE__ */ jsx(
1103
+ motion.div,
1104
+ {
1105
+ initial: { scale: 0 },
1106
+ animate: { scale: 1 },
1107
+ style: {
1108
+ position: "absolute",
1109
+ top: -4,
1110
+ right: -4,
1111
+ width: 16,
1112
+ height: 16,
1113
+ borderRadius: "50%",
1114
+ background: "var(--widget-frustrated)",
1115
+ border: "2px solid var(--widget-glass-bg)",
1116
+ display: "flex",
1117
+ alignItems: "center",
1118
+ justifyContent: "center",
1119
+ fontSize: 10
1120
+ },
1121
+ children: "?"
1122
+ }
1123
+ )
1124
+ ]
1125
+ },
1126
+ "trigger"
1127
+ ),
1128
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showPrompt && !isExpanded && /* @__PURE__ */ jsxs(
1129
+ motion.div,
1130
+ {
1131
+ initial: { opacity: 0, x: 10, scale: 0.9 },
1132
+ animate: { opacity: 1, x: 0, scale: 1 },
1133
+ exit: { opacity: 0, x: 10, scale: 0.9 },
1134
+ transition: springPresets.smooth,
1135
+ onClick: handleOpen,
1136
+ style: {
1137
+ position: "absolute",
1138
+ right: 70,
1139
+ top: "50%",
1140
+ transform: "translateY(-50%)",
1141
+ background: "var(--widget-glass-bg)",
1142
+ backdropFilter: "blur(12px)",
1143
+ WebkitBackdropFilter: "blur(12px)",
1144
+ border: "1px solid var(--widget-glass-border)",
1145
+ borderRadius: 12,
1146
+ padding: "10px 16px",
1147
+ boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
1148
+ cursor: "pointer",
1149
+ whiteSpace: "nowrap"
1150
+ },
1151
+ children: [
1152
+ /* @__PURE__ */ jsx("span", { style: {
1153
+ fontSize: 13,
1154
+ fontWeight: 500,
1155
+ color: "var(--widget-text-primary)"
1156
+ }, children: promptMessage }),
1157
+ /* @__PURE__ */ jsx(
1158
+ "div",
1159
+ {
1160
+ style: {
1161
+ position: "absolute",
1162
+ right: -6,
1163
+ top: "50%",
1164
+ transform: "translateY(-50%) rotate(45deg)",
1165
+ width: 12,
1166
+ height: 12,
1167
+ background: "var(--widget-glass-bg)",
1168
+ borderRight: "1px solid var(--widget-glass-border)",
1169
+ borderTop: "1px solid var(--widget-glass-border)"
1170
+ }
1171
+ }
1172
+ )
1173
+ ]
1174
+ }
1175
+ ) })
1176
+ ] }),
1177
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isExpanded && /* @__PURE__ */ jsx(
1178
+ motion.div,
1179
+ {
1180
+ variants: containerVariants,
1181
+ initial: "trigger",
1182
+ animate: "expanded",
1183
+ exit: "trigger",
1184
+ style: {
1185
+ background: "var(--widget-glass-bg)",
1186
+ backdropFilter: "blur(20px) saturate(180%)",
1187
+ WebkitBackdropFilter: "blur(20px) saturate(180%)",
1188
+ border: "1px solid var(--widget-glass-border)",
1189
+ boxShadow: "0 20px 60px -12px rgba(0, 0, 0, 0.5)",
1190
+ overflow: "hidden",
1191
+ display: "flex",
1192
+ flexDirection: "column"
1193
+ },
1194
+ role: "dialog",
1195
+ "aria-modal": "true",
1196
+ "aria-labelledby": "widget-title",
1197
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: state === "success" ? /* @__PURE__ */ jsx(SuccessCelebration, { onComplete: handleSuccessComplete }, "success") : /* @__PURE__ */ jsxs(
1198
+ motion.div,
1199
+ {
1200
+ variants: contentVariants,
1201
+ initial: "hidden",
1202
+ animate: "visible",
1203
+ exit: "exit",
1204
+ style: { display: "flex", flexDirection: "column", height: "100%" },
1205
+ children: [
1206
+ /* @__PURE__ */ jsxs(
1207
+ "div",
1208
+ {
1209
+ style: {
1210
+ display: "flex",
1211
+ justifyContent: "space-between",
1212
+ alignItems: "center",
1213
+ padding: "20px 24px",
1214
+ borderBottom: "1px solid var(--widget-glass-border)"
1215
+ },
1216
+ children: [
1217
+ /* @__PURE__ */ jsx(
1218
+ "h2",
1219
+ {
1220
+ id: "widget-title",
1221
+ style: {
1222
+ fontSize: 18,
1223
+ fontWeight: 700,
1224
+ color: "var(--widget-text-primary)",
1225
+ margin: 0
1226
+ },
1227
+ children: "Share Your Thoughts"
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ jsx(
1231
+ motion.button,
1232
+ {
1233
+ whileHover: { scale: 1.1, rotate: 90 },
1234
+ whileTap: { scale: 0.9 },
1235
+ onClick: handleClose,
1236
+ "aria-label": "Close widget",
1237
+ style: {
1238
+ width: 32,
1239
+ height: 32,
1240
+ borderRadius: 8,
1241
+ border: "none",
1242
+ background: "transparent",
1243
+ color: "var(--widget-text-secondary)",
1244
+ cursor: "pointer",
1245
+ display: "flex",
1246
+ alignItems: "center",
1247
+ justifyContent: "center"
1248
+ },
1249
+ children: /* @__PURE__ */ jsx(
1250
+ "svg",
1251
+ {
1252
+ width: 20,
1253
+ height: 20,
1254
+ viewBox: "0 0 24 24",
1255
+ fill: "none",
1256
+ stroke: "currentColor",
1257
+ strokeWidth: 2,
1258
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
1259
+ }
1260
+ )
1261
+ }
1262
+ )
1263
+ ]
1264
+ }
1265
+ ),
1266
+ allowVoice && allowText && /* @__PURE__ */ jsx(
1267
+ "div",
1268
+ {
1269
+ style: {
1270
+ display: "flex",
1271
+ borderBottom: "1px solid var(--widget-glass-border)"
1272
+ },
1273
+ children: ["voice", "text"].map((tab) => /* @__PURE__ */ jsxs(
1274
+ motion.button,
1275
+ {
1276
+ onClick: () => setActiveTab(tab),
1277
+ whileHover: { backgroundColor: "rgba(255,255,255,0.05)" },
1278
+ style: {
1279
+ flex: 1,
1280
+ padding: "14px 0",
1281
+ border: "none",
1282
+ background: "transparent",
1283
+ fontSize: 14,
1284
+ fontWeight: 500,
1285
+ color: activeTab === tab ? "var(--widget-primary)" : "var(--widget-text-secondary)",
1286
+ cursor: "pointer",
1287
+ position: "relative"
1288
+ },
1289
+ children: [
1290
+ tab === "voice" ? "Voice" : "Text",
1291
+ activeTab === tab && /* @__PURE__ */ jsx(
1292
+ motion.div,
1293
+ {
1294
+ layoutId: "activeTab",
1295
+ style: {
1296
+ position: "absolute",
1297
+ bottom: -1,
1298
+ left: 0,
1299
+ right: 0,
1300
+ height: 2,
1301
+ background: "var(--widget-primary)"
1302
+ },
1303
+ transition: springPresets.smooth
1304
+ }
1305
+ )
1306
+ ]
1307
+ },
1308
+ tab
1309
+ ))
1310
+ }
1311
+ ),
1312
+ /* @__PURE__ */ jsxs("div", { style: { padding: "20px 24px" }, children: [
1313
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx(
1314
+ CategorySelector,
1315
+ {
1316
+ categories,
1317
+ selected: selectedCategory,
1318
+ onSelect: setSelectedCategory
1319
+ }
1320
+ ) }),
1321
+ activeTab === "voice" && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
1322
+ /* @__PURE__ */ jsx(AmbientOrb, { audioLevel, isRecording }),
1323
+ isRecording && /* @__PURE__ */ jsx(
1324
+ RecordingTimer,
1325
+ {
1326
+ seconds: recordingSeconds,
1327
+ maxDuration: maxRecordingDuration
1328
+ }
1329
+ ),
1330
+ !isRecording && recordingSeconds === 0 && /* @__PURE__ */ jsx(
1331
+ "p",
1332
+ {
1333
+ style: {
1334
+ color: "var(--widget-text-secondary)",
1335
+ fontSize: 14,
1336
+ marginTop: 16
1337
+ },
1338
+ children: "Tap the orb to start recording"
1339
+ }
1340
+ ),
1341
+ (isRecording || transcription) && /* @__PURE__ */ jsx("div", { style: { marginTop: 20 }, children: /* @__PURE__ */ jsx(
1342
+ TranscriptionDisplay,
1343
+ {
1344
+ text: transcription,
1345
+ isLive: isRecording
1346
+ }
1347
+ ) }),
1348
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 20 }, children: /* @__PURE__ */ jsx(
1349
+ MagneticButton,
1350
+ {
1351
+ onClick: isRecording ? handleStopRecording : handleStartRecording,
1352
+ variant: isRecording ? "secondary" : "primary",
1353
+ children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
1354
+ /* @__PURE__ */ jsx(
1355
+ "svg",
1356
+ {
1357
+ width: 16,
1358
+ height: 16,
1359
+ viewBox: "0 0 24 24",
1360
+ fill: "currentColor",
1361
+ children: /* @__PURE__ */ jsx("rect", { x: 6, y: 6, width: 12, height: 12, rx: 2 })
1362
+ }
1363
+ ),
1364
+ "Stop Recording"
1365
+ ] }) : recordingSeconds > 0 ? "Record Again" : "Start Recording"
1366
+ }
1367
+ ) })
1368
+ ] }),
1369
+ activeTab === "text" && /* @__PURE__ */ jsxs("div", { children: [
1370
+ /* @__PURE__ */ jsx(
1371
+ "textarea",
1372
+ {
1373
+ value: textContent,
1374
+ onChange: (e) => setTextContent(e.target.value),
1375
+ placeholder: "Tell us what you think...",
1376
+ style: {
1377
+ width: "100%",
1378
+ minHeight: 150,
1379
+ padding: 16,
1380
+ fontSize: 14,
1381
+ lineHeight: 1.6,
1382
+ color: "var(--widget-text-primary)",
1383
+ background: "var(--widget-glass-bg)",
1384
+ border: "1px solid var(--widget-glass-border)",
1385
+ borderRadius: 12,
1386
+ resize: "vertical",
1387
+ fontFamily: "inherit"
1388
+ }
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsxs(
1392
+ "div",
1393
+ {
1394
+ style: {
1395
+ display: "flex",
1396
+ justifyContent: "flex-end",
1397
+ marginTop: 8,
1398
+ fontSize: 12,
1399
+ color: "var(--widget-text-muted)"
1400
+ },
1401
+ children: [
1402
+ textContent.length,
1403
+ " / 5000"
1404
+ ]
1405
+ }
1406
+ )
1407
+ ] })
1408
+ ] }),
1409
+ /* @__PURE__ */ jsx(
1410
+ "div",
1411
+ {
1412
+ style: {
1413
+ padding: "16px 24px",
1414
+ borderTop: "1px solid var(--widget-glass-border)"
1415
+ },
1416
+ children: /* @__PURE__ */ jsx(
1417
+ MagneticButton,
1418
+ {
1419
+ onClick: handleSubmit,
1420
+ disabled: !selectedCategory || activeTab === "voice" && !transcription || activeTab === "text" && !textContent.trim(),
1421
+ loading: isSubmitting,
1422
+ children: "Submit Feedback"
1423
+ }
1424
+ )
1425
+ }
1426
+ )
1427
+ ]
1428
+ },
1429
+ "content"
1430
+ ) })
1431
+ },
1432
+ "modal"
1433
+ ) }),
1434
+ /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: 1, height: 1, overflow: "hidden" }, children: [
1435
+ isRecording && "Recording started. Speak now.",
1436
+ !isRecording && transcription && `Transcription complete.`,
1437
+ state === "success" && "Feedback submitted successfully."
1438
+ ] })
1439
+ ]
1440
+ }
1441
+ ) });
1442
+ };
1443
+ function adjustColor(color, amount) {
1444
+ const hex = color.replace("#", "");
1445
+ const num = parseInt(hex, 16);
1446
+ const r = Math.min(255, Math.max(0, (num >> 16) + amount));
1447
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
1448
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
1449
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1450
+ }
1451
+
1452
+ // src/index.tsx
1453
+ function IntentAIWidget({
1454
+ apiKey,
1455
+ apiUrl,
1456
+ widgetUrl,
1457
+ position = "bottom-right",
1458
+ theme = "light",
1459
+ primaryColor,
1460
+ allowVoice = true,
1461
+ allowText = true,
1462
+ allowScreenshot = true,
1463
+ customMetadata,
1464
+ user,
1465
+ onOpen: _onOpen,
1466
+ onClose: _onClose,
1467
+ onFeedbackSubmitted: _onFeedbackSubmitted,
1468
+ onError
1469
+ }) {
1470
+ const widgetRef = useRef2(null);
1471
+ const scriptLoadedRef = useRef2(false);
1472
+ const initWidget = useCallback2(() => {
1473
+ if (!window.FeedbackIQ || widgetRef.current) return;
1474
+ try {
1475
+ widgetRef.current = new window.FeedbackIQ({
1476
+ apiKey,
1477
+ apiUrl,
1478
+ widgetUrl,
1479
+ position,
1480
+ theme,
1481
+ primaryColor,
1482
+ allowVoice,
1483
+ allowText,
1484
+ allowScreenshot,
1485
+ customMetadata,
1486
+ user
1487
+ });
1488
+ } catch (error) {
1489
+ onError?.(error instanceof Error ? error : new Error("Failed to initialize widget"));
1490
+ }
1491
+ void _onOpen;
1492
+ void _onClose;
1493
+ void _onFeedbackSubmitted;
1494
+ }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1495
+ useEffect2(() => {
1496
+ if (!widgetUrl) {
1497
+ onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1498
+ return;
1499
+ }
1500
+ if (window.FeedbackIQ) {
1501
+ initWidget();
1502
+ return;
1503
+ }
1504
+ if (scriptLoadedRef.current) return;
1505
+ const existingScript = document.querySelector(`script[src="${widgetUrl}"]`);
1506
+ if (existingScript) {
1507
+ existingScript.addEventListener("load", initWidget);
1508
+ return;
1509
+ }
1510
+ scriptLoadedRef.current = true;
1511
+ const script = document.createElement("script");
1512
+ script.src = widgetUrl;
1513
+ script.async = true;
1514
+ script.onload = initWidget;
1515
+ script.onerror = () => {
1516
+ onError?.(new Error("Failed to load Intent AI widget script"));
1517
+ };
1518
+ document.head.appendChild(script);
1519
+ return () => {
1520
+ if (widgetRef.current) {
1521
+ widgetRef.current.destroy();
1522
+ widgetRef.current = null;
1523
+ }
1524
+ };
1525
+ }, [initWidget, onError, widgetUrl]);
1526
+ useEffect2(() => {
1527
+ if (widgetRef.current && user) {
1528
+ widgetRef.current.identify(user);
1529
+ }
1530
+ }, [user]);
1531
+ useEffect2(() => {
1532
+ if (widgetRef.current && customMetadata) {
1533
+ widgetRef.current.setMetadata(customMetadata);
1534
+ }
1535
+ }, [customMetadata]);
1536
+ return null;
1537
+ }
1538
+ function useIntentAI() {
1539
+ const open = useCallback2(() => {
1540
+ const widget = document.getElementById("feedbackiq-widget");
1541
+ if (widget) {
1542
+ const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1543
+ trigger?.click();
1544
+ }
1545
+ }, []);
1546
+ const close = useCallback2(() => {
1547
+ const widget = document.getElementById("feedbackiq-widget");
1548
+ if (widget) {
1549
+ const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1550
+ closeBtn?.click();
1551
+ }
1552
+ }, []);
1553
+ const identify = useCallback2((user) => {
1554
+ if (window.IntentAI?.widget) {
1555
+ window.IntentAI.widget.identify(user);
1556
+ }
1557
+ }, []);
1558
+ const setMetadata = useCallback2((metadata) => {
1559
+ if (window.IntentAI?.widget) {
1560
+ window.IntentAI.widget.setMetadata(metadata);
1561
+ }
1562
+ }, []);
1563
+ return { open, close, identify, setMetadata };
1564
+ }
1565
+ var index_default = IntentAIWidget;
1566
+ export {
1567
+ IntentAIWidget,
1568
+ PremiumVoiceWidget,
1569
+ index_default as default,
1570
+ useIntentAI
1571
+ };