@intentai/react 1.0.2 → 2.0.0

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,1485 @@
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: 400,
176
+ height: 520,
177
+ borderRadius: 24,
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: 120,
263
+ y: 120,
264
+ angle: Math.random() * Math.PI * 2,
265
+ speed: 1 + Math.random() * 3,
266
+ size: 2 + Math.random() * 4,
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: 240, height: 240 }, children: /* @__PURE__ */ jsxs("svg", { width: 240, height: 240, viewBox: "0 0 240 240", 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: 120,
295
+ cy: 120,
296
+ r: 80,
297
+ fill: "none",
298
+ stroke: "var(--widget-primary-glow)",
299
+ strokeWidth: 2,
300
+ animate: {
301
+ r: isRecording ? [80, 90, 80] : 80,
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: 120,
315
+ cy: 120,
316
+ r: 60,
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(100, 95)", 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: particle.x,
357
+ cy: particle.y,
358
+ r: particle.size,
359
+ fill: particle.color,
360
+ initial: { opacity: 0.8, scale: 1 },
361
+ animate: {
362
+ cx: particle.x + Math.cos(particle.angle) * 80,
363
+ cy: particle.y + Math.sin(particle.angle) * 80,
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 ListeningDots = () => /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
504
+ motion.span,
505
+ {
506
+ animate: {
507
+ scale: [1, 1.4, 1],
508
+ opacity: [0.5, 1, 0.5]
509
+ },
510
+ transition: {
511
+ duration: 1,
512
+ repeat: Infinity,
513
+ delay: i * 0.15
514
+ },
515
+ style: {
516
+ width: 6,
517
+ height: 6,
518
+ borderRadius: "50%",
519
+ background: "var(--widget-primary)"
520
+ }
521
+ },
522
+ i
523
+ )) });
524
+ var CategorySelector = ({ categories, selected, onSelect }) => {
525
+ return /* @__PURE__ */ jsxs("div", { className: "fiq-category-selector", children: [
526
+ /* @__PURE__ */ jsx(
527
+ "label",
528
+ {
529
+ style: {
530
+ display: "block",
531
+ fontSize: 13,
532
+ fontWeight: 600,
533
+ color: "var(--widget-text-secondary)",
534
+ marginBottom: 10,
535
+ textTransform: "uppercase",
536
+ letterSpacing: "0.05em"
537
+ },
538
+ children: "Category"
539
+ }
540
+ ),
541
+ /* @__PURE__ */ jsx(
542
+ "div",
543
+ {
544
+ style: {
545
+ display: "grid",
546
+ gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
547
+ gap: 10
548
+ },
549
+ children: categories.map((category, index) => /* @__PURE__ */ jsxs(
550
+ motion.button,
551
+ {
552
+ custom: index,
553
+ variants: categoryVariants,
554
+ initial: "hidden",
555
+ animate: selected === category.id ? "selected" : "visible",
556
+ whileHover: "hover",
557
+ whileTap: "tap",
558
+ onClick: () => onSelect(category.id),
559
+ style: {
560
+ display: "flex",
561
+ flexDirection: "column",
562
+ alignItems: "center",
563
+ gap: 6,
564
+ padding: "12px 8px",
565
+ background: selected === category.id ? "var(--widget-primary)" : "var(--widget-glass-bg)",
566
+ border: `2px solid ${selected === category.id ? "var(--widget-primary)" : "var(--widget-glass-border)"}`,
567
+ borderRadius: 12,
568
+ cursor: "pointer",
569
+ transition: "background 0.2s, border-color 0.2s"
570
+ },
571
+ children: [
572
+ /* @__PURE__ */ jsx(
573
+ motion.span,
574
+ {
575
+ style: { fontSize: 20 },
576
+ animate: selected === category.id ? { scale: [1, 1.3, 1], rotate: [0, 10, -10, 0] } : {},
577
+ transition: { duration: 0.4 },
578
+ children: category.icon
579
+ }
580
+ ),
581
+ /* @__PURE__ */ jsx(
582
+ "span",
583
+ {
584
+ style: {
585
+ fontSize: 12,
586
+ fontWeight: 500,
587
+ color: selected === category.id ? "white" : "var(--widget-text-primary)"
588
+ },
589
+ children: category.label
590
+ }
591
+ )
592
+ ]
593
+ },
594
+ category.id
595
+ ))
596
+ }
597
+ )
598
+ ] });
599
+ };
600
+ var SuccessCelebration = ({ onComplete }) => {
601
+ const { reducedMotion } = useWidget();
602
+ const confettiParticles = useMemo(
603
+ () => reducedMotion ? [] : Array.from({ length: 30 }, (_, i) => ({
604
+ id: i,
605
+ angle: i / 30 * Math.PI * 2,
606
+ distance: 100 + Math.random() * 80,
607
+ size: 4 + Math.random() * 6,
608
+ color: ["#10b981", "#22d3d3", "#fbbf24", "#f472b6"][Math.floor(Math.random() * 4)],
609
+ rotation: Math.random() * 360
610
+ })),
611
+ [reducedMotion]
612
+ );
613
+ useEffect(() => {
614
+ const timer = setTimeout(onComplete, 3e3);
615
+ return () => clearTimeout(timer);
616
+ }, [onComplete]);
617
+ return /* @__PURE__ */ jsxs(
618
+ motion.div,
619
+ {
620
+ initial: { opacity: 0, scale: 0.8 },
621
+ animate: { opacity: 1, scale: 1 },
622
+ exit: { opacity: 0, scale: 0.8 },
623
+ style: {
624
+ display: "flex",
625
+ flexDirection: "column",
626
+ alignItems: "center",
627
+ justifyContent: "center",
628
+ padding: 40,
629
+ textAlign: "center",
630
+ position: "relative",
631
+ overflow: "hidden"
632
+ },
633
+ children: [
634
+ confettiParticles.map((particle) => /* @__PURE__ */ jsx(
635
+ motion.div,
636
+ {
637
+ initial: { x: 0, y: 0, scale: 0, rotate: 0 },
638
+ animate: {
639
+ x: Math.cos(particle.angle) * particle.distance,
640
+ y: Math.sin(particle.angle) * particle.distance + 50,
641
+ scale: [0, 1, 1, 0],
642
+ rotate: particle.rotation
643
+ },
644
+ transition: { duration: 1.2, ease: [0.22, 1, 0.36, 1] },
645
+ style: {
646
+ position: "absolute",
647
+ top: "50%",
648
+ left: "50%",
649
+ width: particle.size,
650
+ height: particle.size,
651
+ background: particle.color,
652
+ borderRadius: Math.random() > 0.5 ? "50%" : 2
653
+ }
654
+ },
655
+ particle.id
656
+ )),
657
+ /* @__PURE__ */ jsx(motion.div, { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxs("svg", { width: 80, height: 80, viewBox: "0 0 80 80", children: [
658
+ /* @__PURE__ */ jsx(
659
+ motion.circle,
660
+ {
661
+ cx: 40,
662
+ cy: 40,
663
+ r: 36,
664
+ fill: "var(--widget-success)",
665
+ initial: { scale: 0 },
666
+ animate: { scale: 1 },
667
+ transition: springPresets.bouncy
668
+ }
669
+ ),
670
+ /* @__PURE__ */ jsx(
671
+ motion.path,
672
+ {
673
+ d: "M24 40 L35 51 L56 28",
674
+ fill: "none",
675
+ stroke: "white",
676
+ strokeWidth: 5,
677
+ strokeLinecap: "round",
678
+ strokeLinejoin: "round",
679
+ initial: { pathLength: 0 },
680
+ animate: { pathLength: 1 },
681
+ transition: { delay: 0.3, duration: 0.5, ease: "easeOut" }
682
+ }
683
+ )
684
+ ] }) }),
685
+ /* @__PURE__ */ jsx(
686
+ motion.h3,
687
+ {
688
+ initial: { opacity: 0, y: 20 },
689
+ animate: { opacity: 1, y: 0 },
690
+ transition: { delay: 0.5 },
691
+ style: {
692
+ fontSize: 24,
693
+ fontWeight: 700,
694
+ color: "var(--widget-text-primary)",
695
+ margin: 0,
696
+ marginBottom: 8
697
+ },
698
+ children: "Thank you!"
699
+ }
700
+ ),
701
+ /* @__PURE__ */ jsx(
702
+ motion.p,
703
+ {
704
+ initial: { opacity: 0, y: 20 },
705
+ animate: { opacity: 1, y: 0 },
706
+ transition: { delay: 0.6 },
707
+ style: {
708
+ fontSize: 14,
709
+ color: "var(--widget-text-secondary)",
710
+ margin: 0
711
+ },
712
+ children: "Your feedback helps us improve"
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsx(
716
+ motion.div,
717
+ {
718
+ style: {
719
+ position: "absolute",
720
+ bottom: 20,
721
+ left: "50%",
722
+ transform: "translateX(-50%)",
723
+ width: 100,
724
+ height: 3,
725
+ background: "var(--widget-glass-border)",
726
+ borderRadius: 2,
727
+ overflow: "hidden"
728
+ },
729
+ children: /* @__PURE__ */ jsx(
730
+ motion.div,
731
+ {
732
+ initial: { width: "100%" },
733
+ animate: { width: 0 },
734
+ transition: { duration: 3, ease: "linear" },
735
+ style: {
736
+ height: "100%",
737
+ background: "var(--widget-primary)"
738
+ }
739
+ }
740
+ )
741
+ }
742
+ )
743
+ ]
744
+ }
745
+ );
746
+ };
747
+ var MagneticButton = ({ children, onClick, variant = "primary", disabled, loading }) => {
748
+ const buttonRef = useRef(null);
749
+ const [magnetOffset, setMagnetOffset] = useState({ x: 0, y: 0 });
750
+ const handleMouseMove = (e) => {
751
+ if (!buttonRef.current || disabled) return;
752
+ const rect = buttonRef.current.getBoundingClientRect();
753
+ const centerX = rect.left + rect.width / 2;
754
+ const centerY = rect.top + rect.height / 2;
755
+ setMagnetOffset({
756
+ x: (e.clientX - centerX) * 0.1,
757
+ y: (e.clientY - centerY) * 0.1
758
+ });
759
+ };
760
+ const handleMouseLeave = () => {
761
+ setMagnetOffset({ x: 0, y: 0 });
762
+ };
763
+ const baseStyles = {
764
+ width: "100%",
765
+ padding: "14px 24px",
766
+ fontSize: 15,
767
+ fontWeight: 600,
768
+ borderRadius: 12,
769
+ border: "none",
770
+ cursor: disabled ? "not-allowed" : "pointer",
771
+ transition: "background 0.2s, opacity 0.2s",
772
+ display: "flex",
773
+ alignItems: "center",
774
+ justifyContent: "center",
775
+ gap: 8
776
+ };
777
+ const variantStyles = {
778
+ primary: {
779
+ background: "var(--widget-primary)",
780
+ color: "white"
781
+ },
782
+ secondary: {
783
+ background: "var(--widget-glass-bg)",
784
+ color: "var(--widget-text-primary)",
785
+ border: "1px solid var(--widget-glass-border)"
786
+ },
787
+ ghost: {
788
+ background: "transparent",
789
+ color: "var(--widget-text-secondary)"
790
+ }
791
+ };
792
+ return /* @__PURE__ */ jsx(
793
+ motion.button,
794
+ {
795
+ ref: buttonRef,
796
+ onClick,
797
+ disabled: disabled || loading,
798
+ onMouseMove: handleMouseMove,
799
+ onMouseLeave: handleMouseLeave,
800
+ animate: {
801
+ x: magnetOffset.x,
802
+ y: magnetOffset.y,
803
+ opacity: disabled ? 0.5 : 1
804
+ },
805
+ whileHover: { scale: disabled ? 1 : 1.02 },
806
+ whileTap: { scale: disabled ? 1 : 0.97 },
807
+ transition: springPresets.snappy,
808
+ style: { ...baseStyles, ...variantStyles[variant] },
809
+ children: loading ? /* @__PURE__ */ jsx(
810
+ motion.div,
811
+ {
812
+ animate: { rotate: 360 },
813
+ transition: { duration: 1, repeat: Infinity, ease: "linear" },
814
+ style: {
815
+ width: 18,
816
+ height: 18,
817
+ border: "2px solid rgba(255,255,255,0.3)",
818
+ borderTopColor: "white",
819
+ borderRadius: "50%"
820
+ }
821
+ }
822
+ ) : children
823
+ }
824
+ );
825
+ };
826
+ var DEFAULT_CATEGORIES = [
827
+ { id: "bug", label: "Bug", icon: "\u{1F41B}" },
828
+ { id: "feature", label: "Feature", icon: "\u2728" },
829
+ { id: "improvement", label: "Improve", icon: "\u{1F4A1}" },
830
+ { id: "praise", label: "Praise", icon: "\u2764\uFE0F" },
831
+ { id: "other", label: "Other", icon: "\u{1F4AC}" }
832
+ ];
833
+ var PremiumVoiceWidget = ({
834
+ position = "bottom-right",
835
+ primaryColor = "#10b981",
836
+ accentColor = "#22d3d3",
837
+ theme = "dark",
838
+ categories = DEFAULT_CATEGORIES,
839
+ allowVoice = true,
840
+ allowText = true,
841
+ maxRecordingDuration = 180,
842
+ onSubmit,
843
+ onOpen,
844
+ onClose
845
+ }) => {
846
+ const [state, setState] = useState("idle");
847
+ const [isExpanded, setIsExpanded] = useState(false);
848
+ const [activeTab, setActiveTab] = useState(allowVoice ? "voice" : "text");
849
+ const [selectedCategory, setSelectedCategory] = useState(null);
850
+ const [isRecording, setIsRecording] = useState(false);
851
+ const [recordingSeconds, setRecordingSeconds] = useState(0);
852
+ const [transcription, setTranscription] = useState("");
853
+ const [textContent, setTextContent] = useState("");
854
+ const [isSubmitting, setIsSubmitting] = useState(false);
855
+ const reducedMotion = useReducedMotion();
856
+ const scrollState = useScrollAwareness();
857
+ const frustrationLevel = useFrustrationDetection();
858
+ const timeOnPage = useTimeOnPage();
859
+ const audioLevel = useAudioLevel(isRecording);
860
+ const triggerRef = useRef(null);
861
+ const recordingInterval = useRef(null);
862
+ const positionStyles = {
863
+ "bottom-right": { bottom: 24, right: 24 },
864
+ "bottom-left": { bottom: 24, left: 24 },
865
+ "top-right": { top: 24, right: 24 },
866
+ "top-left": { top: 24, left: 24 }
867
+ };
868
+ const cssVariables = {
869
+ "--widget-primary": primaryColor,
870
+ "--widget-primary-hover": adjustColor(primaryColor, -20),
871
+ "--widget-primary-light": adjustColor(primaryColor, 30),
872
+ "--widget-primary-glow": `${primaryColor}66`,
873
+ "--widget-accent": accentColor,
874
+ "--widget-accent-light": adjustColor(accentColor, 30),
875
+ "--widget-accent-glow": `${accentColor}4d`,
876
+ "--widget-glass-bg": theme === "dark" ? "rgba(24, 24, 27, 0.85)" : "rgba(255, 255, 255, 0.85)",
877
+ "--widget-glass-border": theme === "dark" ? "rgba(63, 63, 70, 0.5)" : "rgba(0, 0, 0, 0.1)",
878
+ "--widget-glass-highlight": theme === "dark" ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.02)",
879
+ "--widget-text-primary": theme === "dark" ? "#fafafa" : "#18181b",
880
+ "--widget-text-secondary": theme === "dark" ? "#a1a1aa" : "#71717a",
881
+ "--widget-text-muted": theme === "dark" ? "#71717a" : "#a1a1aa",
882
+ "--widget-recording": "#ef4444",
883
+ "--widget-recording-glow": "rgba(239, 68, 68, 0.4)",
884
+ "--widget-success": "#22c55e",
885
+ "--widget-success-glow": "rgba(34, 197, 94, 0.4)",
886
+ "--widget-frustrated": "#f97316"
887
+ };
888
+ const handleOpen = useCallback(() => {
889
+ setIsExpanded(true);
890
+ setState("open");
891
+ onOpen?.();
892
+ }, [onOpen]);
893
+ const handleClose = useCallback(() => {
894
+ setIsExpanded(false);
895
+ setState("idle");
896
+ setIsRecording(false);
897
+ setRecordingSeconds(0);
898
+ setTranscription("");
899
+ onClose?.();
900
+ }, [onClose]);
901
+ const handleStartRecording = useCallback(() => {
902
+ setIsRecording(true);
903
+ setState("recording");
904
+ setRecordingSeconds(0);
905
+ const samplePhrases = [
906
+ "I really like the new dashboard design, ",
907
+ "but I think the navigation could be improved. ",
908
+ "Sometimes I have trouble finding the settings page."
909
+ ];
910
+ let phraseIndex = 0;
911
+ recordingInterval.current = setInterval(() => {
912
+ setRecordingSeconds((prev) => {
913
+ if (prev >= maxRecordingDuration) {
914
+ handleStopRecording();
915
+ return prev;
916
+ }
917
+ return prev + 1;
918
+ });
919
+ if (phraseIndex < samplePhrases.length && Math.random() > 0.7) {
920
+ setTranscription((prev) => prev + samplePhrases[phraseIndex]);
921
+ phraseIndex++;
922
+ }
923
+ }, 1e3);
924
+ }, [maxRecordingDuration]);
925
+ const handleStopRecording = useCallback(() => {
926
+ setIsRecording(false);
927
+ setState("open");
928
+ if (recordingInterval.current) {
929
+ clearInterval(recordingInterval.current);
930
+ }
931
+ }, []);
932
+ const handleSubmit = useCallback(async () => {
933
+ if (!selectedCategory) return;
934
+ setIsSubmitting(true);
935
+ setState("processing");
936
+ try {
937
+ await onSubmit?.({
938
+ type: activeTab,
939
+ content: activeTab === "voice" ? transcription : textContent,
940
+ category: selectedCategory,
941
+ transcription: activeTab === "voice" ? transcription : void 0
942
+ });
943
+ setState("success");
944
+ } catch (error) {
945
+ setState("error");
946
+ } finally {
947
+ setIsSubmitting(false);
948
+ }
949
+ }, [activeTab, selectedCategory, transcription, textContent, onSubmit]);
950
+ const handleSuccessComplete = useCallback(() => {
951
+ handleClose();
952
+ setSelectedCategory(null);
953
+ setTextContent("");
954
+ setTranscription("");
955
+ }, [handleClose]);
956
+ const contextValue = {
957
+ state,
958
+ setState,
959
+ audioLevel,
960
+ isRecording,
961
+ transcription,
962
+ selectedCategory,
963
+ setSelectedCategory,
964
+ frustrationLevel,
965
+ reducedMotion
966
+ };
967
+ const triggerState = useMemo(() => {
968
+ if (isExpanded) return "hidden";
969
+ if (scrollState === "scrolling") return "scrolling";
970
+ if (state === "hover") return "hover";
971
+ return "idle";
972
+ }, [isExpanded, scrollState, state]);
973
+ const shouldShowAttentionPulse = timeOnPage === 60 && !isExpanded && frustrationLevel < 3;
974
+ return /* @__PURE__ */ jsx(WidgetContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
975
+ "div",
976
+ {
977
+ className: "fiq-premium-widget",
978
+ style: {
979
+ ...cssVariables,
980
+ position: "fixed",
981
+ ...positionStyles[position],
982
+ zIndex: 2147483640,
983
+ fontFamily: "system-ui, -apple-system, sans-serif"
984
+ },
985
+ children: [
986
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: !isExpanded && /* @__PURE__ */ jsxs(
987
+ motion.button,
988
+ {
989
+ ref: triggerRef,
990
+ variants: triggerVariants,
991
+ initial: "idle",
992
+ animate: triggerState,
993
+ exit: "hidden",
994
+ whileHover: "hover",
995
+ whileTap: "tap",
996
+ onClick: handleOpen,
997
+ onMouseEnter: () => setState("hover"),
998
+ onMouseLeave: () => setState("idle"),
999
+ "aria-label": "Open feedback widget",
1000
+ "aria-expanded": isExpanded,
1001
+ style: {
1002
+ width: 56,
1003
+ height: 56,
1004
+ borderRadius: "50%",
1005
+ border: "none",
1006
+ cursor: "pointer",
1007
+ position: "relative",
1008
+ background: "var(--widget-glass-bg)",
1009
+ backdropFilter: "blur(20px) saturate(180%)",
1010
+ WebkitBackdropFilter: "blur(20px) saturate(180%)",
1011
+ boxShadow: `
1012
+ inset 0 0 0 1px var(--widget-glass-highlight),
1013
+ 0 4px 24px -4px rgba(0, 0, 0, 0.5),
1014
+ 0 0 40px -10px var(--widget-primary-glow)
1015
+ `
1016
+ },
1017
+ children: [
1018
+ /* @__PURE__ */ jsx(BreathingRing, { isActive: !reducedMotion && triggerState === "idle" }),
1019
+ /* @__PURE__ */ jsx(
1020
+ "div",
1021
+ {
1022
+ style: {
1023
+ position: "absolute",
1024
+ inset: 3,
1025
+ borderRadius: "50%",
1026
+ background: `linear-gradient(135deg, var(--widget-primary) 0%, var(--widget-accent) 100%)`,
1027
+ opacity: 0.9
1028
+ }
1029
+ }
1030
+ ),
1031
+ /* @__PURE__ */ jsxs(
1032
+ "svg",
1033
+ {
1034
+ width: 24,
1035
+ height: 24,
1036
+ viewBox: "0 0 24 24",
1037
+ fill: "none",
1038
+ stroke: "white",
1039
+ strokeWidth: 2,
1040
+ strokeLinecap: "round",
1041
+ style: { position: "relative", zIndex: 1 },
1042
+ children: [
1043
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
1044
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1045
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
1046
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
1047
+ ]
1048
+ }
1049
+ ),
1050
+ shouldShowAttentionPulse && /* @__PURE__ */ jsx(
1051
+ motion.div,
1052
+ {
1053
+ initial: { scale: 1, opacity: 1 },
1054
+ animate: { scale: 1.5, opacity: 0 },
1055
+ transition: { duration: 1, repeat: 3 },
1056
+ style: {
1057
+ position: "absolute",
1058
+ inset: 0,
1059
+ borderRadius: "50%",
1060
+ border: "2px solid var(--widget-primary)",
1061
+ pointerEvents: "none"
1062
+ }
1063
+ }
1064
+ ),
1065
+ frustrationLevel >= 5 && /* @__PURE__ */ jsx(
1066
+ motion.div,
1067
+ {
1068
+ initial: { scale: 0 },
1069
+ animate: { scale: 1 },
1070
+ style: {
1071
+ position: "absolute",
1072
+ top: -4,
1073
+ right: -4,
1074
+ width: 16,
1075
+ height: 16,
1076
+ borderRadius: "50%",
1077
+ background: "var(--widget-frustrated)",
1078
+ border: "2px solid var(--widget-glass-bg)",
1079
+ display: "flex",
1080
+ alignItems: "center",
1081
+ justifyContent: "center",
1082
+ fontSize: 10
1083
+ },
1084
+ children: "?"
1085
+ }
1086
+ )
1087
+ ]
1088
+ },
1089
+ "trigger"
1090
+ ) }),
1091
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isExpanded && /* @__PURE__ */ jsx(
1092
+ motion.div,
1093
+ {
1094
+ variants: containerVariants,
1095
+ initial: "trigger",
1096
+ animate: "expanded",
1097
+ exit: "trigger",
1098
+ style: {
1099
+ background: "var(--widget-glass-bg)",
1100
+ backdropFilter: "blur(20px) saturate(180%)",
1101
+ WebkitBackdropFilter: "blur(20px) saturate(180%)",
1102
+ border: "1px solid var(--widget-glass-border)",
1103
+ boxShadow: "0 20px 60px -12px rgba(0, 0, 0, 0.5)",
1104
+ overflow: "hidden",
1105
+ display: "flex",
1106
+ flexDirection: "column"
1107
+ },
1108
+ role: "dialog",
1109
+ "aria-modal": "true",
1110
+ "aria-labelledby": "widget-title",
1111
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: state === "success" ? /* @__PURE__ */ jsx(SuccessCelebration, { onComplete: handleSuccessComplete }, "success") : /* @__PURE__ */ jsxs(
1112
+ motion.div,
1113
+ {
1114
+ variants: contentVariants,
1115
+ initial: "hidden",
1116
+ animate: "visible",
1117
+ exit: "exit",
1118
+ style: { display: "flex", flexDirection: "column", height: "100%" },
1119
+ children: [
1120
+ /* @__PURE__ */ jsxs(
1121
+ "div",
1122
+ {
1123
+ style: {
1124
+ display: "flex",
1125
+ justifyContent: "space-between",
1126
+ alignItems: "center",
1127
+ padding: "20px 24px",
1128
+ borderBottom: "1px solid var(--widget-glass-border)"
1129
+ },
1130
+ children: [
1131
+ /* @__PURE__ */ jsx(
1132
+ "h2",
1133
+ {
1134
+ id: "widget-title",
1135
+ style: {
1136
+ fontSize: 18,
1137
+ fontWeight: 700,
1138
+ color: "var(--widget-text-primary)",
1139
+ margin: 0
1140
+ },
1141
+ children: "Share Your Thoughts"
1142
+ }
1143
+ ),
1144
+ /* @__PURE__ */ jsx(
1145
+ motion.button,
1146
+ {
1147
+ whileHover: { scale: 1.1, rotate: 90 },
1148
+ whileTap: { scale: 0.9 },
1149
+ onClick: handleClose,
1150
+ "aria-label": "Close widget",
1151
+ style: {
1152
+ width: 32,
1153
+ height: 32,
1154
+ borderRadius: 8,
1155
+ border: "none",
1156
+ background: "transparent",
1157
+ color: "var(--widget-text-secondary)",
1158
+ cursor: "pointer",
1159
+ display: "flex",
1160
+ alignItems: "center",
1161
+ justifyContent: "center"
1162
+ },
1163
+ children: /* @__PURE__ */ jsx(
1164
+ "svg",
1165
+ {
1166
+ width: 20,
1167
+ height: 20,
1168
+ viewBox: "0 0 24 24",
1169
+ fill: "none",
1170
+ stroke: "currentColor",
1171
+ strokeWidth: 2,
1172
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
1173
+ }
1174
+ )
1175
+ }
1176
+ )
1177
+ ]
1178
+ }
1179
+ ),
1180
+ allowVoice && allowText && /* @__PURE__ */ jsx(
1181
+ "div",
1182
+ {
1183
+ style: {
1184
+ display: "flex",
1185
+ borderBottom: "1px solid var(--widget-glass-border)"
1186
+ },
1187
+ children: ["voice", "text"].map((tab) => /* @__PURE__ */ jsxs(
1188
+ motion.button,
1189
+ {
1190
+ onClick: () => setActiveTab(tab),
1191
+ whileHover: { backgroundColor: "rgba(255,255,255,0.05)" },
1192
+ style: {
1193
+ flex: 1,
1194
+ padding: "14px 0",
1195
+ border: "none",
1196
+ background: "transparent",
1197
+ fontSize: 14,
1198
+ fontWeight: 500,
1199
+ color: activeTab === tab ? "var(--widget-primary)" : "var(--widget-text-secondary)",
1200
+ cursor: "pointer",
1201
+ position: "relative"
1202
+ },
1203
+ children: [
1204
+ tab === "voice" ? "Voice" : "Text",
1205
+ activeTab === tab && /* @__PURE__ */ jsx(
1206
+ motion.div,
1207
+ {
1208
+ layoutId: "activeTab",
1209
+ style: {
1210
+ position: "absolute",
1211
+ bottom: -1,
1212
+ left: 0,
1213
+ right: 0,
1214
+ height: 2,
1215
+ background: "var(--widget-primary)"
1216
+ },
1217
+ transition: springPresets.smooth
1218
+ }
1219
+ )
1220
+ ]
1221
+ },
1222
+ tab
1223
+ ))
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflow: "auto", padding: 24 }, children: [
1227
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx(
1228
+ CategorySelector,
1229
+ {
1230
+ categories,
1231
+ selected: selectedCategory,
1232
+ onSelect: setSelectedCategory
1233
+ }
1234
+ ) }),
1235
+ activeTab === "voice" && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
1236
+ /* @__PURE__ */ jsx(AmbientOrb, { audioLevel, isRecording }),
1237
+ isRecording && /* @__PURE__ */ jsx(
1238
+ RecordingTimer,
1239
+ {
1240
+ seconds: recordingSeconds,
1241
+ maxDuration: maxRecordingDuration
1242
+ }
1243
+ ),
1244
+ !isRecording && recordingSeconds === 0 && /* @__PURE__ */ jsx(
1245
+ "p",
1246
+ {
1247
+ style: {
1248
+ color: "var(--widget-text-secondary)",
1249
+ fontSize: 14,
1250
+ marginTop: 16
1251
+ },
1252
+ children: "Tap the orb to start recording"
1253
+ }
1254
+ ),
1255
+ (isRecording || transcription) && /* @__PURE__ */ jsx("div", { style: { marginTop: 20 }, children: /* @__PURE__ */ jsx(
1256
+ TranscriptionDisplay,
1257
+ {
1258
+ text: transcription,
1259
+ isLive: isRecording
1260
+ }
1261
+ ) }),
1262
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 20 }, children: /* @__PURE__ */ jsx(
1263
+ MagneticButton,
1264
+ {
1265
+ onClick: isRecording ? handleStopRecording : handleStartRecording,
1266
+ variant: isRecording ? "secondary" : "primary",
1267
+ children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
1268
+ /* @__PURE__ */ jsx(
1269
+ "svg",
1270
+ {
1271
+ width: 16,
1272
+ height: 16,
1273
+ viewBox: "0 0 24 24",
1274
+ fill: "currentColor",
1275
+ children: /* @__PURE__ */ jsx("rect", { x: 6, y: 6, width: 12, height: 12, rx: 2 })
1276
+ }
1277
+ ),
1278
+ "Stop Recording"
1279
+ ] }) : recordingSeconds > 0 ? "Record Again" : "Start Recording"
1280
+ }
1281
+ ) })
1282
+ ] }),
1283
+ activeTab === "text" && /* @__PURE__ */ jsxs("div", { children: [
1284
+ /* @__PURE__ */ jsx(
1285
+ "textarea",
1286
+ {
1287
+ value: textContent,
1288
+ onChange: (e) => setTextContent(e.target.value),
1289
+ placeholder: "Tell us what you think...",
1290
+ style: {
1291
+ width: "100%",
1292
+ minHeight: 150,
1293
+ padding: 16,
1294
+ fontSize: 14,
1295
+ lineHeight: 1.6,
1296
+ color: "var(--widget-text-primary)",
1297
+ background: "var(--widget-glass-bg)",
1298
+ border: "1px solid var(--widget-glass-border)",
1299
+ borderRadius: 12,
1300
+ resize: "vertical",
1301
+ fontFamily: "inherit"
1302
+ }
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ jsxs(
1306
+ "div",
1307
+ {
1308
+ style: {
1309
+ display: "flex",
1310
+ justifyContent: "flex-end",
1311
+ marginTop: 8,
1312
+ fontSize: 12,
1313
+ color: "var(--widget-text-muted)"
1314
+ },
1315
+ children: [
1316
+ textContent.length,
1317
+ " / 5000"
1318
+ ]
1319
+ }
1320
+ )
1321
+ ] })
1322
+ ] }),
1323
+ /* @__PURE__ */ jsx(
1324
+ "div",
1325
+ {
1326
+ style: {
1327
+ padding: "16px 24px",
1328
+ borderTop: "1px solid var(--widget-glass-border)"
1329
+ },
1330
+ children: /* @__PURE__ */ jsx(
1331
+ MagneticButton,
1332
+ {
1333
+ onClick: handleSubmit,
1334
+ disabled: !selectedCategory || activeTab === "voice" && !transcription || activeTab === "text" && !textContent.trim(),
1335
+ loading: isSubmitting,
1336
+ children: "Submit Feedback"
1337
+ }
1338
+ )
1339
+ }
1340
+ )
1341
+ ]
1342
+ },
1343
+ "content"
1344
+ ) })
1345
+ },
1346
+ "modal"
1347
+ ) }),
1348
+ /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: 1, height: 1, overflow: "hidden" }, children: [
1349
+ isRecording && "Recording started. Speak now.",
1350
+ !isRecording && transcription && `Transcription complete.`,
1351
+ state === "success" && "Feedback submitted successfully."
1352
+ ] })
1353
+ ]
1354
+ }
1355
+ ) });
1356
+ };
1357
+ function adjustColor(color, amount) {
1358
+ const hex = color.replace("#", "");
1359
+ const num = parseInt(hex, 16);
1360
+ const r = Math.min(255, Math.max(0, (num >> 16) + amount));
1361
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
1362
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
1363
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1364
+ }
1365
+
1366
+ // src/index.tsx
1367
+ function IntentAIWidget({
1368
+ apiKey,
1369
+ apiUrl,
1370
+ widgetUrl,
1371
+ position = "bottom-right",
1372
+ theme = "light",
1373
+ primaryColor,
1374
+ allowVoice = true,
1375
+ allowText = true,
1376
+ allowScreenshot = true,
1377
+ customMetadata,
1378
+ user,
1379
+ onOpen: _onOpen,
1380
+ onClose: _onClose,
1381
+ onFeedbackSubmitted: _onFeedbackSubmitted,
1382
+ onError
1383
+ }) {
1384
+ const widgetRef = useRef2(null);
1385
+ const scriptLoadedRef = useRef2(false);
1386
+ const initWidget = useCallback2(() => {
1387
+ if (!window.FeedbackIQ || widgetRef.current) return;
1388
+ try {
1389
+ widgetRef.current = new window.FeedbackIQ({
1390
+ apiKey,
1391
+ apiUrl,
1392
+ widgetUrl,
1393
+ position,
1394
+ theme,
1395
+ primaryColor,
1396
+ allowVoice,
1397
+ allowText,
1398
+ allowScreenshot,
1399
+ customMetadata,
1400
+ user
1401
+ });
1402
+ } catch (error) {
1403
+ onError?.(error instanceof Error ? error : new Error("Failed to initialize widget"));
1404
+ }
1405
+ void _onOpen;
1406
+ void _onClose;
1407
+ void _onFeedbackSubmitted;
1408
+ }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1409
+ useEffect2(() => {
1410
+ if (!widgetUrl) {
1411
+ onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1412
+ return;
1413
+ }
1414
+ if (window.FeedbackIQ) {
1415
+ initWidget();
1416
+ return;
1417
+ }
1418
+ if (scriptLoadedRef.current) return;
1419
+ const existingScript = document.querySelector(`script[src="${widgetUrl}"]`);
1420
+ if (existingScript) {
1421
+ existingScript.addEventListener("load", initWidget);
1422
+ return;
1423
+ }
1424
+ scriptLoadedRef.current = true;
1425
+ const script = document.createElement("script");
1426
+ script.src = widgetUrl;
1427
+ script.async = true;
1428
+ script.onload = initWidget;
1429
+ script.onerror = () => {
1430
+ onError?.(new Error("Failed to load Intent AI widget script"));
1431
+ };
1432
+ document.head.appendChild(script);
1433
+ return () => {
1434
+ if (widgetRef.current) {
1435
+ widgetRef.current.destroy();
1436
+ widgetRef.current = null;
1437
+ }
1438
+ };
1439
+ }, [initWidget, onError, widgetUrl]);
1440
+ useEffect2(() => {
1441
+ if (widgetRef.current && user) {
1442
+ widgetRef.current.identify(user);
1443
+ }
1444
+ }, [user]);
1445
+ useEffect2(() => {
1446
+ if (widgetRef.current && customMetadata) {
1447
+ widgetRef.current.setMetadata(customMetadata);
1448
+ }
1449
+ }, [customMetadata]);
1450
+ return null;
1451
+ }
1452
+ function useIntentAI() {
1453
+ const open = useCallback2(() => {
1454
+ const widget = document.getElementById("feedbackiq-widget");
1455
+ if (widget) {
1456
+ const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1457
+ trigger?.click();
1458
+ }
1459
+ }, []);
1460
+ const close = useCallback2(() => {
1461
+ const widget = document.getElementById("feedbackiq-widget");
1462
+ if (widget) {
1463
+ const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1464
+ closeBtn?.click();
1465
+ }
1466
+ }, []);
1467
+ const identify = useCallback2((user) => {
1468
+ if (window.IntentAI?.widget) {
1469
+ window.IntentAI.widget.identify(user);
1470
+ }
1471
+ }, []);
1472
+ const setMetadata = useCallback2((metadata) => {
1473
+ if (window.IntentAI?.widget) {
1474
+ window.IntentAI.widget.setMetadata(metadata);
1475
+ }
1476
+ }, []);
1477
+ return { open, close, identify, setMetadata };
1478
+ }
1479
+ var index_default = IntentAIWidget;
1480
+ export {
1481
+ IntentAIWidget,
1482
+ PremiumVoiceWidget,
1483
+ index_default as default,
1484
+ useIntentAI
1485
+ };