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